swap contractSection: 22.2.2 [utility.swap], 16.4.4.3 [swappable.requirements], 23.2.2 [container.requirements.general] Status: LEWG Submitter: Robert Shearer Opened: 2012-04-13 Last modified: 2020-10-02
Priority: 2
View all other issues in [utility.swap].
View all issues with LEWG status.
Discussion:
Sub-clause 22.2.2 [utility.swap] defines a non-member 'swap' function with defined behavior for
all MoveConstructible and MoveAssignable types. It does not guarantee
constant-time complexity or noexcept in general, however this definition does
render all objects of MoveConstructible and MoveAssignable type swappable
(by the unary definition of sub-clause 16.4.4.3 [swappable.requirements]) in the absence of
specializations or overloads.
swap function defined in Table 96, however,
defines semantics incompatible with the generic non-member swap function,
since it is defined to call a member swap function whose semantics are
undefined for some values of MoveConstructible and MoveAssignable types.
The obvious (perhaps naive) interpretation of sub-clause 16.4.4.3 [swappable.requirements] is as a guide to
the "right" semantics to provide for a non-member swap function (called in
the context defined by 16.4.4.3 [swappable.requirements] p3) in order to provide interoperable
user-defined types for generic programming. The standard container types don't follow these guidelines.
More generally, the design in the standard represents a classic example of "contract narrowing". It
is entirely reasonable for the contract of a particular swap overload to provide more
guarantees, such as constant-time execution and noexcept, than are provided by the swap
that is provided for any MoveConstructible and MoveAssignable types, but it is not
reasonable for such an overload to fail to live up to the guarantees it provides for general types when
it is applied to more specific types. Such an overload or specialization in generic programming is akin
to an override of an inherited virtual function in OO programming: violating a superclass contract in a
subclass may be legal from the point of view of the language, but it is poor design and can easily lead
to errors. While we cannot prevent user code from providing overloads that violate the more general
swap contract, we can avoid doing so within the library itself.
My proposed resolution is to draw a sharp distinction between member swap functions, which provide
optimal performance but idiosyncratic contracts, and non-member swap functions, which should always
fulfill at least the contract of 22.2.2 [utility.swap] and thus render objects swappable. The member
swap for containers with non-propagating allocators, for example, would offer constant-time
guarantees and noexcept but would only offer defined behavior for values with allocators that compare
equal; non-member swap would test allocator equality and then dispatch to either member swap or
std::swap depending on the result, providing defined behavior for all values (and rendering the type
"swappable"), but offering neither the constant-time nor the noexcept guarantees.
[2013-03-15 Issues Teleconference]
Moved to Open.
This topic deserves more attention than can be given in the telecon, and there is no proposed resolution.
[2016-03 Jacksonville]
Alisdair says that his paper P0178 addresses this.
[2016-08 Chicago]
Send to LEWG
[2016-06 Oulu]
P0178 reviewed, and sent back to LEWG for confirmation.
Thursday Morning: A joint LWG/LEWG meeting declined to adopt P0178.
[2020-10-02; remove P0178 as Proposed Resolution]
Proposed resolution: