是“懒人的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” )。