Document number: N3140=10-0130
Date: 2010-10-02
Author: Daniel Krügler
Project: Programming Language C++, Library Working Group
Reply-to: Daniel Krügler

Cleanup of pair and tuple

Addressed NB comments: DE 15, DE 16, US 95, US 96, US 97

Addressed issues: LWG 801, LWG 1314, LWG 1326

Introduction

Several changes during the development of the C++0x Standard left their marks on the very fundamental components pair and tuple. Originally, pair was only specified for non-reference types and with the addition of move-semantics, the suggested changes in terms of std::move where appropriate. Adding the new allocators model to the library as well as updates of the core rules in regard to reference conversions and in regard to special member functions left the types pair and tuple in a state that needs to be cleaned-up to make the specifications consistent.

Discussion

The proposed fixes in this document can be devided into different categories:

During the Rapperwil meeting several examples came up where the explicit declaration of a move special member did suppress the defaulted copy special member (and vice versa). This proposal suggests to explicitly add both defaulted versions where possible to pair and tuple which has the least sensitivity to core rule changes. The specification gives freedom to implementations to omit both declarations equally, because that would not violate the required semantics.

Move/CopyConstructible in the presence of reference members

The current specification of several member functions of pair and tuple use requirement sets that are designed for object types, not for reference types. This becomes obvious by recognizing that a MoveConstructible type (Table 34) can be initialized with an rvalue. This would mean that e.g. an lvalue-reference to non-const violates this requirement but we don't have any constructor that requires less! Both heterogeneous container-like types were intended to contain any form of reference, so we need to ensure that they can hold them. A compiler-generated copy and move constructor of a class type with reference members "copies" lvalue-references by just rebinding them similar to a pointer - this should be done by pair and tuple as well!

There is another disadvantage to applying semantics-loaden requirements like MoveConstructible and CopyConstructible: Both pair and tuple are only thin wrappers of the contained members and there are no member functions that would depend on special invariants of the container as a whole, thus requiring more than necessary for such fundamental types should be prevented, if possible.

Given these boundary conditions it turns out that the constraint specified by

is_constructible<T, T&&>

is necessary and sufficient as a requirement for a move-constructor or move-like-constructor (a template that accepts potentially mutable rvalue arguments) of such a container for every type T including lvalue-references and rvalue-references of object types or function types and is identical in semantics compared to a compiler-generated move-constructor. The consequence is, that using

std::forward<T>(t)

to transfer the direct function argument t (or a corresponding member of a tuple-like rvalue) to the corresponding member for initialization purposes is the correct way to realize the wanted effect: Any object type will behave as if std::move had been used instead, any lvalue-reference or rvalue-reference will just be initialized with the source of appropriate lvalueness.

What about the copy-constructor? For both lvalue-references and object types the intuitive semantics of a compiler-generated copy-constructor is a direct initialization of the corresponding member. This can easily be expressed by

is_constructible<T, const T&>

keeping in mind that for reference types reference-collapsing and "cv-folding" ensures the right semantics in the presence or absence of cv-qualifiers of the referenced type. Neither this requirement nor the effects of direct initialization will work for rvalue-references, because the corresponding named argument is an lvalue that does not bind to the rvalue reference. What should be done here? The core-language is still in the process of evolving but there is currently a tendency to consider that an rvalue-reference as a class member deletes the copy-constructor. I strongly recommend this approach for the library, because of the following simple reason:

Any rvalue-reference is essentially pointing to an identity that has given up ownership of its previous source; saying that we "copy" just the reference implies that the source of this initialization would remain unchanged and could produce an arbitrary number of such copies - but that would be a real trap for the programmer, because every target of such a copy will legally assume that it is the sole destination of this move! As an example consider the following situation where we have a string-like container type that takes ownership of resources:

class MyString { // A class similar to std::string  
  /*..*/
};

and where we use a transient std::tuple<MyString&&> - e.g. as the result of calling std::forward_as_tuple - to construct a pair of strings like this:

void create_and_do(std::tuple<MyString&&> s) {
  std::pair<MyString, MyString> p(std::piecewise_construct, s, s);
  /*..*/
}

It would be a clear service to the programmer if this program would not compile, because we are silently moving an object of type MyString twice!
Being required to write instead:

void create_and_do(std::tuple<MyString&&> s) {
  std::pair<MyString, MyString> p(std::piecewise_construct, std::move(s), std::move(s)); // Oops?!
  /*..*/
}

strongly reduces the risk of such an error, because it becomes immediately visually clear what's happening here.

Move/CopyAssignable in the presence of reference members

