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

Tra*_*kel 50 c++ sfinae visual-c++ language-lawyer c++11

我经常使用一种技术,我称之为"懒人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;
}
Run Code Online (Sandbox Code Playgroud)

随着--std=c++11,g ++ 4.7+和Clang 3.5+愉快地编译那段代码(并且它按照我的预期工作).但是,当使用MSVC 14 CTP5时,我得到这个错误抱怨foo已经定义:

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

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

Col*_*mbo 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;
}
Run Code Online (Sandbox Code Playgroud)

然后VC++ 编译好了.我缩短了[temp.over.link]/6中的引用,其中包括:

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

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

template <int I>
using overload = std::integral_constant<int, I>*;
Run Code Online (Sandbox Code Playgroud)

用法是例如

// 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())
Run Code Online (Sandbox Code Playgroud)

演示.


Jam*_*lis 33

这是一个名为"表达SFINAE"的功能. Visual C++尚未完全支持它(请参阅"VS 2015预览版中的C++ 11/14/17功能",了解截至本答复时的最新一致性更新).

  • 谢谢!至少它是可以解决的.是否有任何关于是否会为大版本修复的问题? (3认同)