时隔多日,终于弄懂如何运用。。。
序 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