namespace std::execution {
struct spawn_future_t { unspecified };
inline constexpr spawn_future_t spawn_future{};
}
概要
spawn_futureは、入力Senderに対して非同期トークンスコープへの関連付けを試み、成功時に入力Senderを早期開始(eagerly start)させるSenderアダプタである。
戻り値Senderの接続(connect)および開始(start)により、早期開始した入力Senderの戻り値を用いて値完了、もしくは入力Senderが開始していなければset_stoppedで完了する。
効果
説明用の式sndr, token, envを下記の通り定義する。
Sndr型をdecltype((sndr))、Token型をremove_cvref_t<decltype((token))>、Env型をremove_cvref_t<decltype((env))>とする。
sender<Sndr>, scope_token<Token>, queryable<Env>のいずれかを満たさないとき、呼び出し式spawn_future(sndr, token, env)は不適格となる。
そうでなければ、呼び出し式spawn_future(sndr, token, env)に対して、説明用の式new_senderをtoken.wrap(sndr)とし、allocとsenvを次の通りとする。
- 式
get_allocator(env)が適格なとき、allocをget_allocator(env)の結果、senvを式envとする。 - そうでなはく、式
get_allocator(get_env(new_sender))が適格なとき、allocをget_allocator(get_env(new_sender))の結果、senvを式JOIN-ENV(prop(get_allocator, alloc), env)とする。 - そうではないとき、
allocをallocator<void>()、senvを式envとする。
呼び出し式spawn_future(sndr, token, env)は、次の効果をもつ。
allocを用いてメモリ確保し、alloc,token.wrap(sndr),token,senvから特殊化されたspawn-future-state型のオブジェクトsを構築する。いずれかのオブジェクト構築・破棄時に例外送出されたときは、確保されたメモリが解放される。- 下記を満たす
unique_ptrの特殊化型のオブジェクトuを構築する。u.get()はsのアドレスに等しく、かつu.get_deleter()(u.release())はu.release()->abandon()に等しい。
make-sender(spawn_future, std::move(u))を返す。
呼び出し式spawn_future(sndr, token)は、式spawn_future(sndr, token, execution::env<>())と等価である。
Senderアルゴリズムタグ spawn_future
Senderアルゴリズム動作説明用のクラステンプレートimpls-forに対して、下記の特殊化が定義される。
namespace std::execution {
template<>
struct impls-for<spawn_future_t> : default-impls {
static constexpr auto start = see below; // exposition only
};
}
impls-for<spawn_future_t>::startメンバは、下記ラムダ式と等価な関数呼び出し可能なオブジェクトで初期化される。
[](auto& state, auto& rcvr) noexcept -> void {
state->consume(rcvr);
}
説明専用エンティティ
クラステンプレートspawn-future-state-base
namespace std::execution {
template<class Completions>
struct spawn-future-state-base; // exposition only
template<class... Sigs>
struct spawn-future-state-base<completion_signatures<Sigs...>> { // exposition only
using variant-t = see below; // exposition only
variant-t result; // exposition only
virtual void complete() noexcept = 0; // exposition only
};
}
説明用のパックSigsを、クラステンプレートspawn-future-state-baseのパラメータに指定するcompletion_signatures特殊化の引数パックと定義する。説明用のエイリアステンプレートas-tuple<Tag(Args...)>をdecayed-tuple<Tag, Args...>と定義する。
Sigsに含まれる全ての完了シグニチャTag(Args...)における全てのパラメータパックArgsの全ての型Anyに対してis_nothrow_constructible_v<decay_t<Arg>, Arg> == trueのとき、エイリアスvariant-tはvariant<monostate, tuple<set_stopped_t>, as-tuple<Sigs>...>において重複削除した型となる。- そうではないとき、エイリアス
variant-tはvariant<monostate, tuple<set_stopped_t>, tuple<set_error_t, exception_ptr>, as-tuple<Sigs>...>において重複削除した型となる。
クラステンプレートspawn-future-receiver
namespace std::execution {
template<class Completions>
struct spawn-future-receiver { // exposition only
using receiver_concept = receiver_t;
spawn-future-state-base<Completions>* state; // exposition only
template<class... T>
void set_value(T&&... t) && noexcept {
set-complete<set_value_t>(std::forward<T>(t)...);
}
template<class E>
void set_error(E&& e) && noexcept {
set-complete<set_error_t>(std::forward<E>(e));
}
void set_stopped() && noexcept {
set-complete<set_stopped_t>();
}
private:
template<class CPO, class... T>
void set-complete(T&&... t) noexcept { // exposition only
constexpr bool nothrow = (is_nothrow_constructible_v<decay_t<T>, T> && ...);
try {
state->result.template emplace<decayed-tuple<CPO, T...>>(CPO{},
std::forward<T>(t)...);
}
catch (...) {
if constexpr (!nothrow) {
using tuple_t = decayed-tuple<set_error_t, exception_ptr>;
state->result.template emplace<tuple_t>(set_error_t{}, current_exception());
}
}
state->complete();
}
};
}
型ssource-tをstoppable-sourceのモデルである未規定の型、ssourceを型ssource-tの左辺値とする。
型stoken-tをdecltype(ssource.get_token())とする。
エイリアステンプレート future-spawned-sender
template<sender Sender, class Env>
using future-spawned-sender = // exposition only
decltype(write_env(stop-when(declval<Sender>(), declval<stoken-t>()), declval<Env>()));
クラステンプレートspawn-future-state
namespace std::execution {
template<class Alloc, scope_token Token, sender Sender, class Env>
struct spawn-future-state // exposition only
: spawn-future-state-base<completion_signatures_of_t<future-spawned-sender<Sender, Env>>> {
using sigs-t = // exposition only
completion_signatures_of_t<future-spawned-sender<Sender, Env>>;
using receiver-t = // exposition only
spawn-future-receiver<sigs-t>;
using op-t = // exposition only
connect_result_t<future-spawned-sender<Sender, Env>, receiver-t>;
spawn-future-state(Alloc alloc, Sender&& sndr, Token token, Env env) // exposition only
: alloc(std::move(alloc)),
op(connect(
write_env(stop-when(std::forward<Sender>(sndr), ssource.get_token()), std::move(env)),
receiver-t(this))),
token(std::move(token)),
associated(token.try_associate()) {
if (associated)
start(op);
else
set_stopped(receiver-t(this));
}
void complete() noexcept override; // exposition only
void consume(receiver auto& rcvr) noexcept; // exposition only
void abandon() noexcept; // exposition only
private:
using alloc-t = // exposition only
typename allocator_traits<Alloc>::template rebind_alloc<spawn-future-state>;
alloc-t alloc; // exposition only
ssource-t ssource; // exposition only
op-t op; // exposition only
Token token; // exposition only
bool associated; // exposition only
void destroy() noexcept; // exposition only
};
}
データ競合の存在を判定する目的において、complete, consume, abandonはアトミック操作として振る舞う。
spawn-future-stateの特殊化である型の単一オブジェクトに対するこれらの操作は、単一の全順序で発生するように見える。
void complete() noexcept;
- 効果 :
void consume(receiver auto& rcvr) noexcept;
- 効果 :
*thisに対するこのconsumeの呼び出しがcompleteの呼び出しよりも前に発生するならば、*thisに対してその後completeが呼び出されるときrcvrが完了するよう登録される。-
そうではないとき、下記のように
rcvrが完了する :
void abandon() noexcept;
-
効果 :
-
*thisに対するこのabandonの呼び出しがcompleteの呼び出しより前に発生するならば、下記と等価。ssource.request_stop(); -
そうでなければ、
destroyが呼び出される。
-
void destroy() noexcept;
-
効果 : 下記と等価。
auto token = std::move(this->token); bool associated = this->associated; { auto alloc = std::move(this->alloc); allocator_traits<alloc-t>::destroy(alloc, this); allocator_traits<alloc-t>::deallocate(alloc, this, 1); } if (associated) token.disassociate();
例
#include <print>
#include <execution>
namespace ex = std::execution;
int main()
{
// システムスレッドプール上の実行タスクを定義
ex::scheduler auto sch = ex::get_parallel_scheduler();
ex::sender auto snd0 =
ex::schedule(sch)
| ex::then([]{
// ワーカースレッド処理
std::println("hello async");
return 42;
});
// 非同期スコープを定義
ex::counting_scope scope;
// タスクを早期開始させる
std::println("spawn");
ex::sender auto snd1 = ex::spawn_future(std::move(snd0), scope.get_token());
// (メインスレッド処理)
// 非同期スコープを合流
auto result = std::this_thread::sync_wait(ex::when_all(std::move(snd1), scope.join()));
auto [value] = *result;
std::println("value={}", value);
}
出力
spawn
hello async
value=42
バージョン
言語
- C++26
処理系
- Clang: ??
- GCC: ??
- ICC: ??
- Visual C++: ??