std库中的一个SFINAE实例

GCC std库实现中的一个SFINAE实例

Part 1. 源代码

std::tupleswap函数上有这样一个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,T2Tn的类成员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::valuefalse,那么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

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
隐藏
变装