泛型与偏特化的推导关系:从enable_if<true>来看偏特化,未指明的第二个参数到底是什么,以及怎么来的?

先看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: 没有可用于执行该转换的用户定义的转换运算符,或者无法调用该运算符

大拿的解释

C++ Template的选择特化 - 知乎

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,然后以这个版本为基调,去检查其他版本。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值