可以在可变lambda中更改`this`吗?

大家都知道thisC++中的对象指针在其方法中是不能改变的。但是对于可变的 lambda,在何处this捕获,一些当前的编译器提供了这种可能性。考虑这个代码:

struct A {
    void foo() {
        //this = nullptr; //error everywhere

        (void) [p = this]() mutable { 
            p = nullptr; //#1: ok everywhere
            (void)p;
        };

        (void) [this]() mutable { 
            this = nullptr; //#2: ok in MSVC only
        };
    }
};

在第一个 lambda 中this被捕获并赋予一个新名称p。这里所有的编译器都允许更改p. 在第二个 lambda 中this被自己的名字捕获,只有 MSVC 允许程序员改变它的值。演示:https : //gcc.godbolt.org/z/x5P81TT4r

我相信 MSVC 在第二种情况下行为不正确(尽管它看起来像一个不错的语言扩展)。谁能从标准中找到正确的措辞(搜索并不容易,因为该词this在那里被提及了 2800 多次)?

回答

初始捕获 this

(void) [p = this]() mutable { 
    p = nullptr; //#1: ok everywhere
    (void)p;
};

这使用 init-capture按值捕获this指针,根据[expr.prim.lambda.capture]/6,这意味着它是指针的副本。在上下文中被-qualified,复制可以自然不能用来改变(即使拉姆达是可变的;具有“指向常量”比较),但作为拉姆达是可变的指针(复制)可用于指向不同的东西,例如。thisthisconstthisnullptr

struct S {
    void f() const {
        (void) [p = this]() mutable { 
            p->value++;   // ill-formed: 'this' is pointer to const, meaning
                          //             'p' is pointer to const.
            p = nullptr;  // OK: 'p' is not const pointer
            (void)p;
        };
    }
    
    void f() {
        (void) [p = this]() mutable { 
            p->value++;   // OK: 'this' is pointer to non-const, meaning
                          //     'p' is pointer to non-const.
            p = nullptr;  // OK: 'p' is not const pointer
            (void)p;
        };
    }
    int value{};
};

简单的捕获 this

(void) [this]() mutable { 
    this = nullptr; //#2: ok in MSVC only
};

根据[expr.prim.lambda.capture],忽略capture-default :s 的情况:

  • 一个捕获列表包含一个捕获
  • 捕获可以是一个简单的捕获INIT-捕获; 我们忽略上面提到的后一种情况
  • 一个简单的捕获具有下列形式之一:
    • 标识符...选择
    • &标识符...选择
    • this
    • *this

根据[expr.prim.lambda.capture]/10 [强调我的]:

一个实体被副本捕获,如果

  • (10.1) 它是隐式捕获的,捕获默认为=,捕获的实体不是*this,或

  • (10.2) 它是用不是this& identifier& identifier initializer形式的捕获显式捕获的。

只有简单捕获形式*this允许*this通过复制显式捕获对象。this然而,简单的 capture是通过引用捕获*this对象(+),根据[expr.prim.lambda.capture]/12:

(+) simple-capture :sthis*this都表示本地实体*this,如[expr.prim.lambda.capture]/4。

如果实体被隐式或
显式捕获但未被 copy捕获,则通过引用捕获该实体。对于通过引用捕获的实体,是否在闭包类型中声明了其他未命名的非静态数据成员是未指定的。[...]

因此:

struct S {
    void f() const {
        (void) [this]() mutable { 
            // '*this' explicitly-captured by-reference
            this->value++;   // ill-formed: 'this' is pointer to const
            this = nullptr;  // #2 ill-formed: 'this' is not a modifyable lvalue
        };
    }
    
    void f() {
        (void) [this]() mutable { 
            // '*this' explicitly-captured by-reference
            this->value++;   // OK: 'this' is pointer to non-const
            this = nullptr;  // #2 ill-formed: 'this' is not a modifyable lvalue
        };
    }
    int value{};
};

根据[class.this]/1,this不是可修改的左值,这就是为什么#2

在非静态 ([class.mfct]) 成员函数的主体中,关键字this 是一个纯右值,其值是指向调用该函数的对象的指针。的类型的this在一个成员函数,其类型具有CV-限定符-SEQ CV和它的类是X为“指向cv X”。[...]

其中,根据[expr.prim.lambda.closure]/12也适用于 whenthis在 lambdas 中使用:

所述λ-表达的化合物语句产生的函数调用操作的功能体([dcl.fct.def]),但为的目的名称查找,确定所述类型和值this和转化ID表达式参照非静态使用 (*this) ([class.mfct.non-static]) 将类成员转换为类成员访问表达式,将则在 lambda 表达式的上下文中考虑复合语句。

MSVC 接受您的代码段是不正确的(接受无效)。

事实上,在以下示例(演示)中:

#include <iostream>

struct S;

void g(S *& )  { std::cout << "lvalue pointer to S"; }
void g(S *&& ) { std::cout << "rvalue pointer to S"; }

struct S {
  void f() {
    auto l = [this]() { g(this); };
    l();
  }
};

int main() {
  S s{};
  s.f();
}

我们期望第二个g重载是一个更好的匹配,就像this一个纯右值。然而,虽然 GCC 和 Clang 的行为符合预期

// GCC & Clang: rvalue pointer to S

MSVC 甚至无法编译程序

// MSVC: error, no viable overload; arg list is '(S *const )'

这违反了 [class.this]/1,因为

[...]this类型具有 cv-qualifier-seq cv 且类X 为“指向 cv X 的指针”的成员函数中的类型[...]

... 而不是“指向 cv X 的常量指针”(首先,纯右值上的常量会很奇怪)。


以上是可以在可变lambda中更改`this`吗?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>