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

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

本文关键字:if 合法 C++ man lazy enable      更新时间:2023-10-16

我经常使用一种我称之为"懒人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:"未知类型 foo(F &&)":函数模板具有 已经定义了 C++-Scratch main.cpp 15

所以我的问题是:"懒人的enable_if"是合法的C++还是MSVC错误?

[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++编译得很好。我缩短了 [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())

演示

这是一个称为"表达SFINAE"的功能。 Visual C++ 尚不完全支持此功能(请参阅"VS 2015 预览版中的 C++11/14/17 功能",了解截至本回答时的最新一致性更新)。