Additional to generalized forms of constructions, both tuple-like containers provide assignment operations, both moving and copying. The question is, what the correct requirements and semantics of such members should be in the potential presence of references as members. A first observation is, that both move-assignment and copy-assignment can be supported by both rvalue-references and lvalue-references. This is different to the initialization situation because now we are not binding a reference to a matching value, but instead we are just performing an assignment operation of the referenced objects (ignoring functions here for a moment, which cannot be assigned at all). The most easiest question to answer is what a copy-assignment(-like) operator should do, if the operands are references: Since both target and destination are named variables, they are lvalues and thus this is normal copy-assignment. This is no surprising rule - not even for rvalue-reference members (considered as lvalues because they are named), which can also be assigned as in the following example:

void assign(int&& lhs, int&& rhs) {
  lhs = rhs;
}

Unfortunately we have no trait that properly describes this simple assigment situation. The nearest one would be the has_copy_assign trait, but it returns false for reference types. We could use other traits to remove the references from the member types, but actually the situation can be very simple described by an (unevaluated) expression of the form

std::declval<T&>() = std::declval<const U&>()

which is correct in the presence of references as well. Alternatively we could use the existing requirement set CopyAssignable, but this set imposes additional semantic requirements that are not necessary for such thin type wrappers. Neither pair's nor tuple's invariants of this operation make it necessary that after the assignment the source remains unchanged - this is very different to normal containers, which are broken without this guarantee. It is also not necessary that this direct assignment guarantees to be well-formed with an rvalue or const lvalue as the right operand (again: if the members are reference types), because this assignment will only be performed with an lvalue to the left and to the right.

It would be very helpful, if the library would have a trait - equivalent to is_constructible - that just returns whether the assignment expression is well-formed. A proposal exists that refactors the current assignment-related traits, but in the absence of such component the most simple requirement is that the above expression - alternatively expressed via the tuple API as

std::get<i>(lhs-tuple-like) = std::get<i>(rhs-tuple-like)

is well-formed for all i in the range [0, std::tuple_size<tuple-like>::value).

The less easier question to answer is what the correct requirements and semantics should be for the move-assignment operator of tuple-like classes with members of reference type. For non-reference members the most intuitive behaviour is to give these members the opportunity for a move operation, this would naturally reflect the owner-ship relation between members that are values and the container type. A first start could be to specify that the required effects are such that the expression

std::get<i>(lhs-tuple-like) = std::move(std::get<i>(rhs-tuple-like))

shall be valid for all i. It is uncontroversial for non-reference types and I assert that it is also uncontroversial for rvalue-reference types, because the tuple-like that is the right operand is an rvalue that is in the way of giving up it's ownership for these resources. But to the author's opinion it's not quite so clear whether a similar move semantics as appropriate for lvalue-reference members as well. The reason is, because such lvalue-references refer to objects (I ignore here functions again) that potentially have a complete different life-cycle. This becomes much clearer when by an example: Assume we have a class type C that allows to read or to modify some of its properties via a single tuple:

class A {
  std::string value;
public:
  explicit A(const std::string& value) : value(value) {}

  // Normal copy/move semantics:
  A(const A&) = default;
  A(A&&) = default;
  A& operator=(const A&) = default;
  A& operator=(A&&) = default;

  // IO support:
  friend std::ostream& operator<<(std::ostream& os, const A& a) {
    return os << a.value;
  }
};

class C {
  A a;
  int i;
public:
  C(const A& a, int i) : a(a), i(i) {}
  
  std::tuple<const A&, int> data() const { return std::tie(a, i); }
  std::tuple<A&, int&> data() { return std::tie(a, i); }
};

Note that we use a very regular pattern here: Instead of providing n individual properties via 2n member functions we just provide a single pair of functions each to n properties at once. We can e.g. use this to modify all the properties of an object c of type C at once:

c.data() = std::make_tuple(A("Hi!"), 6);

Now lets do something with a C object:

int main() {
  C c(A("What-an-homunculus"), 4);
  A a("");
  int i = 0;
  std::tie(a, i) = c.data(); // #1
  std::cout << "{" << a << ',' << i << "}" << std::endl;
  auto t = c.data(); // #2
  std::cout << "{" << std::get<0>(t) << ',' << std::get<1>(t) << "}" << std::endl;
}

Assuming that tuple's move-assignment operator would invoke std::move also for lvalue-reference members, the program output could be:

  {What-an-homunculus,4}
  {,4}

What has happened here? The problem is that in the line marked with #1 the move-assignment operator of tuple was invoked, which again has - under the assumption that we would apply std::move to all members - invoked the move-assignment operator of A, which again has invoked the move-assignment operator of std::string (and I assume here that the implementation decided to clear the memory of the rvalue source string. The same result would occur for implementations that swap their internals). We can observe this silent change to c by reading the properties again in the line marked with #2. This is in my opinion a strong argument for not applying std::move to lvalue-references here. Another way of looking at the same problem is to recognize that an rvalue of type std::tuple<T&> is actually still an lvalue wrapper and the internals behave like a normal lvalue. The most appropriate behavior is in fact, that we call std::forward<T> for any member of type T.

