namespace std::execution {
struct let_value_t { unspecified };
inline constexpr let_value_t let_value{};
}
概要
let_valueは、新しいSenderを返す関数呼び出し可能なオブジェクトに引き渡すことで、入力Senderの値完了結果から入れ子の非同期操作へと変換するSenderアダプタである。
let_valueはパイプ可能Senderアダプタオブジェクトであり、パイプライン記法をサポートする。
本ページにてSenderアルゴリズムlet_value/let_error/let_stoppedの動作仕様を包括的に説明するため、以降のセクションにおいてはlet-cpo, set-cpoをそれぞれ下記の通りとする。またlet-tagをlet_value/let_error/let_stoppedそれぞれに対して一意な空のクラスとする。
let-cpo |
set-cpo |
|---|---|
let_value |
set_value |
let_error |
set_error |
let_stopped |
set_stopped |
効果
説明用の式sndrとfに対して、decltype((sndr))がsenderを満たさない、もしくはdecltype((f))がmovable-valueを満たさないとき、呼び出し式let-cpo(sndr, f)は不適格となる。
そうでなければ、呼び出し式let-cpo(sndr, f)はsndrが1回だけ評価されることを除いて、下記と等価。
transform_sender(get-domain-early(sndr), make-sender(let-cpo, f, sndr))
説明用のクラステンプレートlet-dataを下記の通り定義する。
template<class Sndr, class Fn>
struct let-data {
Sndr sndr; // exposition only
Fn fn; // exposition only
};
式let-cpo.transform_sender(s, es...)は、sが1回だけ評価されることを除いて、下記と等価。
make-sender(let-tag{}, let-data{s.template get<2>(), s.template get<1>()})
Senderアルゴリズムタグ let-tag
Senderアルゴリズム動作説明用のクラステンプレートimpls-forに対して、下記の特殊化が定義される。
namespace std::execution {
template<>
struct impls-for<let-tag> : default-impls {
static constexpr auto get-state = see below;
static constexpr auto start = see below;
template<class Sndr, class... Env>
static consteval void check-types();
};
}
impls-for<let-tag>::get-stateメンバは、下記ラムダ式と等価な関数呼び出し可能なオブジェクトで初期化される。
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) requires see below {
auto& [_, data] = sndr;
auto& [child, fn] = data;
using child_t = decltype(std::forward_like<Sndr>(child));
using fn_t = decay_t<decltype(fn)>;
using args_variant_t = see below;
using ops_variant_t = see below;
using state_t = let-state<decayed-typeof<set-cpo>, child_t, fn_t, Rcvr,
args_variant_t, ops_variant_t>;
return state_t(std::forward_like<Sndr>(child), std::forward_like<Sndr>(fn), rcvr);
}
-
説明用のパック
Sigsをcompletion_signatures_of_t<child-type<Sndr>, FWD-ENV-T(env_of_t<Rcvr>)>によるcompletion_signatures特殊化のテンプレートパラメータとし、パックLetSigsをSigsに含まれる型のうち戻り値型がdecayed-typeof<set-cpo>に等しいものと定義する。説明用のエイリアステンプレートas-tuple<Tag(Args...)>をdecayed-tuple<Args...>と定義する。型args_variant_tは下記定義において重複削除した型となる。 -
説明用の型
TagとパックArgsに対して、説明用のエイリアステンプレートas-sndr2<Tag(Args...)>をcall-result-t<Fn, decay_t<Args>&...>と定義する。型ops_variant_tは下記定義において重複削除した型となる。variant<monostate, connect_result_t<child_t, let-state::receiver, connect_result_t<as-sndr2<LetSigs>, receiver2<Rcvr, env_t>>...> -
型
args_variant_tおよびops2_variant_tが適格なときに限って、上記ラムダ式のrequires節が満たされる。
impls-for<let-tag>::startメンバは、下記ラムダ式と等価な関数呼び出し可能なオブジェクトで初期化される。
[]<class State, class Rcvr>(State& state, Rcvr&) noexcept {
start(get<typename State::op_t>(state.ops));
}
メンバ関数impls-for<let-tag>::check-typesの効果は下記の通り。
using LetFn = remove_cvref_t<data-type<Sndr>>;
auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>();
auto fn = []<class... Ts>(decayed-typeof<set-cpo>(*)(Ts...)) {
if constexpr (!is-valid-let-sender) // see below
throw unspecified-exception();
};
cs.for-each(overload-set(fn, [](auto){}));
説明用の式sndrとenvに対して、型Sndrをdecltype((sndr))とする。sender-for<Sndr, decayed-typeof<let-cpo>> == falseのとき、式let-cpo.transform_env(sndr, env)は不適格となる。
そうでなければ、式let-cpo.transform_env(sndr, env)は下記と等価。
説明専用エンティティ
式let-env
説明用の式sndrを用いて、let-env(sndr)を下記リストのうち最初に適格となる式と定義する。
SCHED-ENV(get_completion_scheduler<decayed-typeof<set-cpo>>(get_env(sndr)))MAKE-ENV(get_domain, get_domain(get_env(sndr)))(void(sndr), env<>{})
変数is-valid-let-sender
説明用の変数is-valid-let-senderは下記を全て満たす時に限ってtrueとなる。
(constructible_from<decay_t<Ts>, Ts> &&...)invocable<LetFn, decay_t<Ts>&...>sender<invoke_result_t<LetFn, decay_t<Ts>&...>>- パック
env-tをdecltype(let-cpo.transform_env(declval<Sndr>(), declval<Env>()))としたとき、sizeof...(Env) == 0 || sender_in<invoke_result_t<LetFn, decay_t<Ts>&...>, env-t...>
クラステンプレートlet-state
template<class Cpo, class Sndr, class Fn, class Rcvr, class ArgsVariant,
class OpsVariant>
struct let-state {
using env_t = decltype(let-env(declval<Sndr>())); // exposition only
Fn fn; // exposition only
env_t env; // exposition only
ArgsVariant args; // exposition only
OpsVariant ops; // exposition only
template<class Tag, class... Ts>
constexpr void impl(Rcvr& rcvr, Tag tag, Ts&&... ts) noexcept
{ // exposition only
using args_t = decayed-tuple<Ts...>;
using receiver_type = receiver2<Rcvr, env_t>;
using sender_type = apply_result_t<Fn, args_t&>;
if constexpr (is_same_v<Tag, Cpo>) {
try {
auto& tuple = args.template emplace<args_t>(std::forward<Ts>(ts)...);
ops.template emplace<monostate>();
auto&& sndr = apply(std::move(fn), tuple);
using op_t = connect_result_t<sender_type, receiver_type>;
auto mkop2 = [&] {
return connect(std::forward<sender_type>(sndr),
receiver_type{rcvr, env});
};
auto& op = ops.template emplace<op_t>(emplace-from{mkop2});
start(op);
} catch (...) {
constexpr bool nothrow =
is_nothrow_constructible_v<args_t, Ts...> &&
is_nothrow_applicable_v<Fn, args_t&> &&
noexcept(
connect(
declval<sender_type>(),
receiver_type{rcvr, env}));
if constexpr (!nothrow) {
set_error(std::move(rcvr), current_exception());
}
}
} else {
tag(std::move(rcvr), std::forward<Ts>(ts)...);
}
}
struct receiver { // exposition only
let-state& state; // exposition only
Rcvr& rcvr; // exposition only
using receiver_concept = receiver_t;
template<class... Args>
constexpr void set_value(Args&&... args) noexcept {
state.impl(rcvr, execution::set_value, std::forward<Args>(args)...);
}
template<class... Args>
constexpr void set_error(Args&&... args) noexcept {
state.impl(rcvr, execution::set_error, std::forward<Args>(args)...);
}
template<class... Args>
constexpr void set_stopped(Args&&... args) noexcept {
state.impl(rcvr, execution::set_stopped, std::forward<Args>(args)...);
}
constexpr env_of_t<const Rcvr&> get_env() const noexcept {
return execution::get_env(rcvr);
}
};
using op_t = connect_result_t<Sndr, receiver>; // exposition only
constexpr let-state(Sndr&& sndr, Fn fn, Rcvr& rcvr) // exposition only
: fn(std::move(fn)), env(let-env(sndr)),
ops(in_place_type<op_t>, std::forward<Sndr>(sndr),
receiver{*this, rcvr})
{}
};
クラステンプレートreceiver2
namespace std::execution {
template<class Rcvr, class Env>
struct receiver2 {
using receiver_concept = receiver_t;
template<class... Args>
void set_value(Args&&... args) && noexcept {
execution::set_value(std::move(rcvr), std::forward<Args>(args)...);
}
template<class Error>
void set_error(Error&& err) && noexcept {
execution::set_error(std::move(rcvr), std::forward<Error>(err));
}
void set_stopped() && noexcept {
execution::set_stopped(std::move(rcvr));
}
decltype(auto) get_env() const noexcept {
return see below;
}
Rcvr& rcvr; // exposition only
Env env; // exposition only
};
}
メンバ関数receiver2::get_envの呼び出しは、下記を満たすオブジェクトeを返す。
- 型
decltype(e)がqueryableのモデルであり、かつ - 与えられたクエリオブジェクト
qに対して、式e.query(q)は式env.query(q)が有効ならばその式と等価。そうではなく、qの型がforwarding-queryを満たすならば式e.query(q)はget_env(rcvr).query(q)と等価。そうでなければ、式e.query(q)は不適格となる。
カスタマイゼーションポイント
Senderアルゴリズム構築時およびReceiver接続時に、関連付けられた実行ドメインに対してexecution::transform_sender経由でSender変換が行われる。
デフォルト実行ドメインでは無変換。
説明用の式out_sndrをlet-cpo(sndr, f)の戻り値Senderとし、式rcvrを式connect(out_sndr, rcvr)が適格となるReceiverとする。式connect(out_sndr, rcvr)は開始(start)時に下記を満たす非同期操作を生成しない場合、動作は未定義となる。
- 入力Sender
sndrの完了結果でset-cpoが呼ばれるとき、fを呼び出すこと。 - 非同期操作の完了は、
fが返すSenderの完了に依存すること。 sndrにより送信された他の完了操作を伝搬すること。
例
例1: 基本の使い方
#include <print>
#include <execution>
namespace ex = std::execution;
int main()
{
{ // 関数呼び出し
ex::sender auto snd0 = ex::just(21);
ex::sender auto snd1 = ex::let_value(
snd0,
[](int n) -> ex::sender auto {
return ex::just(n * 2);
});
auto [val] = std::this_thread::sync_wait(snd1).value();
std::println("{}", val);
}
{ // パイプライン記法
ex::sender auto sndr = ex::just(21)
| ex::let_value(
[](int n) -> ex::sender auto {
return ex::just(n * 2);
});
auto [val] = std::this_thread::sync_wait(sndr).value();
std::println("{}", val);
}
}
出力
42
42
例2: 複数の値完了シグネチャ
#include <string>
#include <print>
#include <execution>
namespace ex = std::execution;
// MySenderは下記いずれかの完了操作を行う
// 値完了 set_value(int), set_value(string)
// エラー完了 set_error(int)
struct MySender {
using sender_concept = ex::sender_t;
using completion_signatures = ex::completion_signatures<
ex::set_value_t(int),
ex::set_value_t(std::string),
ex::set_error_t(int)
>;
template <typename Rcvr>
struct state {
using operation_state_concept = ex::operation_state_t;
state(Rcvr rcvr, int val)
: rcvr_{std::move(rcvr)}, val_{val} {}
void start() noexcept {
using namespace std::string_literals;
switch (val_) {
case 1:
ex::set_value(std::move(rcvr_), 100);
break;
case 2:
ex::set_value(std::move(rcvr_), "C++"s);
break;
default:
ex::set_error(std::move(rcvr_), val_);
break;
}
}
Rcvr rcvr_;
int val_;
};
template <typename Rcvr>
auto connect(Rcvr rcvr) noexcept {
return state{std::move(rcvr), val_};
}
int val_;
};
template<typename... Ts>
struct overload : Ts... { using Ts::operator()...; };
int main()
{
for (int val = 0; ; val++) {
ex::sender auto snd0 = MySender{val};
ex::sender auto sndr = ex::let_value(snd0,
overload {
[](int n) {
std::println("(int) {}", n);
// intを受信 -> 空値を送信
return ex::just();
},
[](std::string s) {
std::println("(str) {}", s);
// stringを受信 -> 停止完了(キャンセル)送信
return ex::just_stopped();
}
});
// Senderチェインsndrは下記いずれかの完了操作を行う
// 値完了 set_value()
// エラー完了 set_error(int)
// 停止完了 set_stopped()
try {
auto result = std::this_thread::sync_wait_with_variant(sndr);
// result := optional<variant<tuple<>>>型
if (!result) {
// 停止完了時はstd::nulloptが返却される
break;
}
// 値完了==空値のためアクセスすべきデータ無し
} catch (int n) {
// エラー完了時は受信int値が例外として送出される
std::println("catch {}", n);
}
}
}
出力
catch 0
(int) 100
(str) C++
バージョン
言語
- C++26
処理系
- Clang: ??
- GCC: ??
- ICC: ??
- Visual C++: ??
関連項目
参照
- P2999R3 Sender Algorithm Customization
- P2300R10
std::execution - P3396R1 std::execution wording fixes
- P3433R1 Allocator Support for Operation States
- P3557R3 High-Quality Sender Diagnostics with Constexpr Exceptions
- LWG 4203. Constraints on
get-statefunctions are incorrect - LWG 4204. specification of
as-sndr2(Sig)in [exec.let] is incomplete - LWG 4205.
let_[*].transform_envis specified in terms of thelet_*sender itself instead of its child - P3373R4 Of Operation States and Their Lifetimes