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 ?
I think
is_constructibleenough, why are we usingis_convertiblehere?
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.
To implement [pairs.pair] / 12 :
This constructor should not be involved in overload resolution unless
is_constructible_v<first_type, U1&&>not true andis_constructible_v<second_type, U2&&>true. The constructor is explicit if and only ifis_convertible_v<U1&&, first_type>is false oris_convertible_v<U2&&, second_type>is false.