Date: Wed, 1 Oct 2025 10:19:50 -0400
On Wed, Oct 1, 2025 at 2:44 AM Rhidian De Wit <rhidiandewit_at_[hidden]>
wrote:
> Hi Arthur,
>
> Thanks for the response, I'll take a look at making a PR in the Core
> Guidelines to clarify the example a bit further.
>
> I wasn't very familiar yet with the explicit parameter object, especially
> not in lambdas, and after reading a bit about I did find that you can refer
> to the captured variables in a lambda by using the `self` parameter you
> declare: https://godbolt.org/z/9d1oPv9fG
> Now, this is pretty painful syntax, as we'd constantly have to type
> `std::forward_like<decltype(self)>(value)` to be able to use our values
> safely.
>
The good news: you don't have to, and should not, use
`std::forward_like<decltype(self)>`, because `self` is not a forwarding
reference. Never forward things that aren't forwarding references!
Because `decltype(self)` is a prvalue type (namely `Lambda`), therefore
`std::forward<decltype(self)>(...)` expands to
`static_cast<Lambda&&>(...)`, and `std::forward_like<decltype(self)>(...)`
expands to basically `std::move(...)`.
The better news: Hm, you seem to have discovered that lambdas with explicit
`this` parameters *already* work the way I was hoping they could be made to!
auto lambda = [x=2](this auto self) { return &x; };
int *px = lambda(); // return a (dangling) pointer into `self`'s
captures, *not* a pointer into `lambda`'s captures!
Godbolt: https://godbolt.org/z/Gve3frTh3
Looks like this comes from
https://eel.is/c++draft/expr.prim.id#unqual-4.sentence-1 (says what *type*
an expression like `x` has, in terms of the type of `self`; but not what
*value* it has)
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html#wording-in-expr
I can't find where this is normatively specified; the closest I've gotten
so far is
https://eel.is/c++draft/expr.prim.lambda#capture-11
"Every id-expression within the compound-statement of a lambda-expression
[...] is transformed into an access to the corresponding unnamed data
member of the closure type" — but this seems like a category error to me;
it should be saying something about the corresponding unnamed data member
of some particular *object* (not some *type*), and in our example above, we
need to ensure that that particular object is specifically the by-value
parameter `self` and not some other object. And:
"the type of [an expression such as `x`] is the type of a class member
access expression naming the non-static data member that would be declared
for such a capture in the object parameter of the function call operator of
E" — but this describes only the expression's *type*; it doesn't specify
how to compute the expression's *value*.
But, on the assumption that it's supposed to work this way — and all three
compiler vendors seem to agree it is —
it looks like your problem can be solved by simply adding `(this auto)` to
your coroutine lambdas' signatures. (You don't need to, and therefore
probably shouldn't, give that parameter any name.) It might even be worth
creating a macro something like
#define DEFERRED this auto
so such lambdas really stand out:
int x = 1;
auto lambda = [&](DEFERRED, int y) -> Deferred { co_return x + y; };
co_await lambda();
–Arthur
>
wrote:
> Hi Arthur,
>
> Thanks for the response, I'll take a look at making a PR in the Core
> Guidelines to clarify the example a bit further.
>
> I wasn't very familiar yet with the explicit parameter object, especially
> not in lambdas, and after reading a bit about I did find that you can refer
> to the captured variables in a lambda by using the `self` parameter you
> declare: https://godbolt.org/z/9d1oPv9fG
> Now, this is pretty painful syntax, as we'd constantly have to type
> `std::forward_like<decltype(self)>(value)` to be able to use our values
> safely.
>
The good news: you don't have to, and should not, use
`std::forward_like<decltype(self)>`, because `self` is not a forwarding
reference. Never forward things that aren't forwarding references!
Because `decltype(self)` is a prvalue type (namely `Lambda`), therefore
`std::forward<decltype(self)>(...)` expands to
`static_cast<Lambda&&>(...)`, and `std::forward_like<decltype(self)>(...)`
expands to basically `std::move(...)`.
The better news: Hm, you seem to have discovered that lambdas with explicit
`this` parameters *already* work the way I was hoping they could be made to!
auto lambda = [x=2](this auto self) { return &x; };
int *px = lambda(); // return a (dangling) pointer into `self`'s
captures, *not* a pointer into `lambda`'s captures!
Godbolt: https://godbolt.org/z/Gve3frTh3
Looks like this comes from
https://eel.is/c++draft/expr.prim.id#unqual-4.sentence-1 (says what *type*
an expression like `x` has, in terms of the type of `self`; but not what
*value* it has)
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html#wording-in-expr
I can't find where this is normatively specified; the closest I've gotten
so far is
https://eel.is/c++draft/expr.prim.lambda#capture-11
"Every id-expression within the compound-statement of a lambda-expression
[...] is transformed into an access to the corresponding unnamed data
member of the closure type" — but this seems like a category error to me;
it should be saying something about the corresponding unnamed data member
of some particular *object* (not some *type*), and in our example above, we
need to ensure that that particular object is specifically the by-value
parameter `self` and not some other object. And:
"the type of [an expression such as `x`] is the type of a class member
access expression naming the non-static data member that would be declared
for such a capture in the object parameter of the function call operator of
E" — but this describes only the expression's *type*; it doesn't specify
how to compute the expression's *value*.
But, on the assumption that it's supposed to work this way — and all three
compiler vendors seem to agree it is —
it looks like your problem can be solved by simply adding `(this auto)` to
your coroutine lambdas' signatures. (You don't need to, and therefore
probably shouldn't, give that parameter any name.) It might even be worth
creating a macro something like
#define DEFERRED this auto
so such lambdas really stand out:
int x = 1;
auto lambda = [&](DEFERRED, int y) -> Deferred { co_return x + y; };
co_await lambda();
–Arthur
>
Received on 2025-10-01 14:20:07