【问题标题】:Can lambdas be used as non-type template parameter?可以将 lambdas 用作非类型模板参数吗?
【发布时间】:2020-10-01 01:51:03
【问题描述】:

下面的代码合法吗?

template <auto Lambda>
struct A {};

int main () {
  auto lmb = [](int i){return i*i;};
  A<lmb> a;
  return 0;
}

我注意到 g++ 编译得很好,而 clang++ 返回 error: a non-type template parameter cannot have type '(lambda at main.cpp:...)'.

【问题讨论】:

  • gcc 在这里也接受constexpr auto= ...。其他编译器呢?只有constexprs 可以是模板参数(包括auto)。
  • 根据this page,clang还不支持“类类型作为非类型模板参数”。
  • ...GCC does,从 GCC 9 开始。
  • @Sam constexpr 适用于非捕获 lambda,自 C++17 以来它们是明确的 Literl 类型,但当您捕获非 constexpr 时它会停止工作。
  • @DarioP 有没有回答您的问题?我刚刚注意到这个问题仍然悬而未决。

标签: c++ templates lambda c++20 non-type


【解决方案1】:

可以将 lambdas 用作非类型模板参数吗?

是的,已经实现 P0732R2 - Class types in non-type template parametersclang++ 尚未实现。

来源: https://en.cppreference.com/w/cpp/compiler_support


请注意,lambda 至少需要为 constexpr(默认情况下):

当此说明符不存在时,函数调用运算符将​​是 constexpr 无论如何,如果它恰好满足所有 constexpr 功能要求。

但是,您可以添加 constexpr 以获取 lambda 本身的错误,而不是在将其用作模板参数时。附带说明:您还可以将其指定为 consteval 以使其作为非类型模板参数工作。

有状态的 lambda 可以是 constexpr:

constexpr auto lmb1 = [](int i) {
    static int x = 0;
    return i*i + ++x;
};

而通过引用捕获的 lambda,或通过复制捕获变异 (mutable),则不能。不过,通过复制 constexpr 进行捕获是可以的。

通用 lambda 也可能是 constexpr

constexpr auto gen_lmb = []<typename T>(T& val) {
   val += val;
   return val;
};

template <auto Lambda>
struct A {
    template<typename T>
    void doit(T&& arg) {
        std::cout << Lambda(arg) << '\n';
    }
};

//...

A<gen_lmb> ginst;

int v = 1000;
ginst.doit(v);
ginst.doit(std::string("foo "));
std::cout << v << '\n';
2000
foo foo
2000

