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.
pair and tuple functionsSection: 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:
22.4.4.2 [tuple.cnstr] before p.4 and p.8, resp.:
explicit tuple(const Types&...);4 Requires: Each type in
Typesshall be copy constructible.tuple(const tuple& u) = default;8 Requires: Each type in
Typesshall satisfy the requirements ofCopyConstructible(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.
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
Typesshall satisfy the requirements ofMoveConstructible(Table 33) from the corresponding type inUTypes.sizeof...(Types) == sizeof...(UTypes).tuple(tuple&& u);10 Requires: Each
typeinTypesshall shall satisfy the requirements ofMoveConstructible(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>::valueshall evaluate to true for allTiinTypesand for allUiinUTypes
and
is_constructible<Ti, Ti>::valueshall evaluate to true for allTiinTypes
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 asstd::tuple, sincepairbecame extended to support reference members as well.
[Original proposed resolution:]
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_typeandsecond_typeshall satisfy theDefaultConstructiblerequirements.1 Effects: Value-initializes
firstandsecond.Initializes its members as if implemented:pair() : first(), second() { }.
Change 22.3.2 [pairs.pair]/2 as indicated:
pair(const T1& x, const T2& y);Requires:
is_constructible<T1, const T1&>::valueistrueandis_constructible<T2, const T2&>::valueistrue.2 Effects: The constructor initializes
firstwithxandsecondwithy.
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>::valueistrueandis_constructible<second_type, V>::valueistrue.3 Effects: The constructor initializes
firstwithstd::forward<U>(x)andsecondwithstd::forward<V>(y).4 Remarks: If
Uis not implicitly convertible tofirst_typeorVis not implicitly convertible tosecond_typethis constructor shall not participate in overload resolution.
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&>::valueistrueandis_constructible<second_type, const V&>::valueistrue.5 Effects: Initializes members from the corresponding members of the argument
, performing implicit conversions as needed.
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>::valueistrueandis_constructible<second_type, V>::valueistrue.6 Effects: The constructor initializes
firstwithstd::andmoveforward<U>(p.first)secondwithstd::.moveforward<V>(p.second)
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...>::valueistrueandis_constructible<second_type, Args2...>::valueistrue.All the types inArgs1andArgs2shall beCopyConstructible(Table 35).T1shall be constructible fromArgs1.T2shall be constructible fromArgs2.8 Effects: The constructor initializes
firstwith arguments of typesArgs1...obtained by forwarding the elements offirst_argsand initializessecondwith arguments of typesArgs2...obtained by forwarding the elements ofsecond_args.(Here, forwarding an elementThis form of construction, whereby constructor arguments forxof typeUwithin atupleobject means callingstd::forward<U>(x).)firstandsecondare each provided in a separatetupleobject, is called piecewise construction.
Change 22.3.2 [pairs.pair] before 12 as indicated:
pair& operator=(pair&& p);Requires:
first_typeandsecond_typeshall satisfy theMoveAssignablerequirements.12 Effects: Assigns to
firstwithstd::move(p.first)and tosecondwithstd::move(p.second).13 Returns:
*this.
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_typeshall beMoveAssignablefromUandsecond_typeshall beMoveAssignablefromV.14 Effects: Assigns to
firstwithstd::move(p.first)and tosecondwithstd::move(p.second).15 Returns:
*this.
Change 22.4.4.2 [tuple.cnstr]/4+5 as indicated:
explicit tuple(const Types&...);4 Requires:
is_constructible<Ti, const Ti&>::value == truefor eEach typeTiinTypesshall be copy constructible.5 Effects:
Copy iInitializes each element with the value of the corresponding parameter.
Change 22.4.4.2 [tuple.cnstr]/6 as indicated:
template <class... UTypes> explicit tuple(UTypes&&... u);6 Requires:
is_constructible<Ti, Ui>::value == truefor eEach typeTiinTypesshall satisfy the requirements ofand for the corresponding typeMoveConstructible(Table 33) fromUiinUTypes.sizeof...(Types) == sizeof...(UTypes).7 Effects: Initializes the elements in the
tuplewith the corresponding value instd::forward<UTypes>(u).
Change 22.4.4.2 [tuple.cnstr]/8+9 as indicated:
tuple(const tuple& u) = default;8 Requires:
is_constructible<Ti, const Ti&>::value == truefor eEach typeTiinTypesshall satisfy the requirements of.CopyConstructible(Table 34)9 Effects: Initializes
Copy constructseach element of*thiswith the corresponding element ofu.
Change 22.4.4.2 [tuple.cnstr]/10+11 as indicated:
tuple(tuple&& u);10 Requires: Let
ibe in[0, sizeof...(Types))and letTibe theith type inTypes. Thenis_constructible<Ti, Ti>::valueshall betruefor alli.Each type in.Typesshall shall satisfy the requirements ofMoveConstructible(Table 34)11 Effects: For each
TiinTypes, initializes theithMove-constructs eachelement of*thiswiththe corresponding element ofstd::forward<Ti>(get<i>(u)).
Change 22.4.4.2 [tuple.cnstr]/15+16 as indicated:
template <class... UTypes> tuple(tuple<UTypes...>&& u);15 Requires: Let
ibe in[0, sizeof...(Types)),Tibe theith type inTypes, andUibe theith type inUTypes. Thenis_constructible<Ti, Ui>::valueshall betruefor alli.Each type in.Typesshall shall satisfy the requirements ofMoveConstructible(Table 34) from the corresponding type inUTypessizeof...(Types) == sizeof...(UTypes).16 Effects: For each type
Ti, initializes theithMove-constructs eachelement of*thiswiththe corresponding element ofstd::forward<Ui>(get<i>(u)).
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 == trueforthe first typeTT1inTypesshall shall satisfy the requirements ofandMoveConstructible(Table 33) fromU1is_constructible<T2, U2>::value == truefor the second typeT2inTypesshall be move-constructible from.U2sizeof...(Types) == 2.20 Effects: Initializes
Constructsthe first element withstd::forward<U1>and the second element withmove(u.first)std::forward<U2>.move(u.second)
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 == truefor each typeTiAll the typesinTTypesshall be.CopyConstructible(Table 34)is_constructible<Ui, const Ui&>::value == truefor each typeUiAll the typesinUTypesshall be.CopyConstructible(Table 34)10 Returns: A
tupleobject constructed by initializingcopy constructingits firstsizeof...(TTypes)elements from the corresponding elements oftand initializingcopy constructingits lastsizeof...(UTypes)elements from the corresponding elements ofu.template <class... TTypes, class... UTypes> tuple<TTypes..., UTypes...> tuple_cat(tuple<TTypes...>&& t, const tuple<UTypes...>& u);11 Requires: Let
ibe in[0, sizeof...(TTypes)),Tibe theith type inTypes,jbe in[0, sizeof...(UTypes)), andUjbe thejth type inUTypes.is_constructible<Ti, Ti>::valueshall betruefor each typeTiandis_constructible<Uj, const Uj&>::valueshall betruefor each typeUjAll the types in.TTypesshall beMoveConstructible(Table 34). All the types inUTypesshall beCopyConstructible(Table 35)12 Returns: A
tupleobject constructed by initializing theith element withstd::forward<Ti>(get<i>(t))for allTiinTTypesand initializing the(j+sizeof...(TTypes))th element withget<j>(u)for allUjinUTypes.move constructing its first.sizeof...(TTypes)elements from the corresponding elements oftand copy constructing its lastsizeof...(UTypes)elements from the corresponding elements ofutemplate <class... TTypes, class... UTypes> tuple<TTypes..., UTypes...> tuple_cat(const tuple<TTypes...>& t, tuple<UTypes...>&& u);13 Requires: Let
ibe in[0, sizeof...(TTypes)),Tibe theith type inTypes,jbe in[0, sizeof...(UTypes)), andUjbe thejth type inUTypes.is_constructible<Ti, const Ti&>::valueshall betruefor each typeTiandis_constructible<Uj, Uj>::valueshall betruefor each typeUjAll the types in.TTypesshall beCopyConstructible(Table 35). All the types inUTypesshall beMoveConstructible(Table 34)14 Returns: A
tupleobject constructed by initializing theith element withget<i>(t)for each typeTiand initializing the(j+sizeof...(TTypes))th element withstd::forward<Uj>(get<j>(u))for each typeUjcopy constructing its first.sizeof...(TTypes)elements from the corresponding elements oftand move constructing its lastsizeof...(UTypes)elements from the corresponding elements ofutemplate <class... TTypes, class... UTypes> tuple<TTypes..., UTypes...> tuple_cat(tuple<TTypes...>&& t, tuple<UTypes...>&& u);15 Requires: Let
ibe in[0, sizeof...(TTypes)),Tibe theith type inTypes,jbe in[0, sizeof...(UTypes)), andUjbe thejth type inUTypes.is_constructible<Ti, Ti>::valueshall betruefor each typeTiandis_constructible<Uj, Uj>::valueshall betruefor each typeUjAll the types in.TTypesshall beMoveConstructible(Table 34). All the types inUTypesshall beMoveConstructible(Table 34)16 Returns: A
tupleobject constructed by initializing theith element withstd::forward<Ti>(get<i>(t))for each typeTiand initializing the(j+sizeof...(TTypes))th element withstd::forward<Uj>(get<j>(u))for each typeUjmove constructing its first.sizeof...(TTypes)elements from the corresponding elements oftand move constructing its lastsizeof...(UTypes)elements from the corresponding elements ofu
[ 2010-10-24 Daniel adds: ]
Accepting n3140 would solve this issue.
Proposed resolution:
See n3140.