This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of Resolved status.

### 1326. Missing/wrong preconditions for `pair` and `tuple` functions

Section: 22.3.2 [pairs.pair], 22.4.4.1 [tuple.cnstr] Status: Resolved Submitter: Daniel Krügler Opened: 2010-03-07 Last modified: 2016-01-28

Priority: Not Prioritized

View other active issues in [pairs.pair].

View all other issues in [pairs.pair].

View all issues with Resolved status.

Discussion:

There are several constructors and creation functions of std::tuple that impose requirements on it's arguments, that are unnecessary restrictive and don't match the intention for the supported argument types. This is related to the fact that tuple is supposed to accept both object types and lvalue-references and the usual MoveConstructible and CopyConstructible requirements are bad descriptions for non-const references. Some examples:

1. 22.4.4.1 [tuple.cnstr] before p.4 and p.8, resp.:

   >     explicit tuple(const Types&...);

   > > 4 Requires: Each type in `Types` shall be copy constructible.

   >     tuple(const tuple& u) = default;

   > > 8 Requires: Each type in `Types` shall satisfy the requirements of `CopyConstructible` (Table 34).

   A tuple that contains lvalue-references to non-const can never satisfy the `CopyConstructible` requirements. `CopyConstructible` requirements refine the `MoveConstructible` requirements and this would require that these lvalue-references could bind rvalues. But the core language does not allow that. Even, if we would interpret that requirement as referring to the underlying non-reference type, this requirement would be wrong as well, because there is no reason to disallow a type such as

   >     struct NoMoveNoCopy {
   >       NoMoveNoCopy(NoMoveNoCopy&&) = delete;
   >       NoMoveNoCopy(const NoMoveNoCopy&) = delete;
   >       ...
   >     }:

   for the instantiation of `std::tuple<NoMoveNoCopy&>` and that of it's copy constructor.

   A more reasonable requirement for this example would be to require that "`is_constructible<Ti, const Ti&>::value` shall evaluate to true for all `Ti` in `Types`". In this case the special reference-folding and const-merging rules of references would make this well-formed in all cases. We could also add the further constraint "if `Ti` is an object type, it shall satisfy the `CopyConstructible` requirements", but this additional requirement seems not really to help here. Ignoring it would only mean that if a user would provide a curious object type `C` that satisfies the `std::is_constructible<C, const C&>` test, but not the "`C` is `CopyConstructible`" test would produce a `tuple<C>` that does not satisfy the `CopyConstructible` requirements as well.

2. 22.4.4.1 [tuple.cnstr] before p.6 and p.10, resp.:

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

   > > 6 Requires: Each type in `Types` shall satisfy the requirements of `MoveConstructible` (Table 33) from the corresponding type in `UTypes`. `sizeof...(Types) == sizeof...(UTypes)`.

   >     tuple(tuple&& u);

   > > 10 Requires: Each `type` in `Types` shall shall satisfy the requirements of `MoveConstructible` (Table 33).

   We have a similar problem as in (a): Non-const lvalue-references are intended template arguments for `std::tuple`, but cannot satisfy the `MoveConstructible` requirements. In this case the correct requirements would be

   > `is_constructible<Ti, Ui>::value` shall evaluate to true for all `Ti` in `Types` and for all `Ui` in `UTypes`

   and

   > `is_constructible<Ti, Ti>::value` shall evaluate to true for all `Ti` in `Types`

   respectively.

Many `std::pair` member functions do not add proper requirements, e.g. the default c'tor does not require anything. This is corrected within the suggested resolution. Further-on the P/R has been adapted to the FCD numbering.

[ 2010-03-25 Daniel updated wording: ]

> The issue became updated to fix some minor inconsistencies and to ensure a similarly required fix for `std::pair`, which has the same specification problem as `std::tuple`, since `pair` became extended to support reference members as well.

[Original proposed resolution:]

