为什么将 lambda 用于非类型模板参数时 gcc 失败?



以下代码片段在 Clang 4.0 中编译时没有错误,但 GCC 7.0 会产生错误(请注意使用 -std=c++1z 标志)。

using FuncT = int (*)(double);
template <FuncT FUNC>
int temp_foo(double a)
{
return FUNC(a);
}
int foo(double a)
{
return 42;
}
void func()
{
auto lambda = [](double a) { return 5; };
struct MyStruct
{
static int foo(double a) { return 42; }
};
temp_foo<foo>(3);
temp_foo<static_cast<FuncT>(lambda)>(3);
temp_foo<MyStruct::foo>(3);
}

具体来说,GCC 抱怨 lambda 和嵌套类的方法都没有链接,因此它们不能用作非类型模板参数。

至少对于lambda的情况,我认为Clang是正确的(而GCC是错误的),因为(引用cpp首选项,转换运算符):

此转换函数返回的值是指向 具有C++语言链接的函数,当调用时,具有相同的 作为调用闭包对象的函数调用运算符的效果 径直。

海湾合作委员会行为不端吗?

根据 http://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter 的说法,自 C++17 年以来,外部链接似乎不再是必需的。在 C++17 草案的 [temp.arg.nontype] 下可以找到相同的语言 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf (请注意,它被错误地链接为 C++14 草案)。

可与非类型模板参数一起使用的模板参数可以是模板参数类型的任何转换常量表达式...

唯一的例外是引用和指针类型的非类型模板参数不能引用/是地址

子对象
  • (包括非静态类成员、基子对象或数组) 元素);
  • 临时对象(包括在引用初始化期间创建的对象);
  • 字符串文字;
  • 类型ID的结果;
  • 或预定义的变量__func__。

cpp首选项上的链接还特别提到了函数指针,C++ 17 之前:

实例化具有非类型模板参数的模板时,存在以下限制:

对于指向函数的指针,有效参数是指向具有链接的函数(或计算结果为 null 指针值的常量表达式)的指针。

由于您的问题标记为 C++1z(我们现在可能应该有一个 17 标签,并在 17 完成时使用它),我们应该使用第一组规则。您的示例似乎不属于 C++ 17 的任何例外类别,因此 gcc 是错误的。

请注意,如果将语言标志更改为 14,则 clang 不会编译您的示例。

我同意Nir的回答,并想在其中添加一些信息。他引用了标准(§14.3.2 [temp.arg.nontype])中的相关部分,该部分表明不再要求非类型参数具有链接,但这仍然没有表明GCC对于lambda部分行为不当。为此,我们需要证明static_cast<FUNCT>(lambda)是一个转换后的常量表达式。为此,我们需要从Nir链接的草案中更新草案。而本节

§5.1.5 Lambda 表达式 [expr.prim.lambda]:

    没有 lambda
  1. 捕获的非泛型 lambda 表达式的闭包类型具有指向函数的转换函数 具有相同参数和返回类型的C++语言链接 (7.5) 作为闭包类型的函数调用运算符。[...]转换 功能 [...] 是公共的,constexpr, 非虚拟、非显式、常量,并且具有非抛出异常 规范。

有趣的是,GCC 声称已经在已经发布的版本 4268 中实现了这个 (N6)(如果你想通过说 GCC 7 尚未正式发布来原谅 GCC 的行为,所以也许当它出来时,这将被修复):

Language Feature                                               Proposal  Available in GCC?  SD-6 Feature Test
Allow constant evaluation for all non-type template arguments  N4268     6                  __cpp_nontype_template_args >= 201411

所以总而言之,这是 GCC 中的一个错误。

相关内容

最新更新