"lazy man's enable_if" 是合法的 C++ 吗?

Is the "lazy man's enable_if" legal C++?

我经常使用一种称为 "lazy man's 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 已经被定义:

Error error C2995: 'unknown-type foo(F &&)': function template has already been defined c++-scratch main.cpp 15

所以我的问题是:"lazy man's enable_if" 是合法的 C++ 还是 MSVC 错误?

这是一个称为 "Expression SFINAE." It is not yet fully supported by Visual C++ (see "C++11/14/17 Features In VS 2015 Preview" 的功能,用于截至本回答时的最新一致性更新。

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

Two function templates are equivalent if they [..] have return types [..] that are equivalent using the rules described above to compare expressions involving template parameters.

"rules described above"是

Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one definition rule (3.2) [..]

与此部分相关的 ODR 在 [basic.def.odr]/6 中指出

Given such an entity named D defined in more than one translation unit, then

  • each definition of D shall consist of the same sequence of tokens;

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

您看到的错误是由于缺少 VC++ 对表达式 SFINAE 的支持而发出的 - 可能未检查尾随 return 类型的等效性。


解决方法

您可以通过另一种方式使函数模板不等效 - 更改模板参数列表。如果你像这样重写第二个定义:

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 中的引用,其中包括:

Two function templates are equivalent if they are declared in the same scope, have the same name, have identical template parameter lists [..]

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

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