大后天就是除夕了,牛年将过,虎年马上来了。以一篇关于C++的非常小众的文章作为牛年的结尾,若有讲的不清楚或讲错的地方欢迎大家留言指出来。为什么今年最后一篇文章是关于c++的呢?因为自己工作后用过的第一门语言便是c++,感谢。
SFINAE简介
SFINAE是Substitution Failure is NOT an Error 的首字母缩写,David Vandevoorde最先使用这个缩写来描述相关编程技术,可翻译为替换失败并非错误 。
维基百科中对此有讲解,举的例子也不错,可点击这里查看,本文关于SFINAE的例子来源于此百科。可是,今天有朋友反应看不太懂这个例子了,相信很多没有使用过c++11或者更高版本的朋友多少都有此种困惑,这里我们简单解说一下。
言归正传,当创建一个重载函数的候选集时,某些(或全部)候选函数是用模板实参替换(可能的推导)模板形参的模板实例化结果。如果某个模板的实参替换时失败,编译器将在候选集中删除该模板,而不是当作一个编译错误从而中断编译过程,这需要C++语言标准授予如此处理的许可。如果一个或多个候选保留下来,那么函数重载的解析就是成功的,函数调用也是良好的。
SFINAE的一个例子
通过一个简单例子了解一下SFINAE,
struct Test {
typedef int foo;
};
template <typename T>
void f(typename T::foo) {}
template <typename T>
void f(T) {}
int main() {
f<Test>(10);
f<int>(10);
}
在限定名字解析时(T::foo) 使用非类的数据类型,导致f<int> 推导失败,因为int 并无嵌套数据类型foo , 但程序仍是良好定义的,因为候选函数集中还有一个有效的函数(Defination #2 )!
虽然SFINAE 最初引入时是用于避免在不相关模板声明可见时(如通过包含头文件)产生不良程序。许多有心的程序员后来发现这种行为可用于编译时内省introspection 。具体说来,在模板实例化时允许模板确定模板参数的特定性质。
例如,SFINAE用于确定一个类型是否包含特定typedef ,
#include <iostream>
template <typename T>
struct has_typedef_foobar {
typedef char yes[1];
typedef char no[2];
template <typename C>
static yes& test(typename C::foobar*);
template <typename>
static no& test(...);
static const bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
};
struct foo {
typedef float foobar;
};
int main() {
std::cout << std::boolalpha;
std::cout << has_typedef_foobar<int>::value << std::endl;
std::cout << has_typedef_foobar<foo>::value << std::endl;
}
当类型T 有嵌套类型foobar ,test 的第一个定义被实例化并且空指针常量被作为参数传入。(结果类型是yes 。)如果不能匹配嵌套类型foobar ,唯一可用函数是第二个test 定义,且表达式的结果类型为no 。省略号(ellipsis )不仅用于接收任何类型,它的转换的优先级是最低的,因而优先匹配第一个定义,这去除了二义性。
使用C++11进行代码简化
在 C++11 中,上述代码可以简化为:
#include <iostream>
#include <type_traits>
template <typename... Ts>
using void_t = void;
template <typename T, typename = void>
struct has_typedef_foobar : std::false_type {};
template <typename T>
struct has_typedef_foobar<T, void_t<typename T::foobar>> : std::true_type {};
struct foo {
using foobar = float;
};
int main() {
std::cout << std::boolalpha;
std::cout << has_typedef_foobar<int>::value << std::endl;
std::cout << has_typedef_foobar<foo>::value << std::endl;
}
对于c++11代码的解说
- 要看懂上边的代码,首先要知道的第一点是
std::false_type 和std::true_type 都是结构体,这两个结构体有bool 型的成员变量,值分别为false 和true ,以下是其可能的实现。
typedef integral_constant<bool, false> false_type;
typedef integral_constant<bool, true> true_type;
template<class T, T v>
struct integral_constant {
static constexpr T value = v;
using value_type = T;
using type = integral_constant;
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; }
};
- 第二点就是在c++中结构体也是可以继承的,所以
has_typedef_foobar 完全继承false_type 或者true_type ,此外什么也没做。 - 为了便于理解,我们把上边的代码在编译器看来会是什么样子,写在下边
#include <iostream>
#include <type_traits>
template <typename... Ts>
using void_t = void;
template <typename T, typename = void>
struct has_typedef_foobar : std::false_type {};
template<>
struct has_typedef_foobar<int, void> : public std::integral_constant<bool, false>
{
};
template<>
struct has_typedef_foobar<foo, void> : public std::integral_constant<bool, true>
{
};
template<typename T>
struct has_typedef_foobar<T, void_t<typename T::foobar> > : public std::integral_constant<bool, true>
{
};
struct foo
{
using foobar = float;
};
int main()
{
std::cout.operator<<(std::boolalpha);
std::cout.operator<<(std::integral_constant<bool, false>::value).operator<<(std::endl);
std::cout.operator<<(std::integral_constant<bool, true>::value).operator<<(std::endl);
}
|