1. Change 22.3.2 [pairs.pair]/1 as indicated [The changes for the effects elements are not normative changes, they just ensure harmonization with existing wording style]:

   >     constexpr pair();

   > > Requires: `first_type` and `second_type` shall satisfy the `DefaultConstructible` requirements.

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

2. Change 22.3.2 [pairs.pair]/2 as indicated:

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

   > > Requires: `is_constructible<T1, const T1&>::value` is `true` and `is_constructible<T2, const T2&>::value` is `true`.

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

3. Change 22.3.2 [pairs.pair]/3 as indicated:

   >     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.

4. Change 22.3.2 [pairs.pair]/5 as indicated [The change in the effects element should be non-normatively and is in compatible to the change suggestion of 1324]:

   >     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.

5. Change 22.3.2 [pairs.pair]/6 as indicated:

   >     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)`.

6. Change 22.3.2 [pairs.pair]/7+8 as indicated [The deletion in the effects element should be non-normatively]:

   >     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.

7. Change 22.3.2 [pairs.pair] before 12 as indicated:

   >     pair& operator=(pair&& p);

   > > Requires: `first_type` and `second_type` shall satisfy the `MoveAssignable` requirements.

   > > 12 Effects: Assigns to `first` with `std::move(p.first)` and to `second` with `std::move(p.second)`.

   > > 13 Returns: `*this`.

8. Change [pairs.pair] before 14 as indicated: [The heterogeneous usage of MoveAssignable is actually not defined, but the library uses it at several places, so we follow this tradition until a better term has been agreed on. One alternative could be to write "first_type shall be assignable from an rvalue of U [..]"]

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

   > > Requires: `first_type` shall be `MoveAssignable` from `U` and `second_type` shall be `MoveAssignable` from `V`.

   > > 14 Effects: Assigns to `first` with `std::move(p.first)` and to `second` with `std::move(p.second)`.

   > > 15 Returns: `*this`.

9. Change 22.4.4.1 [tuple.cnstr]/4+5 as indicated:

   >     explicit tuple(const Types&...);

   > > 4 Requires: `is_constructible<Ti, const Ti&>::value == true` for e Each type `Ti` in `Types` shall be copy constructible.

   > > 5 Effects: Copy i Initializes each element with the value of the corresponding parameter.

10. Change 22.4.4.1 [tuple.cnstr]/6 as indicated:

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

    > > 6 Requires: `is_constructible<Ti, Ui>::value == true` for e Each type `Ti` in `Types` shall satisfy the requirements of `MoveConstructible` (Table 33) from and for the corresponding type `Ui` in `UTypes`. `sizeof...(Types) == sizeof...(UTypes)`.

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

11. Change 22.4.4.1 [tuple.cnstr]/8+9 as indicated:

    >     tuple(const tuple& u) = default;

    > > 8 Requires: `is_constructible<Ti, const Ti&>::value == true` for e Each type `Ti` in `Types` shall satisfy the requirements of `CopyConstructible`(Table 34).

    > > 9 Effects: Initializes Copy constructs each element of `*this` with the corresponding element of `u`.

12. Change 22.4.4.1 [tuple.cnstr]/10+11 as indicated:

    >     tuple(tuple&& u);

    > > 10 Requires: Let `i` be in `[0, sizeof...(Types))` and let `Ti` be the `i`th type in `Types`. Then `is_constructible<Ti, Ti>::value` shall be `true` for all `i`. Each type in `Types` shall shall satisfy the requirements of `MoveConstructible` (Table 34).

    > > 11 Effects: For each `Ti` in `Types`, initializes the `i`th Move-constructs each element of `*this` with the corresponding element of `std::forward<Ti>(get<i>(``u``))`.

13. Change 22.4.4.1 [tuple.cnstr]/15+16 as indicated:

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

    > > 15 Requires: Let `i` be in `[0, sizeof...(Types))`, `Ti` be the `i`th type in `Types`, and `Ui` be the `i`th type in `UTypes`. Then `is_constructible<Ti, Ui>::value` shall be `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 each type `Ti`, initializes the `i`th Move-constructs each element of `*this` with the corresponding element of `std::forward<Ui>(get<i>(``u``))`.

