我经常使用一种技术,我称之为“懒人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错误?
答案 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"了解截至本答复时的最新一致性更新)。