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.2 [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.2 [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.2 [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(i)]:

    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.2 [tuple.cnstr]/4+5 as indicated:

    explicit tuple(const Types&...);
    

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

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

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

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

    6 Requires: is_constructible<Ti, Ui>::value == true for eEach type Ti in Types shall satisfy the requirements of MoveConstructible (Table 33) fromand 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.2 [tuple.cnstr]/8+9 as indicated:

    tuple(const tuple& u) = default;
    

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

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

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

    tuple(tuple&& u);
    

    10 Requires: Let i be in [0, sizeof...(Types)) and let Ti be the ith 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 ith Move-constructs each element of *this with the corresponding element of std::forward<Ti>(get<i>(u)).

  13. Change 22.4.4.2 [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 ith type in Types, and Ui be the ith 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 ith Move-constructs each element of *this with the corresponding element of std::forward<Ui>(get<i>(u)).

  14. Change 22.4.4.2 [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 tThe 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: InitializesConstructs 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 TiAll the types in TTypes shall be CopyConstructible (Table 34). is_constructible<Ui, const Ui&>::value == true for each type UiAll the types in UTypes shall be CopyConstructible (Table 34).

    10 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);
    

    11 Requires: Let i be in [0, sizeof...(TTypes)), Ti be the ith type in Types, j be in [0, sizeof...(UTypes)), and Uj be the jth 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 ith 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 ith type in Types, j be in [0, sizeof...(UTypes)), and Uj be the jth 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 ith 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 ith type in Types, j be in [0, sizeof...(UTypes)), and Uj be the jth 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 ith 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.