std::execution::let_value
| Defined in header <execution>
|
||
struct let_value_t { /*unspecified*/ };
|
(1) | (since C++26) |
inline constexpr let_value_t let_value{};
|
(2) | (since C++26) (customization point object) |
| Call signature |
||
execution::sender auto let_value( execution::sender auto sndr,
/* movable value */ auto f );
|
(since C++26) | |
Continues a sender with more asynchronous work that depends on its result: when sndr completes with values, f is called with them, and f returns a sender. That sender is started, and its completion becomes the completion of the returned sender.
This is the asynchronous counterpart of "call a function with the result, and that function does more asynchronous work". Use then when the next step is a plain function that returns a value; use let_value when the next step is itself asynchronous, or when which asynchronous step comes next depends on the value.
let_value is a pipeable sender adaptor: sndr | let_value(f) is equivalent to let_value(sndr, f).
Parameters
| sndr | - | the sender whose values are passed to f
|
| f | - | a function (or other callable) called with the values sent by sndr, which returns a sender
|
Return value
A sender that behaves like sndr until sndr completes with values. Then:
fis called with the values, and the sender it returns is started.- The returned sender completes the way that inner sender does: with its values, with its error, or stopped.
- If
fthrows, the returned sender completes with the exception as an error (set_errorwith a std::exception_ptr).
Error and stopped completions of sndr are forwarded unchanged: f is not called for them. Use let_error and let_stopped to continue with a sender after those.
let_value(sndr, f) is ill-formed if sndr does not satisfy execution::sender or f cannot be copied or moved into the sender (it must be a movable value, and not an array). It is also diagnosed at compile time if, for some value completion of sndr, f cannot be called with the values as lvalues or does not return a sender.
Customization point objects
The name execution::let_value denotes a customization point object, which is a const function object of a literal semiregular class type. The cv-unqualified version of its type is a tag type denoted as execution::let_value_t. See CustomizationPointObject for details.
Details
- Nothing runs when the sender is built.
fis called only after the returned sender has been connected to a receiver and started, and only ifsndrcompletes with values. fis called with the values as lvalues that stay alive until the sender returned byfcompletes. A parameter of typeT&,const T&orTaccepts them; a parameter of typeT&&does not. Because the values outlive the inner sender, the inner sender may keep references to them, which is not possible with a function that only receives temporaries.fis called on the execution agent on whichsndrcompleted with values, and the sender it returns is started there. The sender returned byfsees that scheduler as its start scheduler (get_start_scheduler).- If
sndrcan complete with several different sets of values,fmust be callable with each of them, and must return a sender for each. All of those senders must complete in compatible ways for the result to be useful (for example with the same value type). - The function must return a single type of sender for each set of values: a lambda whose
returnstatements return different sender types does not compile. To choose between different senders at run time, return a sender that can represent both, or select within one sender (for example by branching inside a then).
Notes
- The sender returned by
let_valuesends what the inner sender sends. Unlikethen, whose result is a plain value,let_valuedoes not wrap the result off: iffreturnsjust(x), the result isx, not a sender. - The lifetime guarantee is the main reason to prefer
let_valueover capturing values yourself: the values sent bysndrstay alive, stored in the operation, until the sender returned byfcompletes. That is what makes it safe for the inner sender to refer to them. For large values, pass a reference or a handle through the chain instead of copying.
Example
The function gets the id found by the first step and decides what asynchronous work comes next, here a lookup that uses the id. The id stays valid for the whole inner operation. Nothing runs until sync_wait starts the sender. The standard header is not available in the online compilers yet, so this link runs the example on Compiler Explorer with stdexec, the reference implementation.
#include <execution>
#include <print>
#include <string>
#include <thread>
namespace ex = std::execution;
// Stand-ins for asynchronous operations (for example network requests).
ex::sender auto find_user_id(std::string name)
{
return ex::just(name.size()) | ex::then([](std::size_t n) { return static_cast<int>(n); });
}
ex::sender auto load_profile(int id)
{
return ex::just() | ex::then([id] { return std::string("profile of user #") + std::to_string(id); });
}
int main()
{
auto sndr = find_user_id("alice")
| ex::let_value([](int& id)
{
std::println("found id {}, loading profile", id);
return load_profile(id);
});
std::println("sender built, nothing has run yet");
auto [profile] = std::this_thread::sync_wait(std::move(sndr)).value();
std::println("{}", profile);
}
Output:
sender built, nothing has run yet
found id 5, loading profile
profile of user #5
See also
(C++26) |
chains a function onto a sender, calling it with the values the sender sends and sending its result on (customization point object) |
(C++26) |
chains a function onto a sender, calling it with the error if the sender fails and continuing with the sender it returns (customization point object) |
(C++26) |
chains a function onto a sender, calling it if the sender is cancelled and continuing with the sender it returns (customization point object) |