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

Why is gcc failing when using lambda for non-type template parameter?

以下代码段 compiles with no error with Clang 4.0 but GCC 7.0 produces 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 和嵌套 class 的方法都没有链接,因此它们不能用作非类型模板参数。

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

The value returned by this conversion function is a pointer to a function with C++ language linkage that, when invoked, has the same effect as invoking the closure object's function call operator directly.

GCC 是否行为不端?

根据 http://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter, it seems like external linkage is no longer a requirement since C++17. The same language is found in the C++17 draft under [temp.arg.nontype] at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf(请注意,它被错误地 link 编辑为 C++14 草案)。

The template argument that can be used with a non-type template parameter can be any converted constant expression of the type of the template parameter...

The only exceptions are that non-type template parameters of reference and pointer type cannot refer to/be the address of

  • a subobject (including non-static class member, base subobject, or array element);
  • a temporary object (including one created during reference initialization);
  • a string literal;
  • the result of typeid;
  • or the predefined variable __func__.

那个 link on cppreference 也特别提到了函数指针,pre C++ 17:

The following limitations apply when instantiating templates that have non-type template parameters:

...

For pointers to functions, the valid arguments are pointers to functions with linkage (or constant expressions that evaluate to null pointer values).

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

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

我同意 and wanted to add some information to it. He cites the relevant section in the standard (§14.3.2 [temp.arg.nontype]) that shows that there is no longer a requirement for non-type parameters to have linkage, but that still doesn't show that GCC is misbehaving for the lambda part. For that we need to show that static_cast<FUNCT>(lambda) is a converted constant expression. For that we need a newer 来自 Nir ​​链接的草稿。而这一段

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

  1. The closure type for a non-generic lambda-expression with no lambda-capture has a conversion function to pointer to function with C++ language linkage (7.5) having the same parameter and return types as the closure type’s function call operator. [...] The conversion function [...] is public, constexpr, non-virtual, non-explicit, const, and has a non-throwing exception specification.

有趣的是,GCC claims to have already implemented this (N4268) 在已经发布的第 6 版中(如果您想借口 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 中的一个错误。