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: