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
}
utility file for VS 2017 line 206,
_Other1 and _Other2 are parameters, this is std::pair's construction func,
and we are using Other1 and Other2 to initialize "first" and "second",
I think is_constructible is enough, why are we using is_convertible here?
and by the way, what's the difference between class = enable_if_t< ... ::value>
and enable_if_t< ... ::value,int> = 0
?
std::pair is a class template that provides a way to store two heterogeneous objects as a single unit. A pair is a specific case of a std::tuple with two elements.
Pair is used to combine together two values that may be of different data types. Pair provides a way to store two heterogeneous objects as a single unit. It is basically used if we want to store tuples.
std::make_pairCreates a std::pair object, deducing the target type from the types of arguments.
The default constructor of std::pair would value-initialize both elements of the pair, that means for pair<int, int> res; , its first and second would be initialized to 0 . That's the only way you can check for a default-constructed std::pair , if they're guaranteed to be non-zero after the assignment.
I think
is_constructible
is enough, why are we usingis_convertible
here?
The goal here is to properly handle explicit
construction. Consider just doing the former and trying to write a wrapper (using REQUIRES
here to hide whatever approach to SFINAE 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 that's all 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 don't want that last one to be okay - that breaks the expectation for Exp
!
Now, s_constructible<T, U&&>
is true if it's possible to direct-initialize a T
from 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 a U&&
. That is, if T copy() { return std::declval<U&&>(); }
is valid.
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 propagate explicitness, we need to use both traits together - and we can create meta-traits out of 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 are disjoint, so we can write two constructor templates that are definitely not both 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 is doing here - except instead of the two meta-traits they have all of the conditions written out explicit
ly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With