【讨论】:

    【解决方案2】:

    [temp.arg.nontype]/1:

    如果模板参数的类型 T 包含占位符类型 ([dcl.spec.auto]) 或推导类类型的占位符 ([dcl.type.class.deduct]),参数的类型就是类型 为发明声明中的变量 x 推导出来

    T x = template-argument ;
    

    如果推导的参数类型不允许用于 模板参数声明([temp.param]),程序是 格式不正确。

    所以,规则是由[temp.param]/6设置的:

    非类型模板参数应具有以下之一 (可能是 cv 限定的)类型:...

    (6.1) 结构类型 ...

    structural type 的规则是:--我的重点--

    (7.1) 标量类型,

    (7.2) 左值引用类型,

    (7.3) 具有以下属性的文字类类型

    (7.3.1) 所有基类和非静态数据成员都是publicnon-mutable

    (7.3.2) 所有基类和非静态数据成员的类型都是结构类型或其(可能是多维的)数组。

    由于 lambda 没有基类,唯一的要求是它必须是 literal class type ([basic.types]),其中包括:

    (10.5.2) ... 闭包类型 ([expr.prim.lambda.closure]) ...

    结构类型的数据成员也应该是结构类型,这适用于这种情况下的 lambda 捕获,只要它的所有成员都是 public不可变


    @Nicol Bolas commented below 即使constexpr 文字类型捕获,标准也没有强制要求将捕获作为公共字段进行管理。


    底线是,在 C++20 中,没有捕获的 constexpr lambda 表达式作为模板非类型参数是合法的(基于上面提到的[basic.types]/10.5.2)。

    另见an answer by @Barry to a similar question


    下面的代码compiles with gcc,但据我了解,Nicol Bolas 的评论,并非所有情况都由规范保证(或者更糟糕的是,所有情况都不受规范保证)规格?)。


    假设我们有:

    template <auto T> struct A {};
    
    struct B {};
    
    struct C {
        ~C(){}
    };
    

    文字类型 lambda,应该是合法的作为模板参数:

    // compiles in gcc and should be ok by the spec as of [basic.types]/10.5.2
    A<[](){}> a; // compiler deduces the anonymous lambda to be constexpr
    
    auto lmb1 = [](){};
    // same as above
    A<lmb1> a1;
    
    // compiler deduces lmb1 above to be constexpr
    // same as it will deduce the following:
    B b {};
    A<b> here_i_am;
    

    Lambdas,由 gcc 作为模板参数编译,但正如 Nicol Bolas 在评论中所说 - 规范不保证它们是文字类型:

    const int i = 0;
    constexpr auto lmb2 = [i](){};
    // compiles in gcc but is not guaranteed by the spec 
    A<lmb2> a2;
    
    constexpr auto lmb3 = [b](){}; // B is literal
    // compiles in gcc but is not guaranteed by the spec 
    A<lmb3> a3;
    

    非文字类型 lambda,不合法 作为模板参数:

    const int j = 0;
    // below doesn't compile: <lambda()>{j} is not a constant expression
    constexpr auto lmb4 = [&j](){}; // local reference - not constexpr
    A<lmb4> a4;
    
    C c;
    // below doesn't compile: <lambda()>'{c} does not have 'constexpr' destructor
    constexpr auto lmb5 = [c](){}; // C is not literal
    A<lmb5> a5;
    

    【讨论】:

    • 您的分析将您带到正确且最有趣的段落,即给定的 lambda 是否为 literal 类类型,截至[basic.types]/10.5,但我认为答案会跳过最重要的部分(考虑到 OP 的问题),即覆盖未明确标记为 constexpr 的 lambda 仍然是文字类类型?例如。 lambda ([expr.prim.lambda.closure]/14) constexpr 隐式生成的析构函数是什么时候?
    • "或者使用 constexpr 字面量类型捕获" 不,实际上。正如您所引用的,强大的结构平等要求成员是public。标准中没有任何内容要求 lambda 的捕获是公开的。因此,不需要将捕获 lambda 用作 NTTP。此外,标准中没有任何内容要求非捕获 lambda 没有成员。它仍然可以是文字类型,但是如果没有 explicit 声明非捕获 lambda 必须具有强结构相等性,则不能依赖它。
    • @NicolBolas 我刚刚发现这个问题is already discussed elsewhere,带有an answer by @Barry部分引用:“无捕获的lambdas 算作结构类型([temp.param] /7) 根本没有任何数据成员”。我对此的拙见与巴里相同。
    【解决方案3】:

    首先,我认为您的 lambda 应该是 constexpr 以用作非类型模板参数。我觉得它有点奇怪,它有效。

    但是在这种情况下它应该可以工作。 The Standard 告诉我们,非类型模板参数可以是文字类类型(这有点不确定,因为闭包是文字但不是真正的类类型,我认为它们被明确包含here)有额外的要求,

    • 所有基类和非静态成员都必须是非可变和公共的
    • 它们的类型必须是结构或其数组

    所以我们在这个简单的例子中没有问题。但是如果你捕获任何东西,你的 lambda 有一个非公共成员变量,应该被淘汰。如果这对于闭包来说不是那么尖锐,那么如果你捕获一些非常量的东西,它肯定会停止工作。

    【讨论】:

    • 从 c++17 开始,Lambdas 默认为 constexpr
    猜你喜欢
    • 2019-09-23
    • 1970-01-01
    • 1970-01-01
    • 2019-04-24
    • 1970-01-01
    • 2012-10-25
    • 2014-11-06
    • 2023-03-07
    • 2018-02-12
    相关资源
    最近更新 更多