It should be highlighted that this suggestion is actually neither unusual or without any example: In fact this would be exactly the same semantics as the move-assignment operator of std::unique<T, D>, when D is a deleter of lvalue-reference type as specified in 20.9.10.2.3 p.1+5.

Thus, the suggestion of this proposal is to specify that the effects of a move-assignment(-like) operator of pair or tuple should require that the unevaluated expression

std::declval<T&>() = std::declval<U&&>()

or the corresponding expression

std::get<i>(lhs-tuple-like) = std::forward<Ui>(std::get<i>(rhs-tuple-like))

shall be valid for all i.

Proposed resolution

The proposed wording changes refer to N3126.

At some locations this proposal would take advantage of a adjustment proposal in regard to the construction and assignment traits. In particular, the following ones:

is_default_constructible<T> ≡ is_constructible<T>

is_copy_constructible<T> ≡ is_constructible<T, const T&>

is_move_constructible<T> ≡ is_constructible<T, T&&>

as well as this one:

template<class T, class U>
struct is_assignable;

which shall be a BinaryTypeTrait that evaluates to true if and only if the expression

std::declval<T>() = std::declval<U>()

is well-formed, and its derived forms:

is_copy_assignable<T> ≡ is_assignable<T&, const T&>

is_move_assignable<T> ≡ is_assignable<T&, T&&>

