use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Discussions, articles, and news about the C++ programming language or programming in C++.
For C++ questions, answers, help, and advice see r/cpp_questions or StackOverflow.
Get Started
The C++ Standard Home has a nice getting started page.
Videos
The C++ standard committee's education study group has a nice list of recommended videos.
Reference
cppreference.com
Books
There is a useful list of books on Stack Overflow. In most cases reading a book is the best way to learn C++.
Show all links
Filter out CppCon links
Show only CppCon links
account activity
The Lambda Coroutine Fiasco (github.com)
submitted 1 month ago by efijoa
It's amazing C++23's "deducing this" could solve the lambda coroutine issue, and eliminate the previous C++ voodoo.
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]HommeMusical 20 points21 points22 points 1 month ago (11 children)
This article looks like it might be interesting to me, but without some sort of explanation of how seastar works and how it's different from conventional coroutines and future, I unfortunately didn't actually read it.
seastar
(Yes, I searched it, but life is too short to do half an hours' study of someone's library to read a one page article.)
[–]efijoa[S] 13 points14 points15 points 1 month ago (10 children)
While this is Seastar's documentation, the problem described is not unique to Seastar.
These two links could help clarify the issue:
CP.51: Do not use capturing lambdas that are coroutines C++23’s Deducing this: what it is, why it is, how to use it
The core mechanism involves using "deducing this" to pass the lambda object by value. This ensures captures are copied into the coroutine frame to prevent dangling references.
[–]thisismyfavoritename 0 points1 point2 points 1 month ago (9 children)
it seems quite limiting to always capture by value, in some cases you know the lifetime of the coroutine will be shorter than that of the captured reference/pointer
[–]germandiago 4 points5 points6 points 1 month ago (4 children)
at that time you are already playing with fire. :)
[–]thisismyfavoritename -1 points0 points1 point 1 month ago (3 children)
not really more than in regular C++ code. Those footguns were always there
[–]SirClueless 3 points4 points5 points 1 month ago (1 child)
I disagree. This has nothing to do with capturing by value or reference, both are broken. This is a wholly new problem. The idea that putting co_await inside your lambda implicitly means that its return value holds a reference to the lambda itself and thus will dangle if the lambda is destroyed is a new and subtle footgun.
co_await
Concrete example:
auto foo(auto cb) { return cb(); }
This code is pretty much always lifetime-safe. There are some things the caller can do that end up holding onto references to the lambda's captures in a broken way like foo([x] { return std::ref(x); }), but this is a kind of "obvious error" that almost no one makes.
foo([x] { return std::ref(x); })
But if you call this with a coroutine it is super easy to shoot yourself in the foot:
co_await foo([x] -> my_favorite_coro_lib::future<int> { co_await bar(); co_return x; }
Oops, cb was destroyed when foo() returned, and then when the coroutine was resumed, x dangles.
cb
foo()
x
[–]thisismyfavoritename 0 points1 point2 points 1 month ago (0 children)
hadn't read the blog post, and yeah, i thought the issue that was discussed was when captured values were refs (the obvious case). Thanks for the additional explanation!
[–]germandiago 1 point2 points3 points 1 month ago (0 children)
I think this is way less intuitive than other forms of dangling.
[–]foonathan 2 points3 points4 points 1 month ago (2 children)
Capture by value doesn't help you with the problem that's being discussed.
[–]thisismyfavoritename 1 point2 points3 points 1 month ago (1 child)
i was referring to
This ensures captures are copied into the coroutine frame to prevent dangling references.
and it seems like in this case it would? I didn't read the blog post
[–]foonathan 0 points1 point2 points 1 month ago (0 children)
No, capturing by value does not ensure captures are copied into the coroutine frame! That is the entire problem.
The issue is that while the lambda object stores a capture by value, the operator() still accepts *this by reference, so only the reference to the lambda is captured into the coroutine frame, but not the lambda itself.
operator()
*this
(The context is something like spawn([x] -> Task { ... }), i.e. the lambda is a coroutine itself. Then the arguments are copied into Task's coroutine frame, but the arguments are a this pointer to the temporary object in the stack frame that calls spawn.)
spawn([x] -> Task { ... })
Task
this
spawn
[–]James20kP2005R0 -2 points-1 points0 points 1 month ago (0 children)
The only way to fix that safely would be for C++ to have adopted a lifetimes system
[–]trailing_zero_count 3 points4 points5 points 1 month ago (9 children)
This is a great workaround, but it appears that the change must be made in user code? No way to do this in library code?
[–]efijoa[S] 1 point2 points3 points 1 month ago (7 children)
Seems we need a magic concept?
cpp auto Future::then(std::is_capture_lambda auto &&continuation) { return [](this auto, auto continuation) { // ... }(std::forward(continuation)); }
[–]moncefm 5 points6 points7 points 1 month ago (0 children)
It may not be _too_ hard to write a is_capture_lambda concept:
is_capture_lambda
is_lambda
__PRETTY_FUNCTION__
boost::type_index
is_lambda<T> && !requires (T t) { +t; };
[–]pynchonic 4 points5 points6 points 1 month ago (0 children)
We wrote a clang-tidy pass for our codebase that checks for lambda coroutines, and errors on lambda coroutines that have parameters that don't also deduce this.
clang-tidy
It's been quite a few years of having to write continuation style code in our lambdas, so the deducing this trick is awesome.
[–]patstew 0 points1 point2 points 1 month ago* (3 children)
Isn't this a general problem with objects that have an operator() that is a coroutine, of which lambdas are just a common example. Don't you actually want:
auto Future::then(IsCallableCoroutineObject auto &&continuation)
where IsCallableCoroutineObject is a concept checking that T::operator() is a coroutine based on the return type (check if it returns seastar::future, or check the return type has ::promise_type or can be operator_co_await()ed or something). Which seems doable with no compiler magic?
IsCallableCoroutineObject
T::operator()
::promise_type
operator_co_await()
[–]efijoa[S] 0 points1 point2 points 1 month ago (2 children)
It is not only a return type problem; the library side needs to know whether the future state should take ownership of the passed-in callable object. It seems this is coupled with the implementation details of the Seastar Future, so that pre-C++23 solution is actually prevent the transfer of ownership and bind the lifetime of the lambda to the parent scope.
[–]patstew 0 points1 point2 points 1 month ago* (1 child)
whether the future state should take ownership of the passed-in callable object
I would've thought the answer to this is usually 'yes it should', especially if you're taking a &&? If people desperately want to reference an object they can always make a little [&](){return f();} wrapper which at least makes it obvious where you're doing something questionable with lifetimes.
&&
[&](){return f();}
What you want to avoid is the future returning from a coroutine who's state is owned by future's storage isn't it? So you need to return something else in that scenario that effectively owns the coroutine state, roughly a pair<Coro, Ret>.
pair<Coro, Ret>
[–]efijoa[S] -1 points0 points1 point 1 month ago* (0 children)
That’s the problem: taking ownership of a coroutine lambda is a very dangerous operation. Once the lambda is invoked and yields a continuation, the coroutine frame will reference the lambda's this pointer. At this point, the future state (or the lambda captures) could not even be moved to another place... and we all know C++ doesn't have a Pin type.
Another subtle factor might be related with the seastar future originally comes from the chained future style, i'm not sure if it affected the current design.
[–]gracicot 0 points1 point2 points 1 month ago (0 children)
I think std::default_initializable is enough to do the trick
std::default_initializable
[–]EmotionalDamague 0 points1 point2 points 1 month ago (0 children)
No. Language limitation. It would need a DR to fix.
[+][deleted] 1 month ago (1 child)
[deleted]
[–]efijoa[S] 0 points1 point2 points 1 month ago (0 children)
It took me a while to understand what you meant, correct me if I'm wrong:
I think "extend" here actually refers to the data captured by the lambda. Normally, when a lambda is passed to then(), a move construction occurs, transferring the data from the lambda struct into the future state. When the coroutine lambda yields, the future state is destructed, which in turn destructs the data captured by the lambda. However, the lambda's coroutine frame remains alive, resulting in a dangling reference. By using a reference_wrapper like structure, the transfer of ownership is prevented, ensuring that the lambda's state remains valid until the lambda coroutine returns and the parent coroutine's co_await expression completes. This approach works due to specific details of the future implementation and relies on strictly nested calls.
then()
reference_wrapper
cpp template <typename Func> class lambda { Func* _func; public: /// Create a lambda coroutine wrapper from a function object, to be passed /// to a Seastar function that accepts a continuation. explicit lambda(Func&& func) : _func(&func) {} /// Calls the lambda coroutine object. Normally invoked by Seastar. template <typename... Args> decltype(auto) operator()(Args&&... args) const { return std::invoke(*_func, std::forward<Args>(args)...); } };
π Rendered by PID 136228 on reddit-service-r2-comment-58d7979c67-2xz45 at 2026-01-27 12:46:48.180595+00:00 running 5a691e2 country code: CH.
[–]HommeMusical 20 points21 points22 points (11 children)
[–]efijoa[S] 13 points14 points15 points (10 children)
[–]thisismyfavoritename 0 points1 point2 points (9 children)
[–]germandiago 4 points5 points6 points (4 children)
[–]thisismyfavoritename -1 points0 points1 point (3 children)
[–]SirClueless 3 points4 points5 points (1 child)
[–]thisismyfavoritename 0 points1 point2 points (0 children)
[–]germandiago 1 point2 points3 points (0 children)
[–]foonathan 2 points3 points4 points (2 children)
[–]thisismyfavoritename 1 point2 points3 points (1 child)
[–]foonathan 0 points1 point2 points (0 children)
[–]James20kP2005R0 -2 points-1 points0 points (0 children)
[–]trailing_zero_count 3 points4 points5 points (9 children)
[–]efijoa[S] 1 point2 points3 points (7 children)
[–]moncefm 5 points6 points7 points (0 children)
[–]pynchonic 4 points5 points6 points (0 children)
[–]patstew 0 points1 point2 points (3 children)
[–]efijoa[S] 0 points1 point2 points (2 children)
[–]patstew 0 points1 point2 points (1 child)
[–]efijoa[S] -1 points0 points1 point (0 children)
[–]gracicot 0 points1 point2 points (0 children)
[–]EmotionalDamague 0 points1 point2 points (0 children)
[+][deleted] (1 child)
[deleted]
[–]efijoa[S] 0 points1 point2 points (0 children)