代码之家  ›  专栏  ›  技术社区  ›  einpoklum

为什么我可以“捕获”一个int变量,而不是一个未捕获的lambda?

  •  3
  • einpoklum  · 技术社区  · 2 年前

    以下函数有效(从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; };
    }
    

    这无法编译。为什么?编译器调用 l1 从lambda内部;那为什么 B 好的,无捕获捕获,并且 l1 不是吗?

    See this on GodBolt.

    1 回复  |  直到 2 年前
        1
  •  5
  •   Barry    2 年前

    这与odr的使用有关。

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

    在以下情况下,本地实体在作用域中是odr可用的:

    • 本地实体不是*this,或者存在一个封闭类或非lambda函数参数作用域,如果最内部的作用域是函数参数作用域,则它对应于一个非静态成员函数,以及
    • 对于实体引入点和范围(其中*被认为是在最内层的封闭类或非lambda函数定义范围内引入)之间的每个中间范围([basic.scope.scope]),可以:
      • 中间作用域是块作用域,或

    如果一个本地实体在一个不能使用odr的范围内使用odr,那么该程序的格式就不正确。

    所以在这个例子中, a odr可用吗 b 不是:

    void foo() {
        constexpr const int b { 123 };
        constexpr const auto l1 = [](int a) { return b * a; };
        (void) l1;
    }
    

    在这个例子中,类似地 A. c odr是否可用,但两者都不可用 B 或者不 l1

    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表达式,则该变量由表达式命名。变量x的名称显示为可能计算的表达式E,它是E使用的odr

    • x是在常量表达式([expr.const])中可用的引用,或
    • x是一个非引用类型的变量,可在常量表达式中使用,并且没有可变的子对象,E是应用左值到右值转换([conv.lval])的非易失性限定非类类型表达式的潜在结果集的一个元素,或者
    • x是非引用类型的变量,E是未应用左值到右值转换的废弃值表达式([expr.context])的潜在结果集的元素。

    对于 b * a 案例 B 是“一个在常量表达式中可用的非引用类型的变量”,我们正在用它来应用“左值到右值对流”。这是规则的第二个例外,所以 B 使用odr,所以我们没有使用odr,但没有odr可用性问题。

    对于 l1(c) 案例 l1 也是“在常量表达式中可用的非引用类型的变量”。。。但我们并没有对它进行左值到右值的转换。我们正在呼叫接线员。所以我们没有遇到异常,所以我们使用odr l1 ... 但它不能使用odr,这使得它的格式不正确。

    这里的解决方案是 l1 (使其odr可用)或使其 static 或全球(使规则变得无关紧要) l1 不再是本地实体)。