At the places where either of these traits would perfectly match, the proposed resolution is split into parts [A] and [B], where [A] assumes the acceptance of the above mentioned proposal, and [B] the proposal in absence of this trait. This should ease the process of possibly merging them.

  1. Change 20.3.5.2 [pairs.pair], class template pair synopsis, as indicated. The intent is to make copy and move constructor trivial, if the types T1 and T2 are themselves trivial copyable/movable. Also, without any explicit declaration of the move constructor, its generation will be supressed, because of the explicit declaration of the copy constructor. Finally, the copy/move assignment operators are not defaulted, they must be implemented to allow for assignment semantics via references, as required by the specification and intended by the originally proposing papers:

    namespace std {
      template <class T1, class T2>
      struct pair {
        typedef T1 first_type;
        typedef T2 second_type;
    
        T1 first;
        T2 second;
        pair(const pair&) = default;
        pair(pair&&) = default;
        constexpr pair();
        pair(const T1& x, const T2& y);
        template<class U, class V> pair(U&& x, V&& y);
        template<class U, class V> pair(const pair<U, V>& p);
        template<class U, class V> pair(pair<U, V>&& p);
        template <class... Args1, class... Args2>
        pair(piecewise_construct_t,
          tuple<Args1...> first_args, tuple<Args2...> second_args);
    
        pair& operator=(const pair& p);
        template<class U, class V> pair& operator=(const pair<U, V>& p);
        pair& operator=(pair&& p);
        template<class U, class V> pair& operator=(pair<U, V>&& p);
    
        void swap(pair& p);
      };
    }
    

  2. Insert a new paragraph at the beginning of 20.3.5.2 [pairs.pair] that adds some exception-behaviour clarification that is missing for pair (but given for tuple):

    No constructor or member function of pair throws an exception unless one of the element-wise operations that are specified to be called during this operation throws an exception.

  3. Change 20.3.5.2 [pairs.pair]/1 as indicated. The intent is to add the missing requirements and to describe the initialization context more schematic [Intentionally the DefaultConstructible requirements are not imposed here: Type pair does not depend on its semantic constraints]:

    constexpr pair();
    

    [A]
    Requires: is_default_constructible<first_type>::value is true and is_default_constructible<second_type>::value is true.
    [B]
    Requires: is_constructible<first_type>::value is true and is_constructible<second_type>::value is true.

    1 Effects: Value-initializes first and second.Initializes its members as if implemented: pair() : first(), second() { }

  4. Change 20.3.5.2 [pairs.pair]/2 as indicated. The intent is to add the missing requirements:

    pair(const T1& x, const T2& y);
    

    [A]
    Requires: is_copy_constructible<first_type>::value is true and is_copy_constructible<second_type>::value is true.
    [B]
    Requires: is_constructible<first_type, const first_type&>::value is true and is_constructible<second_type, const second_type&>::value is true.

    2 Effects: The constructor initializes first with x and second with y.

  5. Change 20.3.5.2 [pairs.pair]/3 as indicated. The intent is to add the missing requirements:

    template<class U, class V> pair(U&& x, V&& y);
    

    Requires: is_constructible<first_type, U&&>::value is true and is_constructible<second_type, V&&>::value is true.

    3 Effects: The constructor initializes first with std::forward<U>(x) and second with std::forward<V>(y).

    4 Remarks: If U is not implicitly convertible to first_type or V is not implicitly convertible to second_type this constructor shall not participate in overload resolution.

  6. Change 20.3.5.2 [pairs.pair]/5 as indicated. The intent is to add the missing requirements (The change in the effects element should be non-normatively) and to add constraints to prevent silent explicit conversions implicitly:

    template<class U, class V> pair(const pair<U, V>& p);
    

    Requires: is_constructible<first_type, const U&>::value is true and is_constructible<second_type, const V&>::value is true.

    5 Effects: Initializes members from the corresponding members of the argument, performing implicit conversions as needed.

    Remarks: This constructor shall not participate in overload resolution unless const U& is implicitly convertible to first_type and const V& is implicitly convertible to second_type.

  7. Change 20.3.5.2 [pairs.pair]/6 as indicated. The intent is to add the missing requirements, to fix incorrect semantics in terms of std::move, and to add constraints to prevent silent explicit conversions implicitly:

    template<class U, class V> pair(pair<U, V>&& p);
    

    Requires: is_constructible<first_type, U&&>::value is true and is_constructible<second_type, V&&>::value is true.

    6 Effects: The constructor initializes first with std::moveforward<U>(p.first) and second with std::moveforward<V>(p.second).

    Remarks: This constructor shall not participate in overload resolution unless U is implicitly convertible to first_type and V is implicitly convertible to second_type.

  8. Change 20.3.5.2 [pairs.pair]/7 as indicated. The intent is to fix the current requirements:

    template<class... Args1, class... Args2>
      pair(piecewise_construct_t,
        tuple<Args1...> first_args, tuple<Args2...> second_args);
    

    7 Requires: is_constructible<first_type, Args1&&...>::value is true and is_constructible<second_type, Args2&&...>::value is true.All the types in Args1 and Args2 shall be CopyConstructible (Table 35). T1 shall be constructible from Args1. T2 shall be constructible from Args2.

    8 Effects: The constructor initializes first with arguments of types Args1... obtained by forwarding the elements of first_args and initializes second with arguments of types Args2... obtained by forwarding the elements of second_args. (Here, forwarding an element x of type U within a tuple object means calling std::forward<U>(x).) This form of construction, whereby constructor arguments for first and second are each provided in a separate tuple object, is called piecewise construction.

  9. Add a new paragraph after 20.3.5.2 [pairs.pair]/8 as indicated. The intent is to add the prototype specification of the copy-assignment operator, which is intentionally different from the built-in one for reference types. The wording also intentionally does not specify its requirements in terms of the semantics-loaden CopyAssignable requirements both for consistency with the remaining member function requirements and because the "weak" container pair does not depend on specific class-invariants that need to be conserved.

    pair& operator=(const pair& p);
    

    [A]
    Requires: is_copy_assignable<first_type>::value is true and is_copy_assignable<second_type>::value is true.
    [B]
    Requires: The expressions first = p.first and second = p.second shall be valid.

    Effects: Assigns p.first to first and p.second to second.

    Returns: *this.

  10. Change 20.3.5.2 [pairs.pair]/9 as indicated. The intent is to fix the usage of "CopyAssignable from U", because the library does not define such a mixed requirement. Instead wording is used in terms of well-formed expressions useful for heterogeneous assignments.

    template<class U, class V> pair& operator=(const pair<U, V>& p);
    

    [A]
    9 Requires: is_assignable<first_type&, const U&>::value is true and is_assignable<second_type&, const V&>::value is trueT1 shall satisfy the requirements of CopyAssignable from U. T2 shall satisfy the requirements of CopyAssignable from V.
    [B]
    9 Requires: The expressions first = p.first and second = p.second shall be validT1 shall satisfy the requirements of CopyAssignable from U. T2 shall satisfy the requirements of CopyAssignable from V.

    10 Effects: Assigns p.first to first and p.second to second.

    11 Returns: *this.

  11. Change 20.3.5.2 [pairs.pair]/12 as indicated. The intent is to add the missing requirements and to fix the current semantics in the presence of lvalue-references:

    pair& operator=(pair&& p);
    

    [A]
    Requires: is_move_assignable<first_type>::value is true and is_move_assignable<second_type>::value is true.
    [B]
    Requires: The expressions first = std::forward<first_type>(p.first) and second = std::forward<second_type>(p.second) shall be valid.

    12 Effects: Assigns to first with std::moveforward<first_type>(p.first) and to second with std::moveforward<second_type>(p.second).

    13 Returns: *this.

  12. Change 20.3.5.2 [pairs.pair]/14 as indicated. The intent is to add the missing requirements and to fix the current semantics in the presence of lvalue-references:

    template<class U, class V> pair& operator=(pair<U, V>&& p);
    

    [A]
    Requires: is_assignable<first_type&, U&&>::value is true and is_assignable<second_type&, V&&>::value is true.
    [B]
    Requires: The expressions first = std::forward<U>(p.first) and second = std::forward<V>(p.second) shall be valid.

    14 Effects: Assigns to first with std::moveforward<U>(p.first) and to second with std::moveforward<V>(p.second).

    15 Returns: *this.

  13. Change 20.4.2 [tuple.tuple], class template tuple synposis as indicated. The intent is to support a trivial move constructor, if the types T1 and T2 are themselves trivially movable.

    namespace std {
      template <class... Types>
      class tuple {
      public:
        // 20.4.2.1, tuple construction
        [..]
        
        tuple(const tuple&) = default;
        tuple(tuple&&) = default;
        
        [..]
      };
    }
    

  14. Change [tuple.cnstr]/1 as indicated. The intent is to introduce a common nomenclature to prevent unnecessary repetitions in the following text:

    1 For each tuple constructor, an exception is thrown only if the construction of one of the types in Types throws an exception. In the constructor descriptions that follow, let i be in the range [0, sizeof...(Types)) in order, Ti be the ith type in Types, and Ui be the ith type in a template parameter pack named UTypes, where indexing is zero-based.

  15. Change [tuple.cnstr]/2 as indicated. The intent is to use the proper requirements [Intentionally the DefaultConstructible requirements are not imposed here: Type tuple does not depend on its semantic constraints]:

    constexpr tuple();
    

    [A]
    2 Requires: is_default_constructible<Ti>::value == true for all iEach type in Types shall be default constructible.
    [B]
    2 Requires: is_constructible<Ti>::value == true for all iEach type in Types shall be default constructible.

    3 Effects: Value initializes each element.

  16. Change 20.4.2.1 [tuple.cnstr]/4+5 as indicated. The intent is to fix the current requirements and the semantics (which can be interpreted to require a copy-initialization):

    explicit tuple(const Types&...);
    

    [A]
    4 Requires: is_copy_constructible<Ti>::value == true for all iEach type in Types shall be copy constructible.
    [B]
    4 Requires: is_constructible<Ti, const Ti&>::value == true for all iEach type in Types shall be copy constructible.

    5 Effects: Copy iInitializes each element with the value of the corresponding parameter.

  17. Change 20.4.2.1 [tuple.cnstr]/6 as indicated. The intent is to fix the current requirements and to constrain this constructor, which is extremely greedy:

    template <class... UTypes>
      explicit tuple(UTypes&&... u);
    

    6 Requires: is_constructible<Ti, Ui&&>::value == true for all iEach type in Types shall satisfy the requirements of MoveConstructible (Table 34) from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).

    7 Effects: Initializes the elements in the tuple with the corresponding value in std::forward<UTypes>(u).

    Remarks: This constructor shall not participate in overload resolution unless each type in UTypes is implicitly convertible to its corresponding type in Types.

  18. Change 20.4.2.1 [tuple.cnstr]/8+9 as indicated. The intent is to fix the current requirements (This proposal intentionally recommends to keep the additional specification of this defaulted constructor for tuple but not for pair: This requirements are given to ensure that implementors of tuple need to satisfy some minimum criteria).

    tuple(const tuple& u) = default;
    

    [A]
    8 Requires: is_copy_constructible<Ti>::value == true for all iEach type in Types shall satisfy the requirements of CopyConstructible(Table 35).
    [B]
    8 Requires: is_constructible<Ti, const Ti&>::value == true for all iEach type in Types shall satisfy the requirements of CopyConstructible(Table 35).

    9 Effects: InitializesCopy constructs each element of *this with the corresponding element of u.

  19. Change 20.4.2.1 [tuple.cnstr]/10+11 as indicated. The intent is to fix the current requirements (This proposal intentionally recommends to keep the additional specification of this defaulted constructor for tuple but not for pair: The requirements are given to ensure that implementors of tuple need to satisfy some minimum criteria).

    tuple(tuple&& u) = default;
    

    [A]
    10 Requires: is_move_constructible<Ti>::value == true for all i.Each type in Types shall shall satisfy the requirements of MoveConstructible (Table 34).
    [B]
    10 Requires: is_constructible<Ti, Ti>::value == true for all i.Each type in Types shall shall satisfy the requirements of MoveConstructible (Table 34).

    11 Effects: For all i, initializes the ithMove-constructs each element of *this with the corresponding element ofstd::forward<Ti>(get<i>(u)).

  20. Change 20.4.2.1 [tuple.cnstr]/12-14 as indicated. The intent is to harmonize the current requirements with the other member functions and to constrain this member function accordingly.

    template <class... UTypes> tuple(const tuple<UTypes...>& u);
    

    12 Requires: is_constructible<Ti, const Ui&>::value == true for all iEach type in Types shall be constructible from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).

    13 Effects: Constructs each element of *this with the corresponding element of u.

    14 Remarks: This constructor shall not participate in overload resolution unless const Ui& is implicitly convertible to Ti for all i.[ Note: enable_if can be used to make the converting constructor and assignment operator exist only in the cases where the source and target have the same number of elements. — end note ]

  21. Change 20.4.2.1 [tuple.cnstr]/15+16 as indicated. The intent is to fix the current requirements and to constrain this member function accordingly:

    template <class... UTypes> tuple(tuple<UTypes...>&& u);
    

    15 Requires: is_constructible<Ti, Ui&&>::value == true for all iEach type in Types shall shall satisfy the requirements of MoveConstructible (Table 34) from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).

    16 Effects: For all i, initializes the ithMove-constructs each element of *this with the corresponding element ofstd::forward<Ui>(get<i>(u)).

    Remarks: This constructor shall not participate in overload resolution unless each type in UTypes is implicitly convertible to its corresponding type in Types.[ Note: enable_if can be used to make the converting constructor and assignment operator exist only in the cases where the source and target have the same number of elements. — end note ]

  22. Change 20.4.2.1 [tuple.cnstr]/17+18 as indicated. The intent is to harmonize the current requirements with the remaining member functions and to add constraints to prevent silent explicit conversions implicitly:

    template <class U1, class U2> tuple(const pair<U1, U2>& u);
    

    17 Requires: is_constructible<T0, const U1&>::value == true for tThe first type T0 in Types shall be constructible from U1 and is_constructible<T1, const U2&>::value == true for the second type T1 in Types shall be constructible from U2. sizeof...(Types) == 2.

    18 Effects: Constructs the first element with u.first and the second element with u.second.

    Remarks: This constructor shall not participate in overload resolution unless const U1& is implicitly convertible to T0 and const U2& is implicitly convertible to T1.

  23. Change 20.4.2.1 [tuple.cnstr]/19+20 as indicated. The intent is to fix the current requirements and to add constraints to prevent silent explicit conversions implicitly:

    template <class U1, class U2> tuple(pair<U1, U2>&& u);
    

    19 Requires: is_constructible<T0, U1&&>::value == true for tThe first type T0 in Types shall shall satisfy the requirements of MoveConstructible(Table 34) from U1 and is_constructible<T1, U2&&>::value == true for the second type T1 in Types shall be move-constructible from U2. sizeof...(Types) == 2.

    20 Effects: InitializesConstructs the first element with std::moveforward<U1>(u.first) and the second element with std::moveforward<U2>(u.second).

    Remarks: This constructor shall not participate in overload resolution unless U1 is implicitly convertible to T0 and U2 is implicitly convertible to T1.

  24. Change 20.4.2.2 [tuple.assign]/1 as indicated. The intent is to introduce a common nomenclature to prevent unnecessary repetitions in the following text:

    1 For each tuple assignment operator, an exception is thrown only if the assignment of one of the types in Types throws an exception. In the function descriptions that follow, let i be in the range [0, sizeof...(Types)) in order, Ti be the ith type in Types, and Ui be the ith type in a template parameter pack named UTypes, where indexing is zero-based.

  25. Change 20.4.2.2 [tuple.assign]/2 as indicated. The intent is to harmonize the current requirements with the remaining member functions, there is no reason why tuple should depend on additional semantic constraints imposed on by the CopyAssignable requirements:

    tuple& operator=(const tuple& u);
    

    [A]
    2 Requires: is_copy_assignable<Ti>::value == true for all iEach type in Types shall be CopyAssignable (Table 37).
    [B]
    2 Requires: For all i, the expression get<i>(*this) = get<i>(u) shall be valid.Each type in Types shall be CopyAssignable (Table 37).

    3 Effects: Assigns each element of u to the corresponding element of *this.

  26. Change 20.4.2.2 [tuple.assign]/5+6 as indicated. The intent is to fix the current requirements, also there is no reason why tuple should depend on additional semantic constraints imposed on by the MoveAssignable requirements. Additional the semantics are fixed for lvalue-reference members:

    tuple& operator=(tuple&& u);
    

    [A]
    5 Requires: is_move_assignable<Ti>::value == true for all iEach type in Types shall shall satisfy the requirements of MoveAssignable (Table 36).
    [B]
    5 Requires: For all i, the expression get<i>(*this) = std::forward<Ti>(get<i>(u)) shall be validEach type in Types shall shall satisfy the requirements of MoveAssignable (Table 36).

    6 Effects: For all i, assigns to get<i>(*this) with std::forward<Ti>(get<i>(u))Move-assigns each element of u to the corresponding element of *this.

  27. Change 20.4.2.2 [tuple.assign]/8 as indicated. The intent is to fix the current requirements (There does not exist a requirement set Assignable and in contrast to other corresponding function we miss to require that both tuple sizes are equal):

    template <class... UTypes>
      tuple& operator=(const tuple<UTypes...>& u);
    

    [A]
    8 Requires: is_assignable<Ti&, const Ui&>::value == true for all iEach type in Types shall be Assignable from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).
    [B]
    8 Requires: For all i, the expression get<i>(*this) = get<i>(u) shall be validEach type in Types shall be Assignable from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).

    9 Effects: Assigns each element of u to the corresponding element of *this.

  28. Change 20.4.2.2 [tuple.assign]/11+12 as indicated. The intent is to fix the current requirements and the unwanted semantics for lvalue-reference members:

    template <class... UTypes>
      tuple& operator=(tuple<UTypes...>&& u);
    

    [A]
    11 Requires: is_assignable<Ti&, Ui&&>::value == true for all iEach type in Types shall satisfy the requirements of MoveAssignable (Table 36) from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).
    [B]
    11 Requires: For all i, the expression get<i>(*this) = std::forward<Ui>(get<i>(u)) shall be validEach type in Types shall satisfy the requirements of MoveAssignable (Table 36) from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).

    12 Effects: For all i, assigns to get<i>(*this) with std::forward<Ui>(get<i>(u))Move-assigns each element of u to the corresponding element of *this.

  29. Change 20.4.2.2 [tuple.assign]/14+17 as indicated. The intent is to fix the current requirements, also we remove the note in p. 17, because it is misplaced here and no longer relevant given the newly added constraints for some too greedy or too implicit constructors.

    template <class U1, class U2> tuple& operator=(const pair<U1, U2>& u);
    

    [A]
    14 Requires: is_assignable<T0&, const U1&>::value == true for tThe first type T0 in Types shall shall satisfy the requirements of MoveAssignable (Table 36) from U1 and is_assignable<T1&, const U2&>::value == true for the second type T1 in Types shall shall satisfy the requirements of MoveAssignable (Table 36) from U2. sizeof...(Types) == 2.
    [B]
    14 Requires: The expressions get<0>(*this) = p.first and get<1>(*this) = p.second shall be validThe first type in Types shall shall satisfy the requirements of MoveAssignable (Table 36) from U1 and the second type in Types shall shall satisfy the requirements of MoveAssignable (Table 36) from U2. sizeof...(Types) == 2.

    15 Effects: Assigns u.first to the first element of *this and u.second to the second element of *this.

    16 Returns: *this

    17 [ Note: There are rare conditions where the converting copy constructor is a better match than the element-wise construction, even though the user might intend differently. An example of this is if one is constructing a one-element tuple where the element type is another tuple type T and if the parameter passed to the constructor is not of type T, but rather a tuple type that is convertible to T. The effect of the converting copy construction is most likely the same as the effect of the element-wise construction would have been. However, is possible to compare the "nesting depths" of the source and target tuples and decide to select the element-wise constructor if the source nesting depth is smaller than the target nesting-depth. This can be accomplished using an enable_if template or other tools for constrained templates. — end note ]

  30. Change 20.4.2.2 [tuple.assign]/18+19 as indicated. The intent is to fix the current requirements and semantics:

    template <class U1, class U2> tuple& operator=(pair<U1, U2>&& u);
    

    [A]
    18 Requires: is_assignable<T0&, U1&&>::value == true for tThe first type T0 in Types shall be Assignable from U1 and is_assignable<T1&, U2&&>::value == true for the second type T1 in Types shall be Assignable from U2. sizeof...(Types) == 2.
    [B]
    18 Requires: The expressions get<0>(*this) = std::forward<U1>(p.first) and get<1>(*this) = std::forward<U2>(p.second) shall be validThe first type in Types shall be Assignable from U1 and the second type in Types shall be Assignable from U2. sizeof...(Types) == 2.

    19 Effects: Assigns std::movestd::forward<U1>(u.first) to the first element of *this and std::movestd::forward<U2>(u.second) to the second element of *this.

  31. Insert a new paragraph at the very beginning to 20.4.2.4 [tuple.creation]. The intent is to introduce a common nomenclature to prevent unnecessary repetitions in the following text:

    In the function descriptions that follow, let i be in the range [0, sizeof...(TTypes)) in order and Ti be the ith type in a template parameter pack named TTypes, let j be in the range [0, sizeof...(UTypes)) in order and Uj be the jth type in a template parameter pack named UTypes, where indexing is zero-based.

  32. Change 20.4.2.4 [tuple.creation]/8-15 as indicated. The intent is to fix the current requirements and semantics:

    template <class... TTypes, class... UTypes>
      tuple<TTypes..., UTypes...> tuple_cat(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
    

    [A]
    8 Requires: is_copy_constructible<Ti>::value == true for all i and is_copy_constructible<Uj>::value == true for all jAll the types in TTypes shall be CopyConstructible (Table 35). All the types in UTypes shall be CopyConstructible (Table 35).
    [B]
    8 Requires: is_constructible<Ti, const Ti&>::value == true for all i and is_constructible<Uj, const Uj&>::value == true for all jAll the types in TTypes shall be CopyConstructible (Table 35). All the types in UTypes shall be CopyConstructible (Table 35).

    9 Returns: A tuple object constructed by initializingcopy constructing its first sizeof...(TTypes) elements from the corresponding elements of t and initializingcopy constructing its last sizeof...(UTypes) elements from the corresponding elements of u.

    template <class... TTypes, class... UTypes>
      tuple<TTypes..., UTypes...> tuple_cat(tuple<TTypes...<&& t, const tuple<UTypes...>& u);
    

    [A]
    10 Requires: is_move_constructible<Ti>::value == true for all i and is_copy_constructible<Uj>::value == true for all jAll the types in TTypes shall be MoveConstructible (Table 34). All the types in UTypes shall be CopyConstructible (Table 35).
    [B]
    10 Requires: is_constructible<Ti, Ti&&>::value == true for all i and is_constructible<Uj, const Uj&>::value == true for all jAll the types in TTypes shall be MoveConstructible (Table 34). All the types in UTypes shall be CopyConstructible (Table 35).

    11 Returns: A tuple object constructed by initializing the ith element with std::forward<Ti>(get<i>(t)) for all i and initializing the (j+sizeof...(TTypes))th element with get<j>(u) for all jmove constructing its first sizeof...(TTypes) elements from the corresponding elements of t and copy constructing its last sizeof...(UTypes) elements from the corresponding elements of u.

    template <class... TTypes, class... UTypes>
      tuple<TTypes..., UTypes...> tuple_cat(const tuple<TTypes...>& t, tuple<UTypes...>&& u);
    

    [A]
    12 Requires: is_copy_constructible<Ti>::value == true for all i and is_move_constructible<Uj>::value == true for all jAll the types in TTypes shall be CopyConstructible (Table 35). All the types in UTypes shall be MoveConstructible (Table 34).
    [B]
    12 Requires: is_constructible<Ti, const Ti&>::value == true for all i and is_constructible<Uj, Uj&&>::value == true for all jAll the types in TTypes shall be CopyConstructible (Table 35). All the types in UTypes shall be MoveConstructible (Table 34).

    13 Returns: A tuple object constructed by initializing the ith element with get<i>(t) for all i and initializing the (j+sizeof...(TTypes))th element with std::forward<Uj>(get<j>(u)) for all jcopy constructing its first sizeof...(TTypes) elements from the corresponding elements of t and move constructing its last sizeof...(UTypes) elements from the corresponding elements of u.

    template <class... TTypes, class... UTypes>
      tuple<TTypes..., UTypes...> tuple_cat(tuple<TTypes...>&& t, tuple<UTypes...>&& u);
    

    [A]
    14 Requires: is_move_constructible<Ti>::value == true for all i and is_move_constructible<Uj>::value == true for all jAll the types in TTypes shall be MoveConstructible (Table 34). All the types in UTypes shall be MoveConstructible (Table 34).
    [B]
    14 Requires: is_constructible<Ti, Ti&&>::value == true for all i and is_constructible<Uj, Uj&&>::value == true for all jAll the types in TTypes shall be MoveConstructible (Table 34). All the types in UTypes shall be MoveConstructible (Table 34).

    15 Returns: A tuple object constructed by initializing the ith element with std::forward<Ti>(get<i>(t) for all i and initializing the (j+sizeof...(TTypes))th element with std::forward<Uj>(get<j>(u)) for all jmove constructing its first sizeof...(TTypes) elements from the corresponding elements of t and move constructing its last sizeof...(UTypes) elements from the corresponding elements of u.

Acknowledgements

I would like to thank Howard Hinnant for his very helpful discussions and comments during reviews of this paper.