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;
};
}
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{std::forward_like<Sndr>(fn), let-env(child), {}, {}};
}
-
説明用のパック
Sigs
をcompletion_signatures_of_t<child-type<Sndr>, 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>>...>
-
型
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)...);
}
}
説明用の式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)
はJOIN-ENV(let-env(sndr), FWD-ENV(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)
が有効ならばその式と等価。そうでなければ、式e.query(q)
はget_env(rcvr).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++: ??