是"懒惰的人'enable_if"合法的C ++?

时间:2015-01-28 22:53:53

标签: c++ c++11 visual-c++ language-lawyer sfinae

我经常使用一种技术,我称之为“懒人enable_if”,我使用decltype和逗号运算符来启用基于某些模板输入的函数。这是一个小例子:

template <typename F>
auto foo(F&& f) -> decltype(f(0), void())
{
    std::cout << "1" << std::endl;
}

template <typename F>
auto foo(F&& f) -> decltype(f(0, 1), void())
{
    std::cout << "2" << std::endl;
}

使用--std=c++11,g ++ 4.7+和Clang 3.5+很高兴地编译那段代码(并且它可以像我期望的那样工作)。但是,当使用MSVC 14 CTP5时,我收到此错误,抱怨foo已被定义:

  

错误错误C2995:'unknown-type foo(F&amp;&amp;)':函数模板有   已经定义了c ++ - scratch main.cpp 15

所以我的问题是:“懒惰的人enable_if”是合法的C ++还是这是一个MSVC错误?

2 个答案:

答案 0 :(得分:44)

[temp.over.link]/6指定两个函数模板声明何时重载。这是通过定义两个函数模板的等效性来完成的,如下所示:

  

两个函数模板等效如果它们[..]具有使用规则等效的返回类型[..]   如上所述,比较涉及模板参数的表达式。

“上述规则”是

  

考虑涉及模板参数的两个表达式   如果包含表达式的两个函数定义满足一个定义规则(3.2)[...]

,则等效

与此部分相关的ODR在[basic.def.odr]/6中说明了

  

鉴于在多个翻译中定义了名为D的实体   单位,然后

     
      
  • D的每个定义都应包含相同的令牌序列;
  •   

显然,由于返回类型(根据[dcl.fct]/2的尾随返回类型)不包含相同的标记,因此包含这些表达式的两个函数定义将违反ODR。
因此foo的声明声明非等效函数模板并重载名称。

您看到的错误是由于VC ++缺乏对表达式SFINAE的支持而发布的 - 可能是尾随返回类型没有被检查等效。


解决方法

您可以以其他方式使功能模板不等效 - 更改模板参数列表。如果你像这样重写第二个定义:

template <typename F, int=0>
auto foo(F&& f) -> decltype(f(0, 1), void())
{
    std::cout << "2" << std::endl;
}

然后VC ++ compiles it fine。 我缩短了[temp.over.link] / 6中的引用,其中包括:

  

如果两个函数模板在同一个模板中声明,则它们等效   范围,具有相同名称,具有相同的模板参数列表 [..]

事实上,为了能够轻松引入新的重载,你可以使用一个小帮手:

template <int I>
using overload = std::integral_constant<int, I>*;

用法就是例如。

// Remember to separate > and = with whitespace
template <typename... F, overload<0> = nullptr>
auto foo(F&&... f) -> decltype(f(0, 1)..., void())

template <typename... F, overload<1> = nullptr>
auto foo(F&&... f) -> decltype(f(0, 1, 2)..., void())

Demo

答案 1 :(得分:33)

这是一个名为"Expression SFINAE."的功能.VC C ++尚未完全支持它(请参阅"C++11/14/17 Features In VS 2015 Preview"了解截至本答复时的最新一致性更新)。