Addressed issues:
LWG 2312 is partially addressed: The currently proposed wording still intends to make an implementation conforming that would accept a smaller number of elements values compared to the actual tuple-size (and to value-initialize the remaining ones), therefore the size-related requirements are currently not part of the SFINAE constraints. If LWG 2312 would be accepted the corresponding constraints would be similarly adjusted as described by the issue and the corresponding requirement could be removed.
Changes since N3739 (revision 1):
Improved the current rationale (Thanks to Mike Spertus!)
During the Rapperswil LEWG discussion a new vote lead to a revised result on the preferred representation form:
Do we prefer the EXPLICIT(see below) constexpr tuple(UTypes&&...); editorial representation?
SF-F-N-A-SA
0-8-3-2-0
Therefore this revision now uses the annotation EXPLICIT for the affected constructor pairs and defines the meaning of that in Clause 17.
Changes since N3680:
During a straw-pool vote there was a small majority (4:3) in favour for keeping the two-signatures approach, so this presentation form was used in this revision.
The original example has been improved.
The missing application of the declaration pattern to the allocator_arg_t constructors has been fixed.
Consequent usage of the more precise is_convertible trait instead of the somewhat vague is implicitly convertible description.
For many programmers it is a surprise to find out, that the following code is rejected by their compiler:
std::tuple<int, int> pixel_coordinates() { return {10, -15}; // Oops: Error } struct NonCopyable { NonCopyable(int); NonCopyable(const NonCopyable&) = delete; }; std::pair<NonCopyable, double> pmd{42, 3.14}; // Oops: Error
What is wrong with this? Why doesn't this just work?
This paper explains the reason for the current specification of pair and tuple and suggests changes to the working paper to fix these and some other bothersome constraints of these general type wrappers.
At the time when N3240 was proposed, several driving forces defined the constraints of resolving a bunch of library issues and NB comments.
One notable intention was to prevent that the type wrappers tuple and pair should allow implicit conversions for wrapped types that are not implicitly convertible, as expressed by LWG 1324 and DE 15.
Another relevant requirement was to keep backward-compatibility to C++03 in regard to null pointer literals expressed as integral null constants as described by LWG 811.
At that time there was a strong resistance to add further constructors especially to std::pair. At some point in time there did exist a very large number of such constructors due to allocator support. One important consequence of this pair simplification was the acceptance of N3059.
Thus with the previous acceptance of proposal N3240 the specification provides the following advantages for pair and tuple:
Heterogenous template constructors are constrained upon the criterion that the element-wise source types are implicitly convertible to the element-wise destination types.
struct B { explicit B(bool); };
std::tuple<B> tb = std::tuple<bool>(); // Error
The non-template constructor tuple(const Types&...) and the corresponding template-constructor are explicit. This prevents that a single-element tuple from being copy-initialized by an argument object and has only an explicit constructor for this construction.
struct X { X(); explicit X(const X&); } x; std::tuple<X> tx = x; // Error struct E { explicit E(int); }; std::tuple<E> te = 42; // Error
Non-template constructors accepting a sequence of elements, such as explicit tuple(const Types&...) and pair(const T1& x, const T2& y), are still kept to support the special conversion scenario where a pointer(-to-member) element type is initialized with the null pointer constant 0 instead of nullptr.
class C; std::tuple<int*> tpi(0); // OK std::tuple<int C::*> tpmi(0); // OK
We propose that pair/tuple obey the same initialization rules as their underlying element types. Unless we have such "perfect initialization", pair and tuple exhibit confusing and unintuitive behavior as illustrated by the examples below.
It means that tuple objects cannot be returned from a function as simple as this:
std::tuple<int, int> foo_tuple() { return {1, -1}; // Error } std::pair<int, int> foo_pair() { return {1, -1}; // OK }
It means that tuple or pair objects cannot be constructed for element types that cannot be copied:
struct D { D(int); D(const D&) = delete; };
std::tuple<D> td(12); // Error
It even means that tuple or pair objects cannot be direct-constructed for element types via an explicit conversion:
struct Y { explicit Y(int); };
std::tuple<Y> ty(12); // Error
It has been observed by Johannes Schaub that there exists a defect with tuple in regard to the non-template constructor explicit tuple(const Types&...): The current specification has the effect that the instantiation of tuple<> would be required to be ill-formed because it has two conflicting default constructors.
Starting with the last point: This is indeed a simple oversight that slipped in during the tuple standardization. The TR1 document did have the following specification:
template <class T1 = unspecified ,
class T2 = unspecified ,
...,
class TM = unspecified >
class tuple
{
public:
tuple();
explicit tuple(P1, P2, ..., PN); // iff N > 0
[…]
};
When the variadic form of tuples was proposed via N2151 and its successors, the highlighted size constraint inadvertently got lost.
The other three problems are all caused by (A) constructors that are always explicit and (B) by constrained constructor templates that impose implicit convertible constraints on the element types.
So, notwithstanding the good motivation behind the current specification of pair and tuple, it turns out to have some unfortunate consequences.
This proposal is intending to solve all these problems by a simple procedure that still ensures that all positive aspects of the current specification are conserved.
Before explaining the general outline of this proposal it is more helpful to start with a simple, but useful programming idiom.
Consider the following class template A that is intended to be used as a wrapper for some other type T:
#include <type_traits> #include <utility> template<class T> struct A { template<class U, typename std::enable_if< std::is_constructible<T, U>::value && std::is_convertible<U, T>::value , bool>::type = false > A(U&& u) : t(std::forward<U>(u)) {} template<class U, typename std::enable_if< std::is_constructible<T, U>::value && !std::is_convertible<U, T>::value , bool>::type = false > explicit A(U&& u) : t(std::forward<U>(u)) {} T t; };
The shown constructors both use perfect forwarding and they have essentially the same signatures except for one being explicit, the other one not. Furthermore, they are mutually exclusively constrained. In other words: This combination behaves for any destination type T and any argument type U like a single constructor that is either explicit or non-explicit (or no constructor at all). Attempts to construct a A<T> from some value of type U will reflect the allowed initialization forms of the wrapped type T:
struct Im{ Im(int){} }; struct Ex{ explicit Ex(int){} }; A<Im> ai1(1); // OK A<Im> ai2{2}; // OK A<Im> ai3 = 3; // OK A<Im> ai4 = {4}; // OK A<Ex> ae1(1); // OK A<Ex> ae2{2}; // OK A<Ex> ae3 = 3; // Error A<Ex> ae4 = {4}; // Error
This technique can easily be extended to the variadic template case, and when doing so can be considered as a key to solving the problems of tuple and pair.
It should be noted here, that for the general case the std::is_constructible<T, U>::value requirement for the non-explicit constructor which is constrained on std::is_convertible<U, T>::value is not redundant, because it is possible to create types that can be copy-initialized but not direct-initialized:
struct Odd { explicit Odd(int) = delete; Odd(long){} }; Odd o2 = 1; // OK Odd o1(1); // Error
Technically it would be possible to apply the same technique of creating element-dependent explicit or non-explicit default constructors. This application was shortly considered during the write-up of this proposal, but rejected because for the current C++ rules there is no longer any observable difference for an explicit default constructor that cannot be invoked with more than zero arguments and one that is not explicit.
The technique cannot be applied to copy/move operators in the same way as for the other constructors (because these special member functions cannot be templates) and given the very rare request for such a support the idea was no further investigated by the author. A second argument against providing this support is based on the consistency with the core language rules that the explicit-character of these constructors is not conserved for the implicitly declared versions in classes that contain corresponding sub-objects with such explicit constructors.
As shown above, the current over-constraining restrictions of the pair and tuple constructors are due to unconditional usage of explicit and implicitly convertible requirements.
The general approach of this proposal is to require "perfect initialization" semantics for pair and tuple constructors extended to the variadic case. Albeit this seemingly doubles the number of constructor declarations in the draft, it does not change the effective number of these for a particular combination of element type and source type of some initialization due to their mutual exclusion property.
In theory the same technique could be applied to the piecewise_construct_t of pair. This proposal does not propose this, because this constructor is specifically asked for by the corresponding tag and there are no further constraint except the is_constructible requirements.
In addition, this proposal fixes the specification problem of tuple<>'s default constructors.
The wording is intentionally chosen, so that an implementation is not required (but allowed) to use the "perfect initialization" idiom.
This is done by taking advantage of the already existing nomenclature "This function does not participate in overload resolution unless […]". Its worth emphasizing that even though this phrase is usually used to describe constrained templates in the Library specification, the actual wording of this doesn't necessarily imply to "sfinae out" template functions. Many library implementations solve this problem by providing a specialization for the empty tuple case that does not provide the additional default constructor, for example. This is also a valid way to ensure that functions don't participate in overload resolution.
In C++03 explicit constructors had no behavioural difference, unless they had been single-argument constructors, so one might suggest to restrict adding the explicit keyword to constructors that take exactly one argument.
I think this is idea is flawed (unless I'm using specifically tagged constructors like the piecewise-one of pair). Consider the following example:
#include <tuple> #include <chrono> #include <iostream> using hms_t = std::tuple<std::chrono::hours, std::chrono::minutes, std::chrono::seconds>; void launch_rocket_at(std::chrono::seconds s) { std::cout << "launching rocket in " << s.count() << " seconds!\n"; } void launch_rocket_at(hms_t times) { using namespace std; launch_rocket_at(get<0>(times) + get<1>(times) + get<2>(times)); } int main() { using namespace std; launch_rocket_at(make_tuple(1, 2, 3)); // #1: very scary launch_rocket_at({1, 2, 3}); // #2: even scarier using namespace std::chrono; launch_rocket_at(make_tuple(hours(1), minutes(2), seconds(3))); // #3: Perfectly clear! launch_rocket_at({hours(1), minutes(2), seconds(3)}); // #4: Also clear! launch_rocket_at(hms_t{1, 2, 3}); // #5: And this, too }
which should output:
launching rocket in 3723 seconds! launching rocket in 3723 seconds! launching rocket in 3723 seconds!
If the former two calls to function launch_rocket_at where possible, this would directly subvert the intended explicitness of the std::duration constructor and would make using the time-utility types much more unsafe. Why? Consider the following scenario:
If the client believed that the order of the units was seconds, minutes, hours, and input 3, 2, 1, — intending 3 seconds, 2 minutes, and 1 hour — the rocket would launch in 10,921 seconds instead of the intended 3,723 seconds. This mistake can indeed easily happen, if you look again at the lines marked with #1 and #2.
Due to our intentionally conserved constraints to be explicit here we catch that mistake at compile-time, instead of having to shoot the rocket down...
During the write-up of this proposal I had the idea of replacing the prototype declaration pairs by a single one expressed by some pseudo-macro that looks like a single declaration. For example
template <class... UTypes> constexpr tuple(UTypes&&...); template <class... UTypes> explicit constexpr tuple(UTypes&&...);
could instead be declared as follows:
template <class... UTypes> EXPLICIT constexpr tuple(UTypes&&...);
This form of representation still means that EXPLICIT needs to be defined somewhere and somehow, but only once.
There was a strong preference during the Rapperswil 2014 LEWG discussion to use the "macro" instead of the individual declarations.
This paper does not use a previously suggested form of EXPLICIT(see below) instead of EXPLICIT, because there is nothing specific to explain below again (The different SFINAE conditions are not relevant at that point, because they appear as usual with the normal, detailed prototype specifications).
In any way, the final editorial decision is being handed over to the project editor.
The proposed wording changes refer to N3936.
Insert the following new sub-clause immediately following 17.5.2.3 [objects.within.classes] as a further children of 17.5.2 [conventions]:
EXPLICIT constructors [explicit.within.classes]
-?- For the sake of exposition, the library clauses sometimes annotate constructors with EXPLICIT. For every constructor declaration that contains EXPLICIT, the meaning is equivalent to two consecutive declarations of exactly the same members, except that one of these two members has the EXPLICIT removed and the other one is declared with a function-specifier explicit instead of EXPLICIT.
Change 20.3.2 [pairs.pair], class template pair synopsis, as indicated:
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(); EXPLICIT constexpr pair(const T1& x, const T2& y); template<class U, class V> EXPLICIT constexpr pair(U&& x, V&& y); template<class U, class V> EXPLICIT constexpr pair(const pair<U, V>& p); template<class U, class V> EXPLICIT constexpr 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) noexcept(see below); template<class U, class V> pair& operator=(pair<U, V>&& p); void swap(pair& p) noexcept(see below); }; }
Change 20.3.2 [pairs.pair] around p5 as indicated:
EXPLICIT constexpr pair(const T1& x, const T2& y);
-5- Requires: is_copy_constructible<first_type>::value is true and is_copy_constructible<second_type>::value is true.-6- Effects: The constructors initialize
sfirst with x and second with y.-?- Remarks: The non-explicit constructor shall not participate in overload resolution unless
is_copy_constructible<first_type>::value is true and is_copy_constructible<second_type>::value is true, and
is_convertible<const first_type&, first_type>::value is true and is_convertible<const second_type&, second_type>::value is true
The explicit constructor shall not participate in overload resolution unless
is_copy_constructible<first_type>::value is true and is_copy_constructible<second_type>::value is true, and
is_convertible<const first_type&, first_type>::value is false or is_convertible<const second_type&, second_type>::value is false
Change 20.3.2 [pairs.pair] around p7 as indicated:
template<class U, class V> EXPLICIT constexpr pair(U&& x, V&& y);
-7- Requires: is_constructible<first_type, U&&>::value is true and is_constructible<second_type, V&&>::value is true.-8- Effects: The constructors initialize
sfirst with std::forward<U>(x) and second with std::forward<V>(y).-9- 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.The non-explicit constructor shall not participate in overload resolution unless
is_constructible<first_type, U&&>::value is true and is_constructible<second_type, V&&>::value is true, and
is_convertible<U&&, first_type>::value is true and is_convertible<V&&, second_type>::value is true
The explicit constructor shall not participate in overload resolution unless
is_constructible<first_type, U&&>::value is true and is_constructible<second_type, V&&>::value is true, and
is_convertible<U&&, first_type>::value is false or is_convertible<V&&, second_type>::value is false
Change 20.3.2 [pairs.pair] around p10 as indicated:
template<class U, class V> EXPLICIT constexpr pair(const pair<U, V>& p);
-10- Requires: is_constructible<first_type, const U&>::value is true and is_constructible<second_type, const V&>::value is true.-11- Effects: The constructors initialize
Initializesmembers from the corresponding members of the argument.-12- 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.The non-explicit constructor shall not participate in overload resolution unless
is_constructible<first_type, const U&>::value is true and is_constructible<second_type, const V&>::value is true, and
is_convertible<const U&, first_type>::value is true and is_convertible<const V&, second_type>::value is true
The explicit constructor shall not participate in overload resolution unless
is_constructible<first_type, const U&>::value is true and is_constructible<second_type, const V&>::value is true, and
is_convertible<const U&, first_type>::value is false or is_convertible<const V&, second_type>::value is false
Change 20.3.2 [pairs.pair] around p13 as indicated:
template<class U, class V> EXPLICIT constexpr pair(pair<U, V>&& p);
-13- Requires: is_constructible<first_type, U&&>::value is true and is_constructible<second_type, V&&>::value is true.-14- Effects: The constructors initialize
sfirst with std::forward<U>(p.first) and second with std::forward<V>(p.second).-15- 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.The non-explicit constructor shall not participate in overload resolution unless
is_constructible<first_type, U&&>::value is true and is_constructible<second_type, V&&>::value is true, and
is_convertible<U&&, first_type>::value is true and is_convertible<V&&, second_type>::value is true
The explicit constructor shall not participate in overload resolution unless
is_constructible<first_type, U&&>::value is true and is_constructible<second_type, V&&>::value is true, and
is_convertible<U&&, first_type>::value is false or is_convertible<V&&, second_type>::value is false
Change 20.4.2 [tuple.tuple], class template tuple synopsis, as indicated. The intent is to declare the set of "conditionally explicit" constructors and to fix the multiple default constructor problem for empty tuples.
namespace std { template <class... Types> class tuple { public: // 20.4.2.1, tuple construction constexpr tuple(); EXPLICITexplicitconstexpr tuple(const Types&...); // only if sizeof...(Types) >= 1 template <class... UTypes> EXPLICITexplicitconstexpr tuple(UTypes&&...); // only if sizeof...(Types) >= 1 tuple(const tuple&) = default; tuple(tuple&&) = default; template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>&); template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&&); template <class U1, class U2> EXPLICIT constexpr tuple(const pair<U1, U2>&); // only if sizeof...(Types) == 2 template <class U1, class U2> EXPLICIT constexpr tuple(pair<U1, U2>&&); // only if sizeof...(Types) == 2 // allocator-extended constructors template <class Alloc> EXPLICIT tuple(allocator_arg_t, const Alloc& a); template <class Alloc> EXPLICIT tuple(allocator_arg_t, const Alloc& a, const Types&...); template <class Alloc, class... UTypes> EXPLICIT tuple(allocator_arg_t, const Alloc& a, UTypes&&...); template <class Alloc> EXPLICIT tuple(allocator_arg_t, const Alloc& a, const tuple&); template <class Alloc> EXPLICIT tuple(allocator_arg_t, const Alloc& a, tuple&&); template <class Alloc, class... UTypes> EXPLICIT tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&); template <class Alloc, class... UTypes> EXPLICIT tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&&); template <class Alloc, class U1, class U2> EXPLICIT tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&); template <class Alloc, class U1, class U2> EXPLICIT tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&); [..] }; }
Change 20.4.2.1 [tuple.cnstr] around p6 as indicated:
EXPLICITexplicitconstexpr tuple(const Types&...);
-6- Requires: is_copy_constructible<Ti>::value is true for all i.-7- Effects: The constructors initialize
Initializeseach element with the value of the corresponding parameter.-?- Remarks: The non-explicit constructor shall not participate in overload resolution unless
sizeof...(Types) >= 1,
is_copy_constructible<Ti>::value is true for all i, and
is_convertible<const Ti&, Ti>::value is true for all i
The explicit constructor shall not participate in overload resolution unless
sizeof...(Types) >= 1,
is_copy_constructible<Ti>::value is true for all i, and
is_convertible<const Ti&, Ti>::value is false for at least one i
Change 20.4.2.1 [tuple.cnstr] around p8 as indicated:
template <class... UTypes> EXPLICITexplicitconstexpr tuple(UTypes&&... u);-8- Requires: sizeof...(Types) == sizeof...(UTypes).
is_constructible<Ti, Ui&&>::value is true for all i.-9- Effects: The constructors initialize
Initializesthe elements in the tuple with the corresponding value in std::forward<UTypes>(u).-10- Remarks:
This constructor shall not participate in overload resolution unless each type in UTypes is implicitly convertible to its corresponding type in Types.The non-explicit constructor shall not participate in overload resolution unless
sizeof...(Types) >= 1,
is_constructible<Ti, Ui&&>::value is true for all i, and
is_convertible<Ui&&, Ti>::value is true for all i
The explicit constructor shall not participate in overload resolution unless
sizeof...(Types) >= 1,
is_constructible<Ti, Ui&&>::value is true for all i, and
is_convertible<Ui&&, Ti>::value is false for at least one i
Change 20.4.2.1 [tuple.cnstr] around p15 as indicated:
template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);-15- Requires: sizeof...(Types) == sizeof...(UTypes).
is_constructible<Ti, const Ui&>::value is true for all i.-16- Effects: The constructors initialize
Constructseach element of *this with the corresponding element of u.-17- Remarks:
This constructor shall not participate in overload resolution unless const Ui& is implicitly convertible to Ti for all i.The non-explicit constructor shall not participate in overload resolution unless
is_constructible<Ti, const Ui&>::value is true for all i, and
is_convertible<const Ui&, Ti>::value is true for all i
The explicit constructor shall not participate in overload resolution unless
is_constructible<Ti, const Ui&>::value is true for all i, and
is_convertible<const Ui&, Ti>::value is false for at least one i
Change 20.4.2.1 [tuple.cnstr] around p18 as indicated:
template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);-18- Requires: sizeof...(Types) == sizeof...(UTypes).
is_constructible<Ti, Ui&&>::value is true for all i.-19- Effects: For all i, the constructors initialize
sthe i^{th} element of *this with std::forward<Ui>(get<i>(u)).-20- Remarks:
This constructor shall not participate in overload resolution unless each type in UTypes is implicitly convertible to its corresponding type in Types.The non-explicit constructor shall not participate in overload resolution unless
is_constructible<Ti, Ui&&>::value is true for all i, and
is_convertible<Ui&&, Ti>::value is true for all i
The explicit constructor shall not participate in overload resolution unless
is_constructible<Ti, Ui&&>::value is true for all i, and
is_convertible<Ui&&, Ti>::value is false for at least one i
Change 20.4.2.1 [tuple.cnstr] around p21 as indicated:
template <class U1, class U2> EXPLICIT constexpr tuple(const pair<U1, U2>& u);-21- Requires: sizeof...(Types) == 2.
is_constructible<T0, const U1&>::value is true for the first type T0 in Types and is_constructible<T1, const U2&>::value is true for the second type T1 in Types.-22- Effects: The constructors initialize
Constructsthe first element with u.first and the second element with u.second.-23- 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.The non-explicit constructor shall not participate in overload resolution unless
is_constructible<Ti, const Ui&>::value is true for all i, and
is_convertible<const Ui&, Ti>::value is true for all i
The explicit constructor shall not participate in overload resolution unless
is_constructible<Ti, const Ui&>::value is true for all i, and
is_convertible<const Ui&, Ti>::value is false for at least one i
Change 20.4.2.1 [tuple.cnstr] around p24 as indicated:
template <class U1, class U2> EXPLICIT constexpr tuple(pair<U1, U2>&& u);-24- Requires: sizeof...(Types) == 2.
is_constructible<T0, U1&&>::value is true for the first type T0 in Types and is_constructible<T1, U2&&>::value is true for the second type T1 in Types.-25- Effects: The constructors i
Initializesthe first element with std::forward<U1>(u.first) and the second element with std::forward<U2>(u.second).-26- Remarks:
This constructor shall not participate in overload resolution unless U1 is implicitly convertible to T0 and U2 is implicitly convertible to T1.The non-explicit constructor shall not participate in overload resolution unless
is_constructible<Ti, Ui&&>::value is true for all i, and
is_convertible<Ui&&, Ti>::value is true for all i
The explicit constructor shall not participate in overload resolution unless
is_constructible<Ti, Ui&&>::value is true for all i, and
is_convertible<Ui&&, Ti>::value is false for at least one i
Change 20.4.2.1 [tuple.cnstr] around p27 as indicated:
template <class Alloc> EXPLICIT tuple(allocator_arg_t, const Alloc& a); template <class Alloc> EXPLICIT tuple(allocator_arg_t, const Alloc& a, const Types&...); template <class Alloc, class... UTypes> EXPLICIT tuple(allocator_arg_t, const Alloc& a, UTypes&&...); template <class Alloc> EXPLICIT tuple(allocator_arg_t, const Alloc& a, const tuple&); template <class Alloc> EXPLICIT tuple(allocator_arg_t, const Alloc& a, tuple&&); template <class Alloc, class... UTypes> EXPLICIT tuple(allocator_arg_t, const Alloc& a, const tuple<UTypes...>&); template <class Alloc, class... UTypes> EXPLICIT tuple(allocator_arg_t, const Alloc& a, tuple<UTypes...>&&); template <class Alloc, class U1, class U2> EXPLICIT tuple(allocator_arg_t, const Alloc& a, const pair<U1, U2>&); template <class Alloc, class U1, class U2> EXPLICIT tuple(allocator_arg_t, const Alloc& a, pair<U1, U2>&&);-27- Requires: Alloc shall meet the requirements for an Allocator (17.6.3.5).
-28- Effects: Equivalent to the preceding constructors except that each element is constructed with uses-allocator construction (20.8.7.2).
The following example presents how to constrain even non-template functions such as the constructors that directly take the element types.
template<class T1, class T2> struct pair { […] template<class U1 = T1, class U2 = T2, typename enable_if< is_copy_constructible<U1>::value && is_copy_constructible<U2>::value && is_convertible<const U1&, U1>::value && is_convertible<const U2&, U2>::value , bool>::type = false > constexpr pair(const T1&, const T2&); template<class U1 = T1, class U2 = T2, typename enable_if< is_copy_constructible<U1>::value && is_copy_constructible<U2>::value && !(is_convertible<const U1&, U1>::value && is_convertible<const U2&, U2>::value) , bool>::type = false > explicit constexpr pair(const T1&, const T2&); };
I would like to thank Howard Hinnant for his very helpful discussions and comments during reviews of this paper and for his motivating example. Thanks also to Jonathan Wakely for his review that improved this proposal to a large extend. Thanks as well go to Mike Spertus for helping to improve the rationale.