可以在可变 lambda 中更改 this 吗?
Can `this` be changed in a mutable lambda?
大家都知道this
C++中的对象指针不能在它的方法中改变。但是对于捕获 this
的可变 lambda,一些当前的编译器提供了这种可能性。考虑这段代码:
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,这意味着它是 [=17= 的副本] 指针。在 this
是 const
限定的上下文中,副本自然不能用于更改 this
(即使 lambda 是可变的;与 'point to const' 比较),但是 as lambda 是可变的,指针(副本)可以用来指向不同的东西,例如nullptr
.
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:
的大小写
- 一个capture-list包含一个capture
- A capture 是 simple-capture 或 init-capture;我们忽略后一种情况,因为它在上面已经介绍过
- A simply-capture 具有以下形式之一:
- 标识符 ...选择
&
标识符 ...opt
this
*this
根据 [expr.prim.lambda.capture]/10 [强调 我的]:
An entity is captured by copy if
(10.1) it is implicitly captured, the capture-default is =
, and the captured entity is not *this
, or
(10.2) it is explicitly captured with a capture that is not of the form this
, &
identifier, or &
identifier initializer.
只有 simple-capture 形式 *this
允许通过复制显式捕获 *this
对象。然而,简单捕获 this
通过引用捕获 *this
对象 (+),根据 [expr.prim.lambda.capture]/12:
(+) simple-capture:s this
和 *this
都表示本地实体 *this
, 根据 [expr.prim.lambda.capture]/4.
An entity is captured by reference if it is implicitly or
explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the
closure type for entities captured by reference. [...]
因此:
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
格式错误的原因:
In the body of a non-static ([class.mfct]) member function, the keyword this
is a prvalue whose value is a pointer to the object for which the function is called. The type of this
in a member function whose type has a cv-qualifier-seq cv and whose class is X
is “pointer to cv X”. [...]
根据 [expr.prim.lambda.closure]/12,这也适用于在 lambda 中使用 this
的情况:
The lambda-expression's compound-statement yields the function-body ([dcl.fct.def]) of the function call operator, but for purposes of name lookup, determining the type and value of this
and transforming id-expressions referring to non-static class members into class member access expressions using (*this) ([class.mfct.non-static]), the compound-statement is considered in the context of the lambda-expression.
MSVC 接受您的代码段是错误的(接受无效)。
事实上,在下面的例子中 (demo):
#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,如:
[...] The type of this
in a member function whose type has a cv-qualifier-seq cv and whose class is X
is “pointer to cv X” [...]
... 而不是“指向 cv X 的常量指针”(首先,纯右值的常量性很奇怪)。
大家都知道this
C++中的对象指针不能在它的方法中改变。但是对于捕获 this
的可变 lambda,一些当前的编译器提供了这种可能性。考虑这段代码:
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,这意味着它是 [=17= 的副本] 指针。在 this
是 const
限定的上下文中,副本自然不能用于更改 this
(即使 lambda 是可变的;与 'point to const' 比较),但是 as lambda 是可变的,指针(副本)可以用来指向不同的东西,例如nullptr
.
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:
的大小写- 一个capture-list包含一个capture
- A capture 是 simple-capture 或 init-capture;我们忽略后一种情况,因为它在上面已经介绍过
- A simply-capture 具有以下形式之一:
- 标识符 ...选择
&
标识符 ...optthis
*this
根据 [expr.prim.lambda.capture]/10 [强调 我的]:
An entity is captured by copy if
(10.1) it is implicitly captured, the capture-default is
=
, and the captured entity is not*this
, or(10.2) it is explicitly captured with a capture that is not of the form
this
,&
identifier, or&
identifier initializer.
只有 simple-capture 形式 *this
允许通过复制显式捕获 *this
对象。然而,简单捕获 this
通过引用捕获 *this
对象 (+),根据 [expr.prim.lambda.capture]/12:
(+) simple-capture:s this
和 *this
都表示本地实体 *this
, 根据 [expr.prim.lambda.capture]/4.
An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference. [...]
因此:
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
格式错误的原因:
In the body of a non-static ([class.mfct]) member function, the keyword
this
is a prvalue whose value is a pointer to the object for which the function is called. The type ofthis
in a member function whose type has a cv-qualifier-seq cv and whose class isX
is “pointer to cv X”. [...]
根据 [expr.prim.lambda.closure]/12,这也适用于在 lambda 中使用 this
的情况:
The lambda-expression's compound-statement yields the function-body ([dcl.fct.def]) of the function call operator, but for purposes of name lookup, determining the type and value of
this
and transforming id-expressions referring to non-static class members into class member access expressions using (*this) ([class.mfct.non-static]), the compound-statement is considered in the context of the lambda-expression.
MSVC 接受您的代码段是错误的(接受无效)。
事实上,在下面的例子中 (demo):
#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,如:
[...] The type of
this
in a member function whose type has a cv-qualifier-seq cv and whose class isX
is “pointer to cv X” [...]
... 而不是“指向 cv X 的常量指针”(首先,纯右值的常量性很奇怪)。