为什么我可以“捕获无捕获”一个整数变量,但不能对非捕获lambda执行相同的操作?

5
下面的函数是有效的(截至C++20):
void foo() {
    constexpr const int b { 123 };
    constexpr const auto l1 = [](int a) { return b * a; };
    (void) l1;
}

即使l1没有捕获任何内容,据说它仍然可以“无捕获捕获”b的值,因为它是一个const(它甚至不必是constexpr;但请参见@StoryTeller的评论)。
但如果我尝试在新的lambda中捕获更复杂的东西:
void foo() {
    constexpr const int b { 123 };
    constexpr const auto l1 = [](int a) { return b * a; };
    (void) [](int c) { return l1(c) * c; };
}

这段代码无法编译。为什么?编译器应该可以轻松地从 lambda 内部调用 l1,那么为什么 b 对于无捕获的捕获是可以的,而 l1 不行呢? 在 GodBolt 上查看此问题。

3
很确定这是因为ODR-Use(一种C++语言的特性)。让我看看是否能找到我想要的那个章节。 - NathanOliver
可以在不捕获它的情况下在lambda中使用constexpr值吗? - NathanOliver
必须使用lambda在C++中捕获constexpr表达式吗? - NathanOliver
3
因为它是一个const(甚至不需要是constexpr),整数类型是特殊的。即使通过“隐式constexpr”属性,整数类型在声明为const时也必须是constexpr。” - StoryTeller - Unslander Monica
1个回答

8

这与odr-use有关。

首先,从[basic.def.odr]/10

如果一个本地实体在作用域中是odr-usable的,则:

  • 要么该本地实体不是*this,或者存在一个封闭的类或非lambda函数参数作用域,并且,如果最内层的这样的作用域是函数参数作用域,则它对应于非静态成员函数,以及
  • 对于介于引入实体的点和作用域之间(其中*this被认为在最内层的封闭类或非lambda函数定义作用域内引入)的每个中间作用域([basic.scope.scope]),都满足以下条件:
    • 中间作用域是块作用域,或
    • 中间作用域是具有简单捕获命名实体或具有默认捕获的lambda表达式的函数参数作用域,且lambda表达式的块作用域也是中间作用域。

如果在不可odr使用的作用域中使用了局部实体,则程序无法形成。

所以在这个例子中,a 是可重复定义的,但b不是:
void foo() {
    constexpr const int b { 123 };
    constexpr const auto l1 = [](int a) { return b * a; };
    (void) l1;
}

在这个例子中,同样地,ac是odr可用的,但是bl1都不是。
void foo() {
    constexpr const int b { 123 };
    constexpr const auto l1 = [](int a) { return b * a; };
    (void) [](int c) { return l1(c) * c; };
}

但这个规则不仅是“不能被odr使用”,也包括“已被odr使用”。其中哪些是odr使用的呢?参见[basic.def.odr]/5

如果表达式是一个表示它的 id-expression,那么变量由该表达式命名。如果变量 x 的名称出现在可能评估的表达式 E 中,则 E 使用 x,除非

  • x 是可用于常量表达式的引用([expr.const]),或
  • x 是可用于常量表达式且没有可变子对象的非引用类型变量,并且 E 是应用了 lvalue-to-rvalue 转换([conv.lval])的非易失性限定非类类型表达式潜在结果集的元素,或
  • x 是非引用类型变量,而 E 是未应用 lvalue-to-rvalue 转换的弃值表达式([expr.context])潜在结果集的元素。
对于b * a情况,b是“非引用类型的变量,可在常量表达式中使用”,我们正在对其应用“左值到右值转换”。这是规则的第二个例外,因此b不是odr-used,因此我们没有odr-used但不可odr-usable的问题。
对于l1(c)情况,l1也是“非引用类型的变量,可在常量表达式中使用”...但我们没有对其进行左值到右值转换。我们正在调用调用运算符。因此,我们没有触发该异常,因此我们正在odr-using l1...但它不是odr-usable,这使得它不合法。 解决方案是要么捕获l1(使其odr-usable),要么将其设置为static或全局变量(使该规则无关紧要,因为l1不再是局部实体)。

constexpr const变量不是隐式静态的吗? - einpoklum
另外,我想更改问题中变量的名称,以避免与标准中的引用冲突,使事情变得不那么混乱。 - einpoklum
另外,odr-usable 的第一条并不适用于我们的情况,对吧?所以也许可以将其“剪掉”? - einpoklum
2
@einpoklum 不,constexpr变量隐式为const,但是constexprstatic是正交的。 - Barry
我认为这值得跟进的问题... - einpoklum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接