在C++模板中,设置一个无名模板参数的默认值为0到底是什么含义
看下面的例子
#include<type_traits>
// define a class template, and the template parameter is a value of bool。by default,this class is empty, doesn't define any member
template<bool Condition>
struct lfEnableIf
{ };
// specialize the above class template, only when the value is true, the class has a defined type member--Type
template<>
struct lfEnableIf<true>
{typedef int Type;
};// define a class template which has a static constant value, true/false
template<bool Val>
struct lfBoolConstant
{static const bool Value = Val;
};//定义两个特化后的模板类,其中一个具有静态常量 value=true, 另一个具有静态常量value=false
typedef lfBoolConstant<true> lfTrueType;
typedef lfBoolConstant<false> lfFalseType;//定义一个模板类lfIsArithmetic,默认含有一个静态常量value=false
template <typename NumT> struct lfIsArithmetic : lfFalseType{};// 特化上面的模板类,使其在参数类型为char,bool,float时,特化模板类含有一个静态常量value=true
template <> struct lfIsArithmetic<char> : lfTrueType{};
template <> struct lfIsArithmetic<bool> : lfTrueType{};
template <> struct lfIsArithmetic<float> : lfTrueType{};// 最后的应用,使用上述定义的各个模板类,定义一个新的模板类,这个新的模板类含有两个模板参数,其中第一个是个类型参数,第二个是个数值型参数
template<typename T, typename lfEnableIf<lfIsArithmetic<T>::Value>::Type = 0> // v or no v is same,nameless parameter SFINAE
struct Test
{static void print(){std::cout << "参数类型为:" <<typeid(T).name()<< std::endl;}};int main()
{Test<float>::print();// template<float, float = 0> struct Test;Test<bool>::print();// template<bool, bool = 0> struct Test;Test<char>::print();// template<char, char = 0> struct Test;// Test<int>::print(); // error, as fEnableIf<lfIsArithmetic<int>::Value>::Type doesn't exist at allreturn 0;
}
执行结果为:
让我们来看这一行,template<typename T, typename lfEnableIf<lfIsArithmetic<T>::Value>::Type = 0> struct Test的第二个模板参数,设置一个无名的模板参数的默认值为0到底是什么含义?
这一行其实定义一个有名的模板类型参数T和一个无名的模板数值型参数lfEnableIf<lfIsArithmetic<T>::Value>::Type,这第二个参数等价于template<int n=0> struct S{};
在lfEnableIf条件满足(lfIsArithmetic<T>::Value=true)的情况下,类型 lfEnableIf<lfIsArithmetic<T>::Value>::Type最终会被解析为类型int,于是上述那一堆就等价于
template<float, int = 0> struct Test;
template<bool, int = 0> struct Test;
template<char, int = 0> struct Test;
由于第二个模板参数的类型是一个在lfEnableIf范围内定义的一个内嵌类型,因此前面必须有typename 来告诉编译器,lfEnableIf<lfIsArithmetic<T>::Value>::Type是一个类型,而不是一个其它什么东西(比如不是类中的静态变量什么的)。同时这第二个模板参数是无名的,但是你可以给这个参数定义任何名字,这不会改变任何东西。比如template<typename T, typename lfEnableIf<lfIsArithmetic<T>::Value>::Type V = 0>
在上面的例子中,我给了这个无名参数一个名字V,这个参数名字在模板中任何地方都没有使用到,这正是我们没必要给一个确切名字的原因。这是一个虚参数,这也正是我们可以给这个虚参数赋予任何值的原因。我们可以设置默认值为0,也可以设置为42,这不会改变任何事情
于是在这种情况下,这两个关键字typename 就会带来一个误导:那就是你的这个模板中的第一个和第二个typename是类似的,其实不然,这两个typename 其实发挥的作用是完全不同的。第一个参数中的typename表示我定义一个模板参数类型T,这种情况下,typename 可以被class代替。而第二个typename, typename lfEnableIf<lfIsArithmetic<T>::Value>::Type = 0,则发挥一个不同的作用。typename仅仅用来告知编译器它后面的表达式是一个类型, 于是第二个参数就成为一个数值型模板参数(因为数值型模板参数不需要typename),这种情况下,typename不能被class替换
那么这个无名的模板参数默认值为0到底如何使用呢?那就是使用SFINAE原则,看typename lfEnableIf<lfIsArithmetic<T>::Value>::Type = 0能否被正确解析,如果能被正确解析,那么Test结构就存在,Test结构就可以被使用。
比如T使用char时,lfIsArithmetic<char>就含有value=true的成员变量。于是lfEnableIf<true>就含有一个int型的类型Type,于是typename lfEnableIf<lfIsArithmetic<T>::Value>::Type就等价为int,于是Test<char> 就存在,接可以被使用。
如果T为int时,由于lfIsArithmetic<int>只含有value=false的成员变量,于是lfEnableIf<fasle>就不会定义type成员变量,于是解析lfEnableIf<lfIsArithmetic<T>::Value>::Type就报错,于是Test<int> 就不存在,就不能使用