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

Posted

技术标签:

【中文标题】为啥将 lambda 用于非类型模板参数时 gcc 会失败?【英文标题】:Why is gcc failing when using lambda for non-type template parameter?为什么将 lambda 用于非类型模板参数时 gcc 会失败? 【发布时间】:2017-08-30 01:16:33 【问题描述】:

以下 sn-p compiles 在 Clang 4.0 中没有错误,但 GCC 7.0 生成 errors(注意使用 -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 是错误的),因为(引用 cppreference,转换运算符):

这个转换函数返回的值是一个指针 具有 C++ 语言链接的函数,在调用时具有相同的 调用闭包对象的函数调用运算符的效果 直接。

GCC 是否行为不端?

【问题讨论】:

相关:***.com/questions/18498957/… 【参考方案1】:

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

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

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

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

cppreference 上的那个链接还特别提到了 C++ 17 之前的函数指针:

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

...

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

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

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

【讨论】:

N4296 是 C++17 草案。 N4140 (几乎)是 C++14 出版物。 isocpp.org 仍然没有更正他们声称 N4296 是 C++14 草案的说法吗? 我认为c++14的问题是闭包类型的操作符()有什么样的链接? 好的,我会在有时间的时候编辑这个。由于问题是针对 17 的,因此它不会显着改变答案。【参考方案2】:

我同意Nir's answer 并想在其中添加一些信息。他引用了标准中的相关部分(§14.3.2 [temp.arg.nontype]),表明不再要求非类型参数具有链接,但这仍然没有表明 GCC 行为不端对于 lambda 部分。为此,我们需要证明static_cast&lt;FUNCT&gt;(lambda) 是一个转换后的常量表达式。为此,我们需要来自 Nir ​​链接的newer 草稿。而这部分

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

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

有趣的是,GCC claims 已经在已经发布的版本 6 中实现了这个 (N4268)(如果你想通过说 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 中的一个错误。

【讨论】:

以上是关于为啥将 lambda 用于非类型模板参数时 gcc 会失败?的主要内容,如果未能解决你的问题,请参考以下文章

为啥将 lambda 传递给受约束的类型模板参数会导致“不完整类型”编译器错误?

我可以将 C++17 无捕获 lambda constexpr 转换运算符的结果用作函数指针模板非类型参数吗?

为啥 std::apply 可以调用 lambda 而不是等效的模板函数?

C++11 中的非类型可变参数函数模板

错误“lambda不是从'std :: function'派生的

错误“lambda 不是从 'std::function' 派生的