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
Types
shall be copy constructible.tuple(const tuple& u) = default;8 Requires: Each type in
Types
shall 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
Types
shall satisfy the requirements ofMoveConstructible
(Table 33) from the corresponding type inUTypes
.sizeof...(Types) == sizeof...(UTypes)
.tuple(tuple&& u);10 Requires: Each
type
inTypes
shall 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>::value
shall evaluate to true for allTi
inTypes
and for allUi
inUTypes
and
is_constructible<Ti, Ti>::value
shall evaluate to true for allTi
inTypes
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
, sincepair
became 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_type
andsecond_type
shall satisfy theDefaultConstructible
requirements.1 Effects: Value-initializes
first
andsecond
.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&>::value
istrue
andis_constructible<T2, const T2&>::value
istrue
.2 Effects: The constructor initializes
first
withx
andsecond
withy
.
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
istrue
andis_constructible<second_type, V>::value
istrue
.3 Effects: The constructor initializes
first
withstd::forward<U>(x)
andsecond
withstd::forward<V>(y)
.4 Remarks: If
U
is not implicitly convertible tofirst_type
orV
is not implicitly convertible tosecond_type
this 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]:
template<class U, class V> pair(const pair<U, V>& p);Requires:
is_constructible<first_type, const U&>::value
istrue
andis_constructible<second_type, const V&>::value
istrue
.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>::value
istrue
andis_constructible<second_type, V>::value
istrue
.6 Effects: The constructor initializes
first
withstd::
andmoveforward<U>(p.first)second
withstd::
.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...>::value
istrue
andis_constructible<second_type, Args2...>::value
istrue
.All the types inArgs1
andArgs2
shall beCopyConstructible
(Table 35).T1
shall be constructible fromArgs1
.T2
shall be constructible fromArgs2
.8 Effects: The constructor initializes
first
with arguments of typesArgs1...
obtained by forwarding the elements offirst_args
and initializessecond
with arguments of typesArgs2...
obtained by forwarding the elements ofsecond_args
.(Here, forwarding an elementThis form of construction, whereby constructor arguments forx
of typeU
within atuple
object means callingstd::forward<U>(x)
.)first
andsecond
are each provided in a separatetuple
object, is called piecewise construction.
Change 22.3.2 [pairs.pair] before 12 as indicated:
pair& operator=(pair&& p);Requires:
first_type
andsecond_type
shall satisfy theMoveAssignable
requirements.12 Effects: Assigns to
first
withstd::move(p.first)
and tosecond
withstd::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_type
shall beMoveAssignable
fromU
andsecond_type
shall beMoveAssignable
fromV
.14 Effects: Assigns to
first
withstd::move(p.first)
and tosecond
withstd::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 == true
for eEach typeTi
inTypes
shall 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 == true
for eEach typeTi
inTypes
shall satisfy the requirements ofand for the corresponding typeMoveConstructible
(Table 33) fromUi
inUTypes
.sizeof...(Types) == sizeof...(UTypes)
.7 Effects: Initializes the elements in the
tuple
with 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 == true
for eEach typeTi
inTypes
shall satisfy the requirements of.CopyConstructible
(Table 34)9 Effects: Initializes
Copy constructseach element of*this
with the corresponding element ofu
.
Change 22.4.4.2 [tuple.cnstr]/10+11 as indicated:
tuple(tuple&& u);10 Requires: Let
i
be in[0, sizeof...(Types))
and letTi
be thei
th type inTypes
. Thenis_constructible<Ti, Ti>::value
shall betrue
for alli
.Each type in.Types
shall shall satisfy the requirements ofMoveConstructible
(Table 34)11 Effects: For each
Ti
inTypes
, initializes thei
thMove-constructs eachelement of*this
withthe 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
i
be in[0, sizeof...(Types))
,Ti
be thei
th type inTypes
, andUi
be thei
th type inUTypes
. Thenis_constructible<Ti, Ui>::value
shall betrue
for alli
.Each type in.Types
shall shall satisfy the requirements ofMoveConstructible
(Table 34) from the corresponding type inUTypes
sizeof...(Types) == sizeof...(UTypes)
.16 Effects: For each type
Ti
, initializes thei
thMove-constructs eachelement of*this
withthe 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 == true
fort
he first typeT
T1
inTypes
shall shall satisfy the requirements ofandMoveConstructible
(Table 33) fromU1
is_constructible<T2, U2>::value == true
for the second typeT2
inTypes
shall be move-constructible from.U2
sizeof...(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 == true
for each typeTi
All the typesinTTypes
shall be.CopyConstructible
(Table 34)is_constructible<Ui, const Ui&>::value == true
for each typeUi
All the typesinUTypes
shall be.CopyConstructible
(Table 34)10 Returns: A
tuple
object constructed by initializingcopy constructingits firstsizeof...(TTypes)
elements from the corresponding elements oft
and 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
i
be in[0, sizeof...(TTypes))
,Ti
be thei
th type inTypes
,j
be in[0, sizeof...(UTypes))
, andUj
be thej
th type inUTypes
.is_constructible<Ti, Ti>::value
shall betrue
for each typeTi
andis_constructible<Uj, const Uj&>::value
shall betrue
for each typeUj
All the types in.TTypes
shall beMoveConstructible
(Table 34). All the types inUTypes
shall beCopyConstructible
(Table 35)12 Returns: A
tuple
object constructed by initializing thei
th element withstd::forward<Ti>(get<i>(t))
for allTi
inTTypes
and initializing the(j+sizeof...(TTypes))
th element withget<j>(u)
for allUj
inUTypes
.move constructing its first.sizeof...(TTypes)
elements from the corresponding elements oft
and copy constructing its lastsizeof...(UTypes)
elements from the corresponding elements ofu
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 thei
th type inTypes
,j
be in[0, sizeof...(UTypes))
, andUj
be thej
th type inUTypes
.is_constructible<Ti, const Ti&>::value
shall betrue
for each typeTi
andis_constructible<Uj, Uj>::value
shall betrue
for each typeUj
All the types in.TTypes
shall beCopyConstructible
(Table 35). All the types inUTypes
shall beMoveConstructible
(Table 34)14 Returns: A
tuple
object constructed by initializing thei
th element withget<i>(t)
for each typeTi
and initializing the(j+sizeof...(TTypes))
th element withstd::forward<Uj>(get<j>(u))
for each typeUj
copy constructing its first.sizeof...(TTypes)
elements from the corresponding elements oft
and move constructing its lastsizeof...(UTypes)
elements from the corresponding elements ofu
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 thei
th type inTypes
,j
be in[0, sizeof...(UTypes))
, andUj
be thej
th type inUTypes
.is_constructible<Ti, Ti>::value
shall betrue
for each typeTi
andis_constructible<Uj, Uj>::value
shall betrue
for each typeUj
All the types in.TTypes
shall beMoveConstructible
(Table 34). All the types inUTypes
shall beMoveConstructible
(Table 34)16 Returns: A
tuple
object constructed by initializing thei
th element withstd::forward<Ti>(get<i>(t))
for each typeTi
and initializing the(j+sizeof...(TTypes))
th element withstd::forward<Uj>(get<j>(u))
for each typeUj
move constructing its first.sizeof...(TTypes)
elements from the corresponding elements oft
and 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.