14. Change 22.4.4.1 [tuple.cnstr]/19+20 as indicated:

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

    > > 19 Requires: `is_constructible<T1, U1>::value == true` for `t` `T`he first type `T1` in `Types` shall shall satisfy the requirements of `MoveConstructible`(Table 33) from `U1` and `is_constructible<T2, U2>::value == true` for the second type `T2` in `Types` shall be move-constructible from `U2`. `sizeof...(Types) == 2`.

    > > 20 Effects: Initializes Constructs the first element with `std::forward<U1>move(u.first)` and the second element with `std::forward<U2>move(u.second)`.

15. Change 22.4.5 [tuple.creation]/9-16 as indicated:

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

    > > 9 Requires: `is_constructible<Ti, const Ti&>::value == true` for each type `Ti` All the types in `TTypes` shall be `CopyConstructible` (Table 34). `is_constructible<Ui, const Ui&>::value == true` for each type `Ui` All the types in `UTypes` shall be `CopyConstructible` (Table 34).

    > > 10 Returns: A `tuple` object constructed by initializing copy constructing its first `sizeof...(TTypes)` elements from the corresponding elements of `t` and initializing copy 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);

    > > 11 Requires: Let `i` be in `[0, sizeof...(TTypes))`, `Ti` be the `i`th type in `Types`, `j` be in `[0, sizeof...(UTypes))`, and `Uj` be the `j`th type in `UTypes`. `is_constructible<Ti, Ti>::value` shall be `true` for each type `Ti` and `is_constructible<Uj, const Uj&>::value` shall be `true` for each type `Uj` All the types in `TTypes` shall be `MoveConstructible` (Table 34). All the types in `UTypes` shall be `CopyConstructible` (Table 35).

    > > 12 Returns: A `tuple` object constructed by initializing the `i`th element with `std::forward<Ti>(get<i>(t))` for all `Ti` in `TTypes` and initializing the `(j+sizeof...(TTypes))`th element with `get<j>(u)` for all `Uj` in `UTypes`. 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);

    > > 13 Requires: Let `i` be in `[0, sizeof...(TTypes))`, `Ti` be the `i`th type in `Types`, `j` be in `[0, sizeof...(UTypes))`, and `Uj` be the `j`th type in `UTypes`. `is_constructible<Ti, const Ti&>::value` shall be `true` for each type `Ti` and `is_constructible<Uj, Uj>::value` shall be `true` for each type `Uj` All the types in `TTypes` shall be `CopyConstructible` (Table 35). All the types in `UTypes` shall be `MoveConstructible` (Table 34).

    > > 14 Returns: A `tuple` object constructed by initializing the `i`th element with `get<i>(t)` for each type `Ti` and initializing the `(j+sizeof...(TTypes))`th element with `std::forward<Uj>(get<j>(u))` for each type `Uj` 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);

    > > 15 Requires: Let `i` be in `[0, sizeof...(TTypes))`, `Ti` be the `i`th type in `Types`, `j` be in `[0, sizeof...(UTypes))`, and `Uj` be the `j`th type in `UTypes`. `is_constructible<Ti, Ti>::value` shall be `true` for each type `Ti` and `is_constructible<Uj, Uj>::value` shall be `true` for each type `Uj` All the types in `TTypes` shall be `MoveConstructible` (Table 34). All the types in `UTypes` shall be `MoveConstructible` (Table 34).

    > > 16 Returns: A `tuple` object constructed by initializing the `i`th element with `std::forward<Ti>(get<i>(t))` for each type `Ti` and initializing the `(j+sizeof...(TTypes))`th element with `std::forward<Uj>(get<j>(u))` for each type `Uj` 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`.

[ 2010-10-24 Daniel adds: ]

> Accepting n3140 would solve this issue.

Proposed resolution:

See n3140.