You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SFINAE是C++里面的术语,确切名称叫做Substitution failure is not an error 。其实准确的来说还应该加入一个前缀template,其意义从其名称中就可以看出来:模板参数替代失败并非错误。SFINAE是一种编程技巧,利用了函数模板的重载决议(overload resolution):当有多个同名模板函数可用时,单独的一个模板函数匹配失败并不意味着错误,只有当所有的匹配都失败的时候才是错误。只用文字描述的话,SFINAE不是很好理解,后面将给出具体事例及代码说明。
下面给出利用SFINAE实现编译期内部类型判断的最简单的例子:
#include<iostream>usingnamespacestd;structTest{typedefintfoo;};template<typenameT>voidf(typenameT::fooa){cout<<"with T::foo"<<endl;}// Definition #1template<typenameT>voidf(Ta){cout<<"without T::foo"<<endl;}// Definition #2intmain(){f<Test>(10);// Call #1. cout with T::foof<int>(10);// Call #2. cout without T::foo Without error (even though there is no int::foo) thanks to SFINAE.}
#include<iostream>template<typenameT>structhas_typedef_foobar{// Types "yes" and "no" are guaranteed to have different sizes,// specifically sizeof(yes) == 1 and sizeof(no) == 2.typedefcharyes[1];typedefcharno[10];template<typenameC>staticyes&test(typenameC::foobar*);template<typename>staticno&test(...);// If the "sizeof" of the result of calling test<T>(0) would be equal to sizeof(yes),// the first overload worked and T has a nested type named foobar.staticconstboolvalue=sizeof(test<T>(0))==sizeof(yes);};structfoo{typedeffloatfoobar;};intmain(){std::cout<<std::boolalpha;std::cout<<has_typedef_foobar<int>::value<<std::endl;std::cout<<has_typedef_foobar<foo>::value<<std::endl;}
#include<type_traits>#include<utility>template<typenameT,typename=void>structhas_f:std::false_type{};template<typenameT>structhas_f<T,decltype(std::declval<T>().f(),void())>:std::true_type{};template<typenameT,typename=typenamestd::enable_if<has_f<T>::value>::type>structA{};structB{voidf();};structC{};templateclassA<B>;// compilestemplateclassA<C>;// error: no type named ‘type’ // in ‘struct std::enable_if<false, void>’
printf("%d\n",is_pointer<IntPtr>::value);// prints 1 printf("%d\n",is_pointer<FooMemberPtr>::value);// prints 1 printf("%d\n",is_pointer<FuncPtr>::value);// prints 1 return0; }
上面的代码中,_sum<N>是一个模板元函数,A<N>是一个封装了N个整数的结构体。_sum<N>非常巧妙的利用了int与bool的隐式类型转换,以及std::enable_if的特性,还有类型别名机制,实现了递归求前N个元素和的壮举。同时,A(A<otherN> const)也通过利用std::enable_if实现了把拷贝构造中长度控制的奇迹。看到这两个简短而又不知所云的代码是不是感觉有点怕!当SFINAE和TYPETRIATS一起结合的时候,the real terror awaits you。
</div>
via spiritsaway.info https://ift.tt/xtvPQyO
November 13, 2024 at 10:06AM
The text was updated successfully, but these errors were encountered:
CPP SFINAE - spirits away
https://ift.tt/W01Bc2l
SFINAE是C++里面的术语,确切名称叫做
Substitution failure is not an error
。其实准确的来说还应该加入一个前缀template
,其意义从其名称中就可以看出来:模板参数替代失败并非错误。SFINAE是一种编程技巧,利用了函数模板的重载决议(overload resolution
):当有多个同名模板函数可用时,单独的一个模板函数匹配失败并不意味着错误,只有当所有的匹配都失败的时候才是错误。只用文字描述的话,SFINAE不是很好理解,后面将给出具体事例及代码说明。下面给出利用SFINAE实现编译期内部类型判断的最简单的例子:
模板函数
f
有两个定义,第一个定义要求类型struct Test
中定义了一个类型foo
,第二个定义则对参数类型不做任何要求。所以当我们显示实例化f<Test>
时,第一个定义匹配成功,调用第一个定义;而当显示实例化f<int>
时,第一个定义匹配失败,尝试匹配第二个定义,并最终调用第二个定义。事实上,这两个定义的先后顺序并不影响最后的结果。上面的代码的主要功能就是通过SFINAE,判断模板参数的类型是否内部定义了一个foo
类型。类似的,我们可以通过下面的代码来判断传入参数类型是否内部定义了foobar
类型:如果类型
T
内部定义了类型foobar
,则has_typedef_foobar<int>::value
的值为true
,否则为false
。这里之所以将no
定义为10个char
,是因为在不同的平台上可能有不同的内存对齐要求。如果定义为3个的话,遇到4字节对齐的硬件结构会导致sizeof(yes)
与sizeof(no)
都返回4,在8字节对齐的机器上也是同理。目前来说还没见过大于8字节的对齐(当然自己犯贱#pragma_pack(16)的除外),所以定义为10基本可以保证这两个类型返回的字节大小是不一样的。SFINAE的功能不仅仅是能编译期确定类型内部是否定义了新类型,而且还能在编译期确定实参类型内部是否定义了某个成员函数,样例代码如下(注意 VS编译不过。。。):
首先来理解一下
std::declval
,这是在C++11中引入的一个函数模板,具体定义如下:其具体作用就是返回任意类型
T
的一个右值引用,即使该类型不存在构造函数。但是该右值引用不可以用来求值,只能用在不可求值环境,只能用来推导类型,如其成员变量和成员函数的类型。而decltype(std::declval<T>().f(), void())
的作用就是强制进行std::declval<T>().f()
的类型推导,并最后返回void
类型。std::false_type,std::true_type
内部都有一个成员value
,类似于我们上个事例代码中的has_typedef_foobar<int>::value
,值分别为false,true
。而std::enable_if
是一个类模板,并显示特化为了true,false
两种类型。std::enable_if<true>
内部定义了一个新类型type
,而std::enable_if<false>
内部则没有定义这个类型。std::enable_if
具体定义代码如下所示:在这一系列的模板特化和实例化之下,语句
A<B>
能够编译通过,而语句A(C)
则编译报错。当前我们只做到了判断没有参数也没有返回值的函数的存在性,事实上还可以判断具体签名的成员函数的存在性。假设我们要判断某个类内部是否定义了
size_t used_memory() const
这个函数,该需求可以通过下面的代码来实现:这里用到了成员指针
::*
这个类型,因为普通指针无法指向成员函数(多了一个this指针的参数),具体细节读者自行百度吧。由于个人能力所限,还无法得到有参数的成员函数的存在性判断,但是现在最起码得到了有返回值类型的成员函数的存在性判断,先偷着乐一会吧。对于一个变量,我们还可以利用SFINAE来判断该变量是否是指针类型的:包括普通变量指针,成员变量指针,成员函数指针,函数指针这四种指针类型。
type_traits
中的is_pointer
元函数可以做到这一功能,代码如下:上面的代码通过显示特化了所有的指针类型并让函数模板返回值为
char
,同时让非指针类型的函数模板特化为返回double
。这两个返回类型在sizeof
运算符下返回的值是不同的,所以我们可以把指针类型与非指针类型区分出来。除了上面提到的类内部类型和类函数存在性判断之外,我们还可以判断某个类
A
是否是从B
继承下来的。事例代码如下:static_cast<D*>(0)
这一句的作用是将0转换为一个A
类型的指针。由于Test(B*)
接受的是B*
类型的参数,所以会默认进行A*
到B*
的转换,如果可以转换,则匹配第一个模板。如果A*
没有到B*
的转换,则Test(B*)
这个模板匹配失败,并最终匹配了第二个模板。剩下的工作就好理解了,这里就不再赘述。需要注意的是,这里并不是去做真正的类型转换,而且,只有enum
和const static
类型的值才能在编译期动态赋值。上面的代码只能实现可转换的判断,如果
A
与B
的类型相同的时候,会返回1。如果我们要判断的是A
是否是B
的继承类型的时候,该结果并不令人满意。所以我们还需要增加一个代码片段,来排除相同类型的情况。代码示例如下:上面的两个代码合并起来,就能完美的判断是否是继承类型的问题。
我们也可以利用SFINAE来判断一个类是不是纯虚类。此时我们使用到了纯虚类的一个性质:不能声明纯虚类的变量。所以对于纯虚类
A
来说,A(*)[1]
是无法实例化的。因为这个类型是一个指向A
类型的数组的指针(注意不是A
指针的数组),而数组声明要求类型不能为reference,void,function,abstract。所以我们可以用以下的代码测试类型是不是纯虚类:但是,正如前文所说,reference,void,function都会导致类型被判断为纯虚类,所以应该再对这几种类型做特化,这里就不写了。
虽然之前的两个例子让我们感到了模板与SFINAE的强大与恐怖之处,但是得到了判断结果又能怎么样呢,好像都没有多大实际作用的样子。这里,我们给出一个有实际作用的例子:利用SFINAE实现类模板特化。
假设我们当前要实现一个容器
C<T>
,用来存储T
类型的值。一个最简单的实现如下:由于有些类型的
T
是不可拷贝的,例如std::mutex,std::unique_ptr
。但是对于这些类型的T
,is_copy_constructible<C<T>\,>::value
仍然是true
的。为了防止这些类型的拷贝构造,我们要把C<T>
的拷贝构造函数禁用。代码如下:因为默认的拷贝构造函数会首先调用父类的拷贝构造函数。当父类的拷贝构造函数为
delete
的时候,子类的拷贝构造函数也就相当于声明为了delete
,这个解决方案简直完美。模板参数既能决定类的行为,同时也能决定函数的行为。如下例:
上面的代码中,
_sum<N>
是一个模板元函数,A<N>
是一个封装了N个整数的结构体。_sum<N>
非常巧妙的利用了int
与bool
的隐式类型转换,以及std::enable_if
的特性,还有类型别名机制,实现了递归求前N个元素和的壮举。同时,A(A<otherN> const)
也通过利用std::enable_if
实现了把拷贝构造中长度控制的奇迹。看到这两个简短而又不知所云的代码是不是感觉有点怕!当SFINAE和TYPETRIATS一起结合的时候,the real terror awaits you
。via spiritsaway.info https://ift.tt/xtvPQyO
November 13, 2024 at 10:06AM
The text was updated successfully, but these errors were encountered: