2749. swappable traits for variants

Section: 23.7.3.6 [variant.swap], 23.7.10 [variant.specalg] Status: C++17 Submitter: Agustín K-ballo Bergé Opened: 2016-07-19 Last modified: 2017-07-30

Priority: 1

View all issues with C++17 status.

Discussion:

variant does not play nice with swappable traits, the non-member specialized swap overload is not SFINAE friendly. On the other hand, the member swap is SFINAE friendly, albeit with an incomplete condition, when arguably it shouldn't be. Given the Effects, Throws, and Remarks clauses, the SFINAE condition should include is_move_constructible_v and is_move_assignable_v to account for the involvement of variant's move constructor and move assignment operator (the noexcept specification is correct as is, since the move assignment operator would only be called for variants with different alternatives). This SFINAE condition should apply to the non-member swap overload, while the member swap should require all alternatives are swappable (as defined by 20.5.3.2 [swappable.requirements]).

[2016-07 Chicago]

Monday: P1 - review later in the week

Fri PM: Move to Tentatively Ready

Previous resolution [SUPERSEDED]:

This wording is relative to N4606.

  1. Modify 23.7.3.6 [variant.swap] as indicated:

    void swap(variant& rhs) noexcept(see below);
    

    -?- Requires: Lvalues of type Ti shall be swappable and is_move_constructible_v<Ti> && is_move_assignable_v<Ti> is true for all i.

    […]

    -3- Remarks: This function shall not participate in overload resolution unless is_swappable_v<Ti> is true for all i. If an exception is thrown during the call to function swap(get<i>(*this), get<i>(rhs)), the states of the contained values of *this and of rhs are determined by the exception safety guarantee of swap for lvalues of Ti with i being index(). If an exception is thrown during the exchange of the values of *this and rhs, the states of the values of *this and of rhs are determined by the exception safety guarantee of variant's move constructor and move assignment operator. The expression inside noexcept is equivalent to the logical AND of is_nothrow_move_constructible_v<Ti> && is_nothrow_swappable_v<Ti> for all i.

  2. Modify 23.7.10 [variant.specalg] as indicated:

    template <class... Types> void swap(variant<Types...>& v, variant<Types...>& w) noexcept(see below);
    

    -1- Effects: Equivalent to v.swap(w).

    -2- Remarks: This function shall not participate in overload resolution unless is_move_constructible_v<Ti> && is_move_assignable_v<Ti> && is_swappable_v<Ti> is true for all i. The expression inside noexcept is equivalent to noexcept(v.swap(w)).

[2016-08-13, Reopened by Casey Carter]

It is possible to exchange the value of two variants using only move construction on the alternative types, as if by

auto tmp = move(x);
x.emplace<i>(move(y));
y.emplace<j>(move(tmp));
where i is y.index() and j is tmp.index(). Consequently, variant's member swap need not require move assignable alternatives.

[2016-09-09 Issues Resolution Telecon]

Move to Tentatively Ready

Proposed resolution:

This wording is relative to N4606.

  1. Modify 23.7.3.6 [variant.swap] as indicated:

    void swap(variant& rhs) noexcept(see below);
    

    -?- Requires: Lvalues of type Ti shall be swappable and is_move_constructible_v<Ti> shall be true for all i.

    […]

    -2- Throws: If index() == rhs.index(), aAny exception thrown by swap(get<i>(*this), get<i>(rhs)) with i being index() and variant's move constructor and assignment operator. Otherwise, any exception thrown by the move constructor of Ti or Tj with i being index() and j being rhs.index().

    -3- Remarks: This function shall not participate in overload resolution unless is_swappable_v<Ti> is true for all i. If an exception is thrown during the call to function swap(get<i>(*this), get<i>(rhs)), the states of the contained values of *this and of rhs are determined by the exception safety guarantee of swap for lvalues of Ti with i being index(). If an exception is thrown during the exchange of the values of *this and rhs, the states of the values of *this and of rhs are determined by the exception safety guarantee of variant's move constructor and move assignment operator. The expression inside noexcept is equivalent to the logical AND of is_nothrow_move_constructible_v<Ti> && is_nothrow_swappable_v<Ti> for all i.

  2. Modify 23.7.10 [variant.specalg] as indicated:

    template <class... Types> void swap(variant<Types...>& v, variant<Types...>& w) noexcept(see below);
    

    -1- Effects: Equivalent to v.swap(w).

    -2- Remarks: This function shall not participate in overload resolution unless is_move_constructible_v<Ti> && is_swappable_v<Ti> is true for all i. The expression inside noexcept is equivalent to noexcept(v.swap(w)).