Addressed NB comments: DE 15, DE 16, US 95, US 96, US 97
Addressed issues: LWG 801, LWG 1314, LWG 1326
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.
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.
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.
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.
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.
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); }; }
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.
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() { }
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.
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.
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.
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.
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.
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.
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 true
T1 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 valid
T1 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.
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.
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.
namespace std { template <class... Types> class tuple { public: // 20.4.2.1, tuple construction [..] tuple(const tuple&) = default; tuple(tuple&&) = default; [..] }; }
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.
constexpr tuple();
- [A]
- 2 Requires: is_default_constructible<Ti>::value == true for all i
Each type in Types shall be default constructible.- [B]
- 2 Requires: is_constructible<Ti>::value == true for all i
Each type in Types shall be default constructible.3 Effects: Value initializes each element.
explicit tuple(const Types&...);
- [A]
- 4 Requires: is_copy_constructible<Ti>::value == true for all i
Each type in Types shall be copy constructible.- [B]
- 4 Requires: is_constructible<Ti, const Ti&>::value == true for all i
Each type in Types shall be copy constructible.5 Effects:
Copy iInitializes each element with the value of the corresponding parameter.
template <class... UTypes> explicit tuple(UTypes&&... u);6 Requires: is_constructible<Ti, Ui&&>::value == true for all i
Each 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.
tuple(const tuple& u) = default;
- [A]
- 8 Requires: is_copy_constructible<Ti>::value == true for all i
Each type in Types shall satisfy the requirements of CopyConstructible(Table 35).- [B]
- 8 Requires: is_constructible<Ti, const Ti&>::value == true for all i
Each type in Types shall satisfy the requirements of CopyConstructible(Table 35).9 Effects: Initializes
Copy constructseach element of *this with the corresponding element of u.
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 ith
Move-constructs eachelement of *this withthe corresponding element ofstd::forward<Ti>(get<i>(u)).
template <class... UTypes> tuple(const tuple<UTypes...>& u);12 Requires: is_constructible<Ti, const Ui&>::value == true for all i
Each 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 ]
template <class... UTypes> tuple(tuple<UTypes...>&& u);15 Requires: is_constructible<Ti, Ui&&>::value == true for all i
Each 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 ith
Move-constructs eachelement of *this withthe 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 ]
template <class U1, class U2> tuple(const pair<U1, U2>& u);17 Requires: is_constructible<T0, const U1&>::value == true for t
The first type T0 in Typesshall be constructible from U1and is_constructible<T1, const U2&>::value == true for the second type T1 in Typesshall 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.
template <class U1, class U2> tuple(pair<U1, U2>&& u);19 Requires: is_constructible<T0, U1&&>::value == true for t
The first type T0 in Typesshall shall satisfy the requirements of MoveConstructible(Table 34) from U1and is_constructible<T1, U2&&>::value == true for the second type T1 in Typesshall be move-constructible from U2. sizeof...(Types) == 2.20 Effects: Initializes
Constructsthe 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.
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.
tuple& operator=(const tuple& u);
- [A]
- 2 Requires: is_copy_assignable<Ti>::value == true for all i
Each 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.
tuple& operator=(tuple&& u);
- [A]
- 5 Requires: is_move_assignable<Ti>::value == true for all i
Each 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 valid
Each 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.
template <class... UTypes> tuple& operator=(const tuple<UTypes...>& u);
- [A]
- 8 Requires: is_assignable<Ti&, const Ui&>::value == true for all i
Each 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 valid
Each 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.
template <class... UTypes> tuple& operator=(tuple<UTypes...>&& u);
- [A]
- 11 Requires: is_assignable<Ti&, Ui&&>::value == true for all i
Each 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 valid
Each 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.
template <class U1, class U2> tuple& operator=(const pair<U1, U2>& u);
- [A]
- 14 Requires: is_assignable<T0&, const U1&>::value == true for t
The first type T0 in Typesshall shall satisfy the requirements of MoveAssignable (Table 36) from U1and is_assignable<T1&, const U2&>::value == true for the second type T1 in Typesshall 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 valid
The 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 ]
template <class U1, class U2> tuple& operator=(pair<U1, U2>&& u);
- [A]
- 18 Requires: is_assignable<T0&, U1&&>::value == true for t
The first type T0 in Typesshall be Assignable from U1and is_assignable<T1&, U2&&>::value == true for the second type T1 in Typesshall 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 valid
The 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.
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.
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 j
All 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 j
All 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 initializing
copy constructingits first sizeof...(TTypes) elements from the corresponding elements of t and initializingcopy constructingits 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 j
All 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 j
All 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 j
move 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 j
All 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 j
All 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 j
copy 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 j
All 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 j
All 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 j
move 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.
I would like to thank Howard Hinnant for his very helpful discussions and comments during reviews of this paper.