是“懒人的enable_if”合法的C ++吗?

我经常使用一种我称之为“lazy man's enable_if ”的技术,在这里我使用decltype和逗号运算符来启用基于某些模板input的函数。 这是一个小例子:

 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 &&)':函数模板已被定义c ++ – scratch main.cpp 15

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

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

如果两个函数模板具有返回types[..],则使用上述规则来比较涉及模板参数的expression式。

“上述规则”是

如果包含expression式的两个函数定义将满足一个定义规则(3.2)[…],则涉及模板参数的两个expression式被认为是等同的

与这部分相关的ODR在[basic.def.odr] / 6中表示

给定一个名为D的实体定义在多个翻译单元中,那么

  • D每个定义应由相同的令牌序列组成 ;

显然,由于返回types(它是根据[dcl.fct] / 2的尾随返回types)不包含相同的标记,所以包含这些expression式的两个函数定义将违反ODR。
因此, foo声明声明了非等价的函数模板并重载了名字。

你看到的错误是由于缺乏VC ++expression式SFINAE的支持而发布的 – 大概尾随返回types没有被检查是否等同。


解决方法

您可以通过其他方式使function模板不相同 – 更改模板参数列表。 如果你像这样重写第二个定义:

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

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

如果两个function模板在同一个范围内声明,具有相同的名称, 具有相同的模板参数列表 [..]

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

 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()) 

演示

这是一个称为“expressionSFINAE”的function。 Visual C ++尚未完全支持它(请参阅本答案的最新一致性更新中的“VS 2015 Preview中的C ++ 11/14/17function” )。