C++26stlexpert
std::execution::then, when_all, let_value (C++26 Addition)
C++26 · The Next Frontier · Compiler flag: -std=c++26
Explanation
Sender adaptors for composing asynchronous operations. `then` chains a continuation. `when_all` waits for multiple senders to complete. `let_value` creates a new sender based on the result of a previous one, enabling dynamic async composition.
Real-World Scenario
Fetch data from multiple services concurrently, then combine the results once all are available.
Quick Snippet
snippet.cpp
#include <execution>
namespace ex = std::execution;
auto fetch_a = ex::schedule(sched) | ex::then([] { return fetch_service_a(); });
auto fetch_b = ex::schedule(sched) | ex::then([] { return fetch_service_b(); });
// Wait for both, then combine
auto combined = ex::when_all(std::move(fetch_a), std::move(fetch_b))
| ex::then([](auto a, auto b) { return merge(a, b); });
// Dynamic composition with let_value
auto dynamic = ex::just(42) | ex::let_value([](int x) {
return ex::just(x * 2); // Create a new sender based on the value
});Full Example
execution_adaptors.cpp
// CodeNebula - C++26 Example: then, when_all, let_value Adaptors
// Compile: g++ -std=c++26 -Wall -Wextra -o example execution_adaptors.cpp
// Note: Requires C++26 compiler support
#include <iostream>
#include <functional>
#include <string>
#include <tuple>
// C++26 std::execution provides sender adaptors to build async pipelines:
//
// then(sender, f) - transforms the result: f(value) -> new_value
// when_all(s1, s2, ...) - waits for all senders, delivers tuple of results
// let_value(sender, f) - like then, but f returns a new sender (flatmap)
namespace exec_sim {
template <typename T>
struct just_sender { T value; };
template <typename T>
just_sender<T> just(T v) { return {std::move(v)}; }
// then: transform the value
template <typename S, typename F>
auto then(S s, F f) {
auto result = f(s.value);
return just(result);
}
// when_all: combine multiple senders into one producing a tuple
template <typename S1, typename S2>
auto when_all(S1 s1, S2 s2) {
return just(std::make_tuple(s1.value, s2.value));
}
template <typename S1, typename S2, typename S3>
auto when_all(S1 s1, S2 s2, S3 s3) {
return just(std::make_tuple(s1.value, s2.value, s3.value));
}
// let_value: flatmap — function returns a sender
template <typename S, typename F>
auto let_value(S s, F f) {
return f(s.value); // f returns a sender
}
// sync_wait: extract the value
template <typename T>
T sync_wait(just_sender<T> s) { return s.value; }
}
int main() {
std::cout << "--- then adaptor ---\n";
auto s1 = exec_sim::then(exec_sim::just(7), [](int v) { return v * 3; });
std::cout << "just(7) | then(*3) = " << exec_sim::sync_wait(s1) << "\n";
std::cout << "\n--- when_all adaptor ---\n";
auto combined = exec_sim::when_all(
exec_sim::just(10),
exec_sim::just(20),
exec_sim::just(30)
);
auto [a, b, c] = exec_sim::sync_wait(combined);
std::cout << "when_all(10, 20, 30) = (" << a << ", " << b << ", " << c << ")\n";
std::cout << "\n--- let_value adaptor ---\n";
// let_value: the function receives a value and returns a new sender
auto lv = exec_sim::let_value(exec_sim::just(5), [](int v) {
// Based on v, produce a new computation
return exec_sim::then(exec_sim::just(v), [](int x) { return x * x + 1; });
});
std::cout << "let_value(5, x -> x*x+1) = " << exec_sim::sync_wait(lv) << "\n";
std::cout << "\n--- Chained pipeline ---\n";
auto pipeline = exec_sim::then(
exec_sim::then(exec_sim::just(2), [](int v) { return v + 8; }),
[](int v) { return v * v; }
);
std::cout << "just(2) | then(+8) | then(^2) = " << exec_sim::sync_wait(pipeline) << "\n";
return 0;
}
// Expected output:
// --- then adaptor ---
// just(7) | then(*3) = 21
//
// --- when_all adaptor ---
// when_all(10, 20, 30) = (10, 20, 30)
//
// --- let_value adaptor ---
// let_value(5, x -> x*x+1) = 26
//
// --- Chained pipeline ---
// just(2) | then(+8) | then(^2) = 100
Run command
g++ -std=c++26 -Wall -Wextra -o example execution_adaptors.cpp