GCC std库实现中的一个SFINAE实例
Part 1. 源代码
在std::tuple
的swap
函数上有这样一个noexcept
声明:
noexcept(__and_<__is_nothrow_swappable<_Elements>...>::value)
作用是检测tuple
内的每一个元素是否是不抛出可交换的,这里的
这里有一个类模板__and__<T...>
,用来对__is_nothrow_swappable<T>
的每个value
进行相互之间与运算,它的实现如下:
template<typename... _Bn>
struct __and_
: decltype(__detail::__and_fn<_Bn...>(0))
{ };
这个__and__<T...>
依据函数__and_fn<_Bn...>(0)
调用的返回值类型来决定继承类的类型。
而__and_fn<_Bn...>(0)
的实现是这样的:
template<typename... _Bn>
auto __and_fn(int) -> __first_t<true_type,
__enable_if_t<bool(_Bn::value)>...>;
template<typename... _Bn>
auto __and_fn(...) -> false_type;
其中__first_t<T, ...>
等价于T
,也就是说,这个__and_fn
函数的返回类型要么是std::true_type
,要么是std::false_type
。
看到这里你可能感到疑惑:__and_fn<_Bn...>(0)
不是会选择重载__and_fn(int)
吗?__and__
会始终继承std::true_type
吧;又或者,你甚至一眼难以看出这个函数是怎么对Bn...
中的每个类型进行检测的。
Part 2. 另一个简单等效的实现
按照比较容易的想法,我们要对类型T1
,T2
…Tn
的类成员value
进行与运算,可以这样简单地实现:
template<typename ...T>
struct and_impl
{
constexpr static bool value = (T::value && ...)
};
但是这样会产生一个问题:如果类型T
不含value
,导致编译失败怎么办?
如果加requires
,最多是让提示信息更加友好,仍然会产生编译失败的结果,并且,我们使用这个and
的场合,并不总能保证用户输入预期的类型,但我们依然希望程序正常编译该如何解决?
当然,你可以为不同的类型T
其添加特化,指定某个特化下的该类型应该以哪一个值参与运算:
template<typename T>
struct op_and_value
{
constexpr static bool value = T::value;
};
template<>
struct op_and_value<void>
{
constexpr static bool value = false;
};
template<typename ...T>
struct and_impl
{
constexpr static bool value = (op_and_value<T>::value && ...)
};
但是这样做的话会有大量特化代码,写起来繁琐复杂,不可行。
此时,就需要我们用到SFINAE
Part 3. 标准库的实现逻辑
首先我们先回答一个问题:__and_fn<_Bn...>(0)
会始终选择重载__and_fn(int)
吗?
答案是否定的,关键就在__first_t<true_type, __enable_if_t<bool(_Bn::value)>...>
中的__enable_if_t
(=std::enable_if
)这里。
传入__enable_if_t
第一个参数是_Bn::value
,并将其转换为bool
类型,第二个参数Tp
不传入,使用缺省的void
。对于bool(_Bn::value)
的值,有以下几种情况:
- 如果其中一个
_Bn::value
为false
,那么enable_if::type
不存在,这里就会发生错误,即使用enable_if<false, void>
替换enable_if<_Cond, Tp>
失败,发生SFINAE,从重载集中丢弃,此时__and_fn<_Bn...>(0)
只会匹配到__and_fn(...)
,此时函数调用的返回值类型为false_type
- 如果每个
_Bn::value
均为true
,则每一个enable_if
都被替换成功,由于从__add_fn(0)
的调用优先匹配__add_fn(int)
,此时产生的函数返回值类型为__first_t<true_type, void, void, ...>
,即true_type
- 如果任意一个
_Bn::value
不存在,例如_Bn=void
,则使用void
替换_Bn
失败,仍然产生SFINAE,从重载集中丢弃__add_fn(int)
这样,不管类型T
是什么类型,只要这个类型含有value
,那么就能正常参与逻辑与运算了;一旦不含value
,也能编译通过,但逻辑与运算的结果始终为false
。