c++ - C++ 如何将模板参数从类型转换为非类型使SFINAE工作?

  显示原文与译文双语对照的内容

从关于 std::enable_ifcppreference.com 文章中,

注释
一个常见的错误是声明两个仅在默认模板参数上有所不同的函数模板。 这是非法的,因为默认模板参数不是函数模板的签名的一部分,并且声明具有相同签名的两个不同的函数模板是非法的。


/*** WRONG ***/



struct T {


 enum { int_t,float_t } m_type;


 template <


 typename Integer,


 typename = std::enable_if_t<std::is_integral<Integer>::value>


> 


 T(Integer) : m_type(int_t) {}



 template <


 typename Floating,


 typename = std::enable_if_t<std::is_floating_point<Floating>::value>


> 


 T(Floating) : m_type(float_t) {}//error: cannot overload


};



/* RIGHT */



struct T {


 enum { int_t,float_t } m_type;


 template <


 typename Integer,


 typename std::enable_if_t<std::is_integral<Integer>::value, int> = 0


> 


 T(Integer) : m_type(int_t) {}



 template <


 typename Floating,


 typename std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0


> 


T(Floating) : m_type(float_t) {}//OK


};



*** WRONG *** 版本在 *** RIGHT*** 版本中进行编译时,我很难把头包围起来。 对我的解释和例子是对我的。 上所做的就是将类型模板参数更改为非类型模板参数。 对我来说,两个版本都应该是有效的,因为两者都依赖于 std::enable_if<boolean_expression,T> 具有名为 type的typedef成员,并且 std::enable_if<false,T> 没有这样的成员。 替换失败( 这不是一个错误) 应该导致两个版本。

查看标准,它表示在[temp.deduct]中,当引用函数模板特化时,所有模板参数都应具有值",如果是模板参数,则后面应为"如果没有推导出其相应的模板参数,并且其相应的模板参数具有默认参数,则通过将为先前模板参数确定的模板参数替换为默认参数来确定模板参数。 如果替换结果中的替换结果无效,如上面描述的,类型推导失败。 "这类演绎失败并不一定是一个错误,SFINAE就是这么说的。

为什么在 *** WRONG *** 版本中将typename模板参数更改为非typename参数使 *** RIGHT *** 版本正确"成为可能?

时间:

重新组织cppreference引用,在错误的情况下,我们有:


 typename = std::enable_if_t<std::is_integral<Integer>::value>


 typename = std::enable_if_t<std::is_floating_point<Floating>::value>



它们都是默认模板参数,并且不是模板的函数签名部分。 因此在错误的情况下,你会得到两个相同的签名。

在正确的情况下:


typename std::enable_if_t<std::is_integral<Integer>::value, int> = 0




typename std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0



你没有默认的模板参数,但是两个不同类型的,它的默认值为( =0 ) 。 因此签名是不同

为了清楚的区别

带有默认类型的模板参数的示例:


template<typename T=int>


void foo() {};



//usage


foo<double>();


foo<>();



具有默认值的非类型模板参数的示例


template<int = 0>


void foo() {};



//usage


foo<4>();


foo<>();



在你的例子中,你可能会混淆的最后一件事情是使用 enable_if_t,事实上在你的右边代码中有一个多余的typename:


 template <


 typename Integer,


 typename std::enable_if_t<std::is_integral<Integer>::value, int> = 0


>


T(Integer) : m_type(int_t) {}



将更好地编写为:


template <


 typename Floating,


 std::enable_if_t<std::is_floating_point<Floating>::value, int> = 0


>



( 第二个声明同样适用) 。

这是对 enable_if_t的角色精确收费的:


template <bool B, class T = void> 


using enable_if_t = typename enable_if<B,T>::type;



不需要添加 typename ( 与较旧的enable_if 相比)

让我们尝试省略默认参数值和不同的名称( 请记住: 默认模板参数不是模板函数签名的一部分,与参数名称一样,并了解"错误"模板函数签名的外观:


template


<


 typename FirstParamName


, typename SecondParamName


>


T(FirstParamName)



template


<


 typename FirstParamName


, typename SecondParamName


>


T(FirstParamName)



哇,它们完全一样 ! 因此 T(Floating) 实际上是对 T(Integer)的重定义,而正确版本声明了两个具有不同参数的模板:


template


<


 typename FirstParamName


, std::enable_if_t<std::is_integral<FirstParamName>::value, int> SecondParamName


> 


T(FirstParamName)



template


<


 typename FirstParamName


, std::enable_if_t<std::is_floating_point<FirstParamName>::value, int> SecondParamName


>


T(FirstParamName)



还要注意,在以下情况下,不需要使用 typenamestd::enable_if_t<std::is_floating_point<Floating>::value, int> 因为在"右边"模板声明中没有依赖类型名。

...