C++11 SFINAE

@vrqq  September 23, 2021

时隔多日,终于弄懂如何运用。。。

序 c++ template

一个template的普通用法

template<typename T>
T inc(T in) { return T + 1; }

SFINAE

#include <iostream>
#include <type_traits>

//case 0
template<int X, typename = void, typename = void>
class CC {
public:
    int match0() { return 0; }
};

//case 1
template<int X, typename D>
class CC<X, D, std::enable_if_t<X==1>> {
public:
    int match1() { return 1; }
};

//case 1+
template<int X>
class CC<X, std::enable_if_t<X==1>, std::enable_if_t<X==1>> {
public:
    int match1() { return 11111; }
};

//case 2
template<int X>
class CC<X, std::enable_if_t<X==2>, std::enable_if_t<X==2>> {
public:
    int match2() { return 2; }
};


int main() {
    //only case 0 defined
    CC<0> i0;
    std::cout<<i0.match0()<<std::endl; //print 0

    //case0, case1 and case1+ valid, then case1+ selected (the most one)
    CC<1> i1;
    std::cout<<i1.match1()<<std::endl; //print 11111

    //case0 and case1 valid, then case2 selected
    CC<2> i2;
    std::cout<<i2.match2()<<std::endl; //print 2

    return 0;
}

先看CC<2> i2,他同时符合了case0 和 case2 两个class的定义,但对于case1是语法错误。
此时: 有语法错误时 只会跳过语法错误的class(当他不存在) 且不会报编译失败,即SFINAE
(Substitution failure is not an error)

那为什么对于CC<2> i2同时有case0和case1两个class生效,又没有冲突呢?
原因是:case0定义时指定了后两个默认值是void,即如果有多个生效时,通过默认值指定跳转

对于CC<1> i1,有case0, case1, case1+ 三个同时生效,为什么选择了case1+呢?
编译器会选择“最match默认值的(最specialization的)”,这里 case1+满足两个默认值,case1满足一个,故选择满足两个的。

注意:只有typename失败才符合SFINAE规则,而int/char/...等等 non-type parameter 出现语法错误时,不触发SFINAE。(不会跳过定义 直接报编译错误)

奇怪的用法1

把最平凡的class(上述case0),喂给代码提示用(如clangd / VSCode intelligence都支持)

常规的用法2 (从boost库抄的)

//A define to generate has member trait.
//Usage:
//    struct AA { void one(); };
//    HAS_MEMBER_FUNCTION(has_one, one);
//    static_assert(has_one<AA>::value && has_one_v<AA>);
//
#define HAS_MEMBER_FUNCTION(trait, name)                    \
template<typename T, typename Enable = void>                \
    struct trait : std::false_type {};                      \
template<typename T> struct trait<T, std::enable_if_t<      \
    std::is_member_function_pointer_v<decltype(&T::name)>   \
>> : std::true_type {};                                     \
template<typename T> inline constexpr bool trait##_v = trait<T>::value;
  • 如果需要sfinae关掉某个函数,则需要在"函数签名" 中需要包含template typename argument

参考文章:


添加新评论