exchange() utility function, revision 3 (original) (raw)
Overview
Revises N3511 by adding a default template argument and N3608 by adding std:: qualifiers to the wording.
Atomic objects provide an atomic_exchange function ([atomics.types.operations.req]p18) that assigns a new value to the object and returns the old value. This operation is also useful on non-atomic objects, and this paper proposes adding it to the library. The benefit isn't huge, but neither is the specification cost.
template<typename T, typename U=T>
T exchange(T& obj, U&& new_val) {
T old_val = std::move(obj);
obj = std::forward<U>(new_val);
return old_val;
}
For primitive types, this is equivalent to the obvious implementation, while for more complex types, this definition
- Avoids copying the old value when that type defines a move constructor
- Accepts any type as the new value, taking advantage of any converting assignment operator
- Avoids copying the new value if it's a temporary or moved.
I chose the name for symmetry with atomic_exchange, since they behave the same except for this function not being atomic.
We could consider defaulting new_val to T{}, which makes null'ing out a pointer even simpler, but I think that makes the name less good, so I'm not proposing it.
Examples
c++std-ext-13750 mentions a use case for post-increment onbool, which this function would replace. This proposal comes from my message c++std-lib-33183 on that thread.
auto operator << (std::ostream& os, section_num const & sn) -> std::ostream & {
if (!sn.prefix.empty()) { os << sn.prefix << " "; }
bool use_period{false};
for (auto sub : sn.num ) {
if(use_period++) { // <-- deprecated check to omit some code on the first time through the loop
os << '.';
}
if (sub >= 100) {
os << char(sub - 100 + 'A');
}
else {
os << sub;
}
}
return os;
}becomes:
auto operator << (std::ostream& os, section_num const & sn) -> std::ostream & {
if (!sn.prefix.empty()) { os << sn.prefix << " "; }
bool use_period{false};
for (auto sub : sn.num ) {
if(std::exchange(use_period, true)) {
os << '.';
}
if (sub >= 100) {
os << char(sub - 100 + 'A');
}
else {
os << sub;
}
}
return os;
}Implementations of std::unique_ptr can also benefit:
template<typename T, typename D>
void unique_ptr<T, D>::reset(pointer p = pointer()) {
pointer old = ptr_;
ptr_ = p;
if (old)
deleter_(old);
}becomes:
template<typename T, typename D>
void unique_ptr<T, D>::reset(pointer p = pointer()) {
if (pointer old = std::exchange(ptr_, p))
deleter_(old);
}Giving the second template argument a default value fixes the following two cases:
- ```
DefaultConstructible x = ...;
if (exchange(x, {})) { ... }
* ```
int (*fp)(int);
int f(int);
double f(double);
/*...*/ exchange(fp, &f) /*...*/ Wording
Wording is relative to N3485.
In the "Header synopsis" in [utility], add:
// [utility.exchange] exchange:
template <class T, class U=T> T exchange(T& obj, U&& new_val);Add a sub-section under [utility] named "exchange [utility.exchange]"
template <class T, class U=T> T exchange(T& obj, U&& new_val);Effects: Equivalent to:
T old_val = std::move(obj);
obj = std::forward<U>(new_val);
return old_val;