ISO/IEC JTC1 SC22 WG21 N3668

Date:

Jeffrey Yasskin <jyasskin@google.com>

exchange() utility function, revision 3

Skip table of contents

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

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 on bool, 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:


Wording

Wording is relative to N3485.

In the "Header <utility> 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;