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-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))
Senderアルゴリズムタグ let-cpo
Senderアルゴリズム動作説明用のクラステンプレートimpls-forに対して、下記の特殊化が定義される。
namespace std::execution {
template<>
struct impls-for<decayed-typeof<let-cpo>> : default-impls {
static constexpr auto get-state = see below;
static constexpr auto complete = see below;
template<class Sndr, class... Env>
static consteval void check-types();
};
}
impls-for<decayed-typeof<let-cpo>>::get-stateメンバは、下記ラムダ式と等価な関数呼び出し可能なオブジェクトで初期化される。
args_variant_t: 入力Sendersndrの完了シグネチャ集合から求まる送信値リスト型情報(variant<monostate, tuple<...>, ...>)ops2_variant_t:fが返すSenderに対応する非同期操作型情報(variant<monostate, {OperationState型}, ...>)- 戻り値
state-type型オブジェクトを下記の通り初期化する。同オブジェクトはcompleteメンバから呼ばれるlet-bindで利用される。
[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) requires see below {
auto& [_, fn, child] = sndr;
using fn_t = decay_t<decltype(fn)>;
using env_t = decltype(let-env(child));
using args_variant_t = see below;
using ops2_variant_t = see below;
struct state-type {
fn_t fn; // exposition only
env_t env; // exposition only
args_variant_t args; // exposition only
ops2_variant_t ops2; // exposition only
};
return state-type{allocator-aware-forward(std::forward_like<Sndr>(fn), rcvr),
let-env(child), {}, {}};
}
-
説明用のパック
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>&...>と定義する。型ops2_variant_tは下記定義において重複削除した型となる。variant<monostate, connect_result_t<as-sndr2<LetSigs>, receiver2<Rcvr, env_t>>...> -
型
args_variant_tおよびops2_variant_tが適格なときに限って、上記ラムダ式のrequires節が満たされる。
impls-for<decayed-typeof<let-cpo>>::completeメンバは、下記ラムダ式と等価な関数呼び出し可能なオブジェクトで初期化される。
- 完了関数
set-cpoの場合、Sender構築時の引数fに対してf(args...)を呼び出し、戻り値Senderから入れ子非同期操作を開始する。同Senderの完了結果を接続先Receiverへ転送する。 - それ以外の完了操作の場合、接続先Receiverの同種完了関数へ転送する。
[]<class Tag, class... Args>
(auto, auto& state, auto& rcvr, Tag, Args&&... args) noexcept -> void {
if constexpr (same_as<Tag, decayed-typeof<set-cpo>>) {
TRY-EVAL(rcvr, let-bind(state, rcvr, std::forward<Args>(args)...));
} else {
Tag()(std::move(rcvr), std::forward<Args>(args)...);
}
}
メンバ関数impls-for<decayed-typeof<let-cpo>>::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){}));
unspecified-exceptionはexceptionから派生した型となる。
説明用の変数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...>
説明用の式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)は下記と等価。
説明専用エンティティ
説明用の式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<>{})
説明専用のlet-bindテンプレート関数を下記の通り定義する。
- 入力Senderの完了結果から引数リスト
state.argsを構築し、Senderアルゴリズム構築時に指定した関数呼び出し可能オブジェクトstate.fnを呼び出す。 - 上記呼び出しで
state.fnから返されたSenderと、完了結果をSenderアルゴリズムの接続先ReceiverRcvrへ転送するヘルパreceiver2を接続(connect)する。 - 接続結果Operation Stateを
state.op2上に構築し、入れ子の非同期操作を開始(start)する。
namespace std::execution {
template<class State, class Rcvr, class... Args>
void let-bind(State& state, Rcvr& rcvr, Args&&... args); // exposition only
}
let-bindテンプレート関数の効果は下記と等価。
using args_t = decayed-tuple<Args...>;
auto mkop2 = [&] {
return connect(
apply(std::move(state.fn),
state.args.template emplace<args_t>(std::forward<Args>(args)...)),
receiver2{rcvr, std::move(state.env)});
};
start(state.ops2.template emplace<decltype(mkop2())>(emplace-from{mkop2}));
説明専用のテンプレートクラス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