1. Changelog
-
R0
-
First submission
-
2. Motivation and Scope
's behavior is fully specified by the Standard in [utility.exchange]:
template < class T , class U = T > constexpr T exchange ( T & obj , U && new_val ) { T old_val = std :: move ( obj ); obj = std :: forward < U > ( new_val ); return old_val ; }
The body composes two move constructions (to build
, and to
return it) and an assignment from
onto
.
Such as specification would allow for a "natural"
specification,
which is however missing, making
never
(according to the Standard; but also on multiple implementations, see below).
This is unfortunate; for instance, a primary use case of
is in
the implementation of move constructors:
class buffer { int * data ; public : buffer () : data ( new int [ 42 ]) {} buffer ( buffer && other ) noexcept // at face value, this is noexcept(false)! : data ( std :: exchange ( other . data , nullptr )) {} ~ buffer () { delete [] data ; } ~~~ };
If one, pedantically, used a conditional
specification for
's move constructor, using the expressions present in the move
constructor itself (i.e. the
), the surprising answer would be
that the move constructor in question is not
.
This can, of course, be generalized to any function that features an usage of
and wants to provide a
specification written in
terms of its body. While the usage of conditional
specifiers is
"restricted" in the Standard Library by [P1656]'s policies (but see below),
users may want still to use conditional
as a contract. In this
sense, the Standard Library shouldn’t "lie" to them, and claim that operations
that cannot possibly throw (by contract of the inner operations) actually may.
Note that all the operations done by
's body are operations for
which a standardized type trait to detect whether the operation is
already exists; they are, respectively,
and
. The very fact that the operations have
existing type traits shows that it is meaningful to check for their
-ness. A function that composes such operations therefore can very
easily carry the information about whether or not it may possibly throw
exceptions.
2.1. Actual implementations
[res.on.exception.handling]/5 gives implementations some freedoms to mark
functions as
, even if they’re not specified as such in the Standard:
5: An implementation may strengthen the exception specification for a non-virtual function by adding a non-throwing exception specification.
does qualify for this. At the time of this writing, [MS-STL] in fact has a conditional
specification on
(functionally identical to the one we are proposing), but [libstdc++] and [libc++] do not feature one.
At least in the case of Microsoft’s compiler toolchain, the presence of
actually improves codegen of
in debug builds.
2.2. What about the Standard Library noexcept
policy?
[P1656] encodes the latest
policy for the Standard Library.
Its wording precludes any functions but
, default/copy/move constructors
and copy/move assignment operators to be conditionally
(points c, d, e).
From a certain point of view,
is extremely similar to
: it composes move constructions and a (generalized) assignment.
It’s hard to see why the two functions should be therefore treated in different
ways.
We are therefore asking for a very reasonable exception to [P1656]'s rules
in order to make
conditionally noexcept.
2.3. Is this a defect fix?
We believe that the change we are proposing should be treated as a defect fix.
For this reason we are not proposing a bump in
's feature-testing
macro.
A prior art in this sense is [LWG2762], whose resolution is adding a
conditional
specification to
, again in
spite of [P1656] strict rules, because of its "obvious" implementation. (Note
that the time of this writing, [LWG2762] status is still Tentatively Ready.)
3. Impact On The Standard
This is a pure change for
.
4. Implementation experience
The proposed change has been already implemented and shipped by [MS-STL], and experimentally implemented in this libstdc++ branch on GitHub.
5. Technical Specifications
All the proposed changes are relative to [N4892].
6. Proposed wording
Modify [utility.syn] as shown:
template < class T , class U = T > constexpr T exchange ( T & obj , U && new_val ) noexcept ( see below ) ;
Modify [utility.exchange] as shown:
template < class T , class U = T > constexpr T exchange ( T & obj , U && new_val ) noexcept ( see below ) ; Effects: Equivalent to:
Remarks: The exception specification is equivalent to:T old_val = std :: move ( obj ); obj = std :: forward < U > ( new_val ); return old_val ; is_nothrow_move_constructible_v < T > && is_nothrow_assignable_v < T & , U >
7. Acknowledgements
Thanks to KDAB for supporting this work.
Thanks to Jonathan Wakely for the discussions on the LEWG reflector.
All remaining errors are ours and ours only.