在C++ type traits分析 这篇文章我,我们讲述了type traits的实现用法和基本原理。
本文我们讨论一下两个问题:
is_class
的type traits。SFINAE是"Substitution failure is not an error"(替换失败不是错误),官方给的解释为:在函数模板的重载决议中:为模板形参替换推导类型失败时,从重载集抛弃特化,而非导致编译错误。
用一句简单的话来说,C++在编译的时候,模板实例化的时候就算实例化失败也不会出现错误。
例如我们用一个简单的例子来描述:
template<typename T>
void test(typename T::noexist t)
{
}
void test(int t)
{
}
int main(int args, char* argv[])
{
test(100.1);
return 0;
}
我们在使用test(100.1)
实例化模板test的时候,并不会编译错误,只是会实例化失败,使用void test(int t)
这个函数。
所以SFINAE 的含义是:编译器在将函数模板形参替换成实参的过程中,如果针对某个函数模板产生了不合法的代码,其不会直接抛出错误信息,而是继续尝试去匹配其他可能的重载函数。
What… 这是什么鬼? 不是本来C++特性就是这样嘛?有必要讲的那么高大上嘛?不急,我们使用这个特性看一下SFINAE可以做什么事情。
namespace SFINAE
{
template<typename T>
class IsClass
{
private:
template<typename U>
static constexpr bool is_class(int U::*)
{
return true;
}
template<typename U>
static constexpr bool is_class(...)
{
return false;
}
public:
static constexpr bool value = is_class<T>(nullptr);
};
}
class A
{
};
class B
{
int a;
int b;
};
int main(int args, char* argv[])
{
std::cout << "A is class: " << SFINAE::IsClass<A>::value << std::endl;
std::cout << "B is class: " << SFINAE::IsClass<B>::value << std::endl;
std::cout << "int is class: " << SFINAE::IsClass<int>::value << std::endl;
std::cout << "double is class: " << SFINAE::IsClass<double>::value << std::endl;
return 0;
}
这里有两个技巧:
int U::*
使用这个来模板实例化,如果是类,可以定义类的成员指针,这个最佳匹配。is_class(...)
如果不是类,那么这个函数适配所有的类型。这个的运行结构为:
A is class: 1
B is class: 1
int is class: 0
double is class: 0
这个就是利用模板实例化的时候,如果第一个实例化不了,会继续实例化其他而不会出错的特性。
enable_if
解决的一个问题是:如何在函数编译期间根据特定的条件来选择启用或禁用模板实例化。加入我们存在一个Add加法操作,但是我们限定一个东西,就是Add只能使用整数,像std::string
不能适配,我们可以使用如下:
//整数
template<typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
auto Add(T a, T b) -> T
{
return a + b;
}
int main(int args, char* argv[])
{
std::string str = "abc";
Add(100, 200);
str + "abc";
Add(str, str);
return 0;
}
此时编译的时候就会出错:
: error C2672: 'Add': no matching overloaded function found
: error C2783: 'T Add(T,T)': could not deduce template argument for '__formal'
: note: see declaration of 'Add'
也就是说在模板实例化的时候并没有成功。enable_if
的主要作用就是当某个 condition 成立时,enable_if
可以提供某种类型。
我们看下enable_if
的实现原理。
template<bool _Test,
class _Ty = void>
struct enable_if
{ // type is undefined for assumed !_Test
};
template<class _Ty>
struct enable_if<true, _Ty>
{ // type is _Ty for _Test
using type = _Ty;
};
也就是说,默认情况下enable_if
是一个空的类定义,如果bool _Test
参数为TRUE的话,使用片特例模板struct enable_if
,这个模板可以定义type
类型。例如:
class A
{
public:
int a;
};
int main(int args, char* argv[])
{
std::enable_if<std::is_class<A>::value, A>::type a;
a.a = 100;
return 0;
}
C++库还定义了如下类型:
template<bool _Test,
class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;
类似的东西在C++标准库中使用很多,例如std::vector
,如下:
template<class _Iter,
class = enable_if_t<_Is_iterator_v<_Iter>>>
vector(_Iter _First, _Iter _Last, const _Alloc& _Al = _Alloc())
: _Mybase(_Al)
{ // construct from [_First, _Last) with optional allocator
_Adl_verify_range(_First, _Last);
_Range_construct_or_tidy(_Get_unwrapped(_First), _Get_unwrapped(_Last), _Iter_cat_t<_Iter>{});
}