我经常使用一种我称之为“懒人的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+愉快地编译了这段代码(它的工作方式正如我所期望的那样)。然而,当使用MSVC14 CTP5时,我得到这个错误,抱怨foo已经被定义了:
错误C2995:‘未知类型foo(F &&)':函数模板已定义c++-scratch main.cpp 15
所以我的问题是:“懒人的enable_if”是合法的C++,还是MSVC的bug?
发布于 2015-01-29 07:11:55
[temp.over.link]/6指定何时两个函数模板声明为重载。这是通过定义两个函数模板的等价性来实现的,如下所示:
如果两个函数模板..有返回类型..使用上述规则来比较涉及模板参数的表达式是等效的。
上面描述的“规则”是
如果包含两个表达式的两个函数定义满足一个定义规则(3.2),则认为这两个包含模板参数的表达式是等价的。
与本部分相关ODR在[basic.def.odr]/6中声明
给定在多个翻译单元中定义的名为
D的实体,则
由相同的tokens;序列组成的 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())。
发布于 2015-01-29 07:01:38
这是一个称为"Expression SFINAE."的特性,但Visual C++还没有完全支持它(请参阅"C++11/14/17 Features In VS 2015 Preview",了解截至本文回答时的最新一致性更新)。
https://stackoverflow.com/questions/28203981
复制相似问题