std :: pair (STL)? template

Why is "is_convertible" here in the <utility> std :: pair (STL)?

template<class _Other1, class _Other2, class = enable_if_t<is_constructible<_Ty1, _Other1>::value && is_constructible<_Ty2, _Other2>::value>, enable_if_t<is_convertible<_Other1, _Ty1>::value && is_convertible<_Other2, _Ty2>::value, int> = 0> constexpr pair(pair<_Other1, _Other2>&& _Right) _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value && is_nothrow_constructible<_Ty2, _Other2>::value)) : first(_STD forward<_Other1>(_Right.first)), second(_STD forward<_Other2>(_Right.second)) { // construct from moved compatible pair } template<class _Other1, class _Other2, class = enable_if_t<is_constructible<_Ty1, _Other1>::value && is_constructible<_Ty2, _Other2>::value>, enable_if_t<!is_convertible<_Other1, _Ty1>::value || !is_convertible<_Other2, _Ty2>::value, int> = 0> constexpr explicit pair(pair<_Other1, _Other2>&& _Right) _NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1>::value && is_nothrow_constructible<_Ty2, _Other2>::value)) : first(_STD forward<_Other1>(_Right.first)), second(_STD forward<_Other2>(_Right.second)) { // construct from moved compatible pair } 

service file for VS 2017, line 206, _Other1 and _Other2 are parameters, these are std :: pair , build func, and we use Other1 and Other2 to initialize the "first" and "second",

I think is_constructible is enough, why are we using is_convertible here?
and by the way, what is the difference between class = enable_if_t< ... ::value> and enable_if_t< ... ::value,int> = 0 ?

+7
c ++ standard-library c ++ 17
source share
2 answers

I think is_constructible enough, why are we using is_convertible here?

The goal here is to properly handle the explicit construct. Think about how to make the first one and try to write a wrapper (using REQUIRES here to hide any approach to SFINAE that you want):

 template <class T> class wrapper { public: template <class U, REQUIRES(std::is_constructible<T, U&&>::value)> wrapper(U&& u) : val(std::forward<U>(u)) { } private: T val; }; 

If all that we had, then:

 struct Imp { Imp(int ); }; struct Exp { explicit Exp(int ); }; Imp i = 0; // ok Exp e = 0; // error wrapper<Imp> wi = 0; // ok wrapper<Exp> we = 0; // ok?!? 

We definitely do not want the latter to be in order - it breaks expectations for Exp !

Now s_constructible<T, U&&> is true, if possible direct-initialize a T of a U&& - if T(std::declval<U&&>()) is a valid expression.

is_convertible<U&&, T> , on the other hand, checks if it is possible to copy-initialize a T from U&& . That is, if T copy() { return std::declval<U&&>(); } T copy() { return std::declval<U&&>(); } really.

The difference is that the latter does not work if the conversion is explicit :

 +-----+--------------------------+------------------------+ | | is_constructible<T, int> | is_convertible<int, T> | +-----+--------------------------+------------------------+ | Imp | true_type | true_type | | Exp | true_type | false_type | +-----+--------------------------+------------------------+ 

In order to correctly disclose evidence, we need to use both characteristics together - and we can create meta-features from them:

 template <class T, class From> using is_explicitly_constructible = std::integral_constant<bool, std::is_constructible<T, From>::value && !std::is_convertible<From, T>::value>; template <class T, class From> using is_implicitly_constructible = std::integral_constant<bool, std::is_constructible<T, From>::value && std::is_convertible<From, T>::value>; 

These two traits do not intersect, so we can write two constructor templates that are definitely not viable, where one constructor is explicit and the other is not:

 template <class T> class wrapper { public: template <class U, REQUIRES(is_explicitly_constructible<T, U&&>::value)> explicit wrapper(U&& u) : val(std::forward<U>(u)) { } template <class U, REQUIRES(is_implicitly_constructible<T, U&&>::value)> wrapper(U&& u) : val(std::forward<U>(u)) { } private: T val; }; 

This gives us the desired behavior:

 wrapper<Imp> wi = 0; // okay, calls non-explicit ctor wrapper<Exp> we = 0; // error wrapper<Exp> we2(0); // ok 

This is what the implementation does here - in addition to the two metacharacters, they have all the conditions written explicit ly.

+9
source share

To implement [pairs.pair] / 12 :

This constructor should not be involved in overload resolution unless is_constructible_v<first_type, U1&&> not true and is_constructible_v<second_type, U2&&> true. The constructor is explicit if and only if is_convertible_v<U1&&, first_type> is false or is_convertible_v<U2&&, second_type> is false.

+8
source share

All Articles