为什么 'is_convertible' 在 <utility> std::pair (STL) 中?
Why 'is_convertible' here in <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
}
utility VS 2017 文件第 206 行,
_Other1和_Other2是参数,这是std::pair的构造函数,
我们正在使用 Other1 和 Other2 来 初始化 "first" 和 "second",
我觉得is_constructible就够了,为什么我们这里要用is_convertible ?
顺便问一下,class = enable_if_t< ... ::value>
和 enable_if_t< ... ::value,int> = 0
有什么区别?
This constructor shall not participate in overload resolution unless is_constructible_v<first_type, U1&&>
is true and
is_constructible_v<second_type, U2&&>
is 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.
I think is_constructible
is enough, why are we using is_convertible
here?
这里的目标是正确处理 explicit
构造。考虑只做前者并尝试编写一个包装器(在这里使用 REQUIRES
来隐藏你想要的任何 SFINAE 方法):
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;
};
如果我们只有这些,那么:
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?!?
我们绝对不希望最后一个没问题 - 这超出了 Exp
的预期!
现在,如果可以从 U&&
直接初始化 T
,则 s_constructible<T, U&&>
为真 - 如果 T(std::declval<U&&>())
是有效表达式。
另一方面,is_convertible<U&&, T>
检查是否可以 copy-从 U&&
初始化 T
。也就是说,如果 T copy() { return std::declval<U&&>(); }
有效。
不同的是,如果转换为explicit
:
,则后者不起作用
+-----+--------------------------+------------------------+
| | is_constructible<T, int> | is_convertible<int, T> |
+-----+--------------------------+------------------------+
| Imp | true_type | true_type |
| Exp | true_type | false_type |
+-----+--------------------------+------------------------+
为了正确传播显性,我们需要同时使用这两个特征——我们可以从中创建元特征:
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>;
这两个特征是不相交的,因此我们可以编写两个绝对不可行的构造函数模板,其中一个构造函数是显式的,另一个不是:
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;
};
这给了我们想要的行为:
wrapper<Imp> wi = 0; // okay, calls non-explicit ctor
wrapper<Exp> we = 0; // error
wrapper<Exp> we2(0); // ok
这就是这里的实现方式——除了这两个元特征之外,它们已经 explicit
ly 写出了所有条件。
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 VS 2017 文件第 206 行,
_Other1和_Other2是参数,这是std::pair的构造函数,
我们正在使用 Other1 和 Other2 来 初始化 "first" 和 "second",
我觉得is_constructible就够了,为什么我们这里要用is_convertible ?
顺便问一下,class = enable_if_t< ... ::value>
和 enable_if_t< ... ::value,int> = 0
有什么区别?
This constructor shall not participate in overload resolution unless
is_constructible_v<first_type, U1&&>
is true andis_constructible_v<second_type, U2&&>
is true. The constructor is explicit if and only ifis_convertible_v<U1&&, first_type>
is false oris_convertible_v<U2&&, second_type>
is false.
I think
is_constructible
is enough, why are we usingis_convertible
here?
这里的目标是正确处理 explicit
构造。考虑只做前者并尝试编写一个包装器(在这里使用 REQUIRES
来隐藏你想要的任何 SFINAE 方法):
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;
};
如果我们只有这些,那么:
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?!?
我们绝对不希望最后一个没问题 - 这超出了 Exp
的预期!
现在,如果可以从 U&&
直接初始化 T
,则 s_constructible<T, U&&>
为真 - 如果 T(std::declval<U&&>())
是有效表达式。
is_convertible<U&&, T>
检查是否可以 copy-从 U&&
初始化 T
。也就是说,如果 T copy() { return std::declval<U&&>(); }
有效。
不同的是,如果转换为explicit
:
+-----+--------------------------+------------------------+
| | is_constructible<T, int> | is_convertible<int, T> |
+-----+--------------------------+------------------------+
| Imp | true_type | true_type |
| Exp | true_type | false_type |
+-----+--------------------------+------------------------+
为了正确传播显性,我们需要同时使用这两个特征——我们可以从中创建元特征:
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>;
这两个特征是不相交的,因此我们可以编写两个绝对不可行的构造函数模板,其中一个构造函数是显式的,另一个不是:
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;
};
这给了我们想要的行为:
wrapper<Imp> wi = 0; // okay, calls non-explicit ctor
wrapper<Exp> we = 0; // error
wrapper<Exp> we2(0); // ok
这就是这里的实现方式——除了这两个元特征之外,它们已经 explicit
ly 写出了所有条件。