为什么显式捕获 constexpr 变量不起作用,而不捕获它却有效

问题描述 投票:0回答:1

我编写了以下具有两个 lambda 的代码。其中一个明确捕获了

i
,而另一个则没有。注意
i
是 constexpr,因此我们不需要显式捕获它。

我的问题是为什么

func(lambda2)
不能编译而
func(lambda)
可以编译?请注意,
lambda
不会显式捕获
i
,而
lambda2
在其捕获列表中具有
i
演示

template <typename T>
constexpr auto func(T c) {
    constexpr auto k = c; 
    return k;
};


int main() {
    
    constexpr int i = 0;
    
    constexpr auto lambda = []()constexpr { constexpr int j = i; return j; };  //compiles
    constexpr auto lambda2 = [i]()constexpr { constexpr int j = i; return j; };//compiles
   
    constexpr auto a = func(lambda); //compiles as expected

    
    constexpr auto b = func(lambda2); //this doesn't compile why?
    
} 

叮 说:

error: constexpr variable 'k' must be initialized by a constant expression
    3 |     constexpr auto k = c; 
      |                    ^   ~
/home/insights/insights.cpp:18:24: note: in instantiation of function template specialization 'func<(lambda at /home/insights/insights.cpp:13:30)>' requested here
   18 |     constexpr auto b = func(lambda2); //this doesn't compile why?
      |                        ^
/home/insights/insights.cpp:3:24: note: function parameter 'c' with unknown value cannot be used in a constant expression
    3 |     constexpr auto k = c; 
      |                        ^
/home/insights/insights.cpp:3:24: note: in call to '(lambda at /home/insights/insights.cpp:13:30)(c)'
/home/insights/insights.cpp:2:23: note: declared here
    2 | constexpr auto func(T c) {
      |                       ^
c++ lambda language-lawyer c++20
1个回答
0
投票

这是一个类似但更简单的代码版本,但无法编译:

template <typename T>
constexpr auto func(int a) {
    constexpr auto b = a;
    return b;
};

int main() {
    constexpr int x = 1;
    constexpr int y = func(x);
}

这里,因为

x
constexpr
,所以在
x
变量
constexpr
的初始化中读取
y
的值就可以了。

但是,这还不够好,因为不仅

y
需要有一个常量表达式作为其初始值设定项,
b
也需要一个常量表达式,它也被声明为
constexpr
。这就是问题出现的地方。如果
b
没有被声明
constexpr
,那么代码就可以了。

为了使

constexpr auto b = ...;
有效,编译器必须能够在它出现的上下文中将其计算为常量表达式,而不是调用
func
的更大的封闭上下文。在出现
b
初始化的上下文中,直到运行时才知道
a
的值。
func
可以在常量表达式和非常量表达式中调用。因此,检查
b
是否为常量表达式失败。

在OP的程序中,非常相似。捕获

lambda2
i
属于闭包类型,具有
const int
类型的数据成员 ([expr.prim.lambda.capture]/10)。当调用
func
时,语句

constexpr auto k = c;

在其主体中被实例化,

c
是该闭包类型的函数参数。此时,必须将
k
的初始化作为常量表达式在其出现的上下文中进行检查,而不是在封闭常量计算的上下文中进行检查。初始化使用闭包类型的复制构造函数,默认为 ([expr.prim.lambda.closure]/15),从而执行成员复制。因此,它必须读取函数参数
const int
c
成员。

提供更多细节:

[dcl.constexpr]/6 指出“在任何

constexpr
变量声明中,初始化的完整表达式应为常量表达式”。让我们将其应用到
k
的声明中。为了使其成为常量表达式,它必须是满足其他一些条件的核心常量表达式 ([expr.const]/14)。 [expr.const]/5 给出了表达式 E 成为核心常量表达式的条件。在这种情况下,Ek
init-declarator

当初始化

k
时,将读取
const int
c
数据成员,如上所述。这是左值到右值的转换。 [expr.const]/5.9 指出核心常量表达式 E 中不允许进行左值到右值的转换,除非应用于其中一个

  • 非易失性泛左值,引用可在常量表达式中使用的对象,或
  • 文字类型的非易失性泛左值,指的是其生命周期开始于 E
  • 求值内的非易失性对象

第二个要点显然不满足:

const int
c
成员的生命周期不是在E的评估内开始的(k
init-declarator
);当调用该函数时它会提前启动。对于第一个项目符号,函数参数不能在常量表达式中使用,因为它永远不能同时是“常量初始化”和“潜在常量”([expr.const]/4)。特别是,标量或引用类型的函数参数不能满足 [expr.const]/2.1,类类型的函数参数不能满足 [expr.const]/3

(顺便说一句,即使在

consteval
函数中,函数参数的值仍然不是常量表达式,尽管
consteval
函数只能在常量求值期间调用。没有根本原因为什么会这样在
consteval
的情况下不支持,但如果允许的话,就会造成实现困难,并将几乎每个
consteval
函数变成隐式模板。)

© www.soinside.com 2019 - 2024. All rights reserved.