先看enable_if的定义:
// STRUCT TEMPLATE 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;
};
std::enable_if<true>::type t1; 的编译提示是:
按照直觉,std::enable_if<true>匹配的应该是偏特化版本啊,毕竟只给了一个参数,而且还是个true,这个true按直觉应该是实例化偏特化版本的,所以结果应该是enable_if<true,true>。但结果显然不是。
说明:1、type是void,2、实例化的是偏特化的版本
std::enable_if<true>::type* t1; 即可编译通过。
通过下面代码,说明实例化的确实是void。
if (1 == std::is_void<std::enable_if<true>::type>::value)
{
int i = 0;
i++;
}
由于编译提示里说到void,如果实例化的template<bool _Test, class _Ty = void>,那么它应该是没有type这个定义的。
说明实例化的是特化版本?否则怎么会有type。看下面的分析。
template<class _Ty>
struct enable_if<true, _Ty>
{ // type is _Ty for _Test
using type = _Ty;
};
再验证一下,自己的enable_if版本:
写个自己的enable_if,只有泛化的声明(没有实现),然后提供一个偏特化的版本。
template<bool _Test, class _Ty = void>
struct enable_if123;
template<class _Ty>
struct enable_if123<true, _Ty>
{ // type is _Ty for _Test
using type = _Ty;
};
int main()
{
if (1 == std::is_void<std::enable_if<true>::type>::value)
{
int i = 0;
i++;
}
enable_if123<true>::type t1;
}
enable_if123<true>::type t1;的编译错误是:
跟上面一样,说明推导出的是void,因为这个模板只有一个特化版本,没有别的。
而enable_if123<false>::type t1;的编译错误是:
也就是说找到了泛化版本,并且第二个参数也按照默认值void。但泛化版本没有定义(只有声明)。
但是注意了,这里说的是enable_if<false,void>,第二个参数又赋上了void。
试着再研究下:
注意泛化版本里的第二个参数的默认类型改成了double。从编译提示可以看出,在偏特化没给出第二个类型时,编译器采用的是泛化版本里的默认参数类型double。
template<bool _Test, class _Ty = double>
struct enable_if123;
template<class _Ty>
struct enable_if123<true, _Ty>
{ // type is _Ty for _Test
using type = _Ty;
};
struct XXX
{
static bool no_destroy()
{
return true;
}
};
int main()
{
XXX o1;
enable_if123<true>::type t1 = o1; //故意赋值一个对象,看看打印出什么提示
}
编译提示:
error C2440: “初始化”: 无法从“XXX”转换为“double”
note: 没有可用于执行该转换的用户定义的转换运算符,或者无法调用该运算符
大拿的解释
class B {};
class D: public B {};
template <typename T, typename U, bool Enabled = std::is_base_of<T, U>::value>
class Add; // 模板的泛化形式(原型)
template <typename T, typename U>
struct Add<T, U, true>
{
// Blah blah blah
};
template <typename T, typename U>
struct Add<T, U, false>
{
// Blah blah blah
};
Add<B, D> a; // 用上面那个特化
Add<int, float> b; // 用下面那个特化
这个方案之所以能工作的原因是,Add<B, D> 首先会找原型去匹配,
匹配的时候发现,诶,有个Default Parameter,然后它就会把
Default Parameter算好后,得到所有的参数,
通过模式匹配的方法,去找一个对的上的偏特化
(Partial Specialized)实现。
也就是说,对于Add<B,D>,先去将泛化版本给Substitution,变成Add<T,U,true>(但此时泛型版本只有声明,没有定义),然后进行匹配,根据优先级,偏特化版本比泛化版本要高,所以先在偏特化版本里去找,最后找到了合适偏特化版本。
所以呢,泛化版本是必要的,即使没有实现。它用于先生成一个Add<T,U,true>。
如果注释掉泛化版本,只剩下偏特化版本,那会会有编译错误出现:
带using type = _Ty;的enable_if就是泛化enable_if的偏特化版本。结语
1。首先,偏特化是需要泛化版本的,即使这个泛化版本只有声明,没有实现。就是
template <typename T, typename U, bool Enabled = std::is_base_of<T, U>::value>
class Add; // 模板的泛化形式(原型)
如果不提供泛化版本,编译就会失败。从侧面也证实了它的存在的必要性(起码是从编译器的角度它是必须的)。
2。对于上面的enable_if<true>,由于enable_if是两个模板参数的类,所以编译器首先要把参数给补全,好了,这里有默认的参数类型,可以是具体的类型,可以是推导出来的(enable_if是直接给出的void,Add类是推导出来的,是bool Enabled = std::is_base_of<T, U>::value)。编译器先把泛化版本的声明给“实例化”,补全成enable_if<true, double>,然后偏特化中去匹配,匹配上了enable_if<true, _Ty>这个偏特化版本,而且没啥毛病,也没有二义性。所以就它了。
怎么匹配的?第一个:它是偏特化,优先级由于泛型,然后它里面有个true,肯定比泛化更匹配。所以把偏特化的第二个参数_Ty就赋予double
---------------------------------------------------------------------------------------------------------------
所以,std::enable_if<true>在实例化的时候,先根据泛化版本(这个是根,规定了模板参数的个数)“实例化”出声明:std::enable_if<true,void>,
由于有偏特化版本,因为偏特化版本的优先级比较高,然后试图在各个偏特化中去找更匹配的版本,遇到偏特化版:std::enable_if<true,_Ty>,_Ty没有给出,但和std::enable_if<true,void>可以匹配上,并且没有二义性,没啥毛病,所以最后的匹配结果是这个优先级比较高的偏特化版本。
此处_Ty还可以是一个推导的形式,比如_Ty=std::enable_if<std::is_same<double,double>::value,int>::type,它的推导结果是类型int,也就是说这个偏特化其实是std::enable_if<true,int>,那么这个偏特化的版本就匹配不上enable_if<true,void>,它会被编译器从候选者中删除,所以最后谁也都匹配不上,最后报编译错误。
并且参数是:std::enable_if<true,void>,所以:
1、尽力尝试匹配优先级比较高的偏特化的版本
2、_Ty虽然没有提供,但是有默认参数(比如void)或者默认的推导(比如bool Enabled = std::is_base_of<T, U>::value),在泛化版本中有定义
3、匹配成功
它适合这套规则:先找泛化原型去匹配,然后再去匹配偏特化版本。
下面这个例子,可以展示偏特化版本的优先级:
template<bool _Test,
class _Ty = double>
struct enable_if123
{
using type = wchar_t;
};
template<class _Ty>
struct enable_if123<true, _Ty>
{ // type is _Ty for _Test
using type = _Ty;
};
struct XXX
{
static bool no_destroy()
{
return true;
}
};
int main()
{
XXX o1;
enable_if123<true>::type t1 = o1; //故意赋值一个对象,看看打印出什么提示
}
编译错误提示为:error C2440: “初始化”: 无法从“XXX”转换为“double”
注意,泛化版本也有实现,里面是wchar_t,但匹配的版本是偏特化里的double,而不是wchar_t
要是泛化版本不提供默认参数呢:
凭着本能理解,应该按照偏特化版本匹配成std::enable_if123<true,true>,但其实并不是。还是先“实现”(或者成功替换)泛化版本,然后依次去偏特化里寻找。而不是直接去偏特化里去匹配。然后编译器发现参数个数不够,本来需要两个,但现在只有一个,也没有提供默认参数,所以直接就报错了,不再继续。
而且,必须要提供泛化版本,即使没有实现。
template<bool _Test,
class _Ty/* = double*/>
struct enable_if123
{
using type = wchar_t;
};
template<class _Ty>
struct enable_if123<true, _Ty>
{ // type is _Ty for _Test
using type = _Ty;
};
struct XXX
{
static bool no_destroy()
{
return true;
}
};
int main111()
{
XXX o1;
enable_if123<true>::type t1 = o1; //故意赋值一个对象,看看打印出什么提示
}
编译错误提示为:
error C2976: “enable_if123”: 模板 参数太少
note: 参见“enable_if123”的声明
error C2955: “enable_if123”: 使用 类 模板 需要 模板 参数列表
note: 参见“enable_if123”的声明
error C2440: “初始化”: 无法从“XXX”转换为“enable_if123<_Test,_Ty>::type”
note: 没有可用于执行该转换的用户定义的转换运算符,或者无法调用该运算符
1、要有泛化版本,不管有没有实现
2、不管有没有偏特化,编译器要把每个版本都Substitution之后再确定具体选谁。谁更合适。特化最合适,然后是偏特化,然后是泛化版本
3、首先对泛化版本进行Substitution,然后以这个版本为基调,去检查其他版本。