最終更新日時: 2026年06月08日 10時13分52秒 (JST)
yoh が更新

履歴 編集

customization point object
<execution>

# std::execution::when_all(C++26)

    namespace std::execution {
      struct when_all_t { unspecified };
      inline constexpr when_all_t when_all{};
    }

## 概要

`when_all`は、複数の入力Senderが全て完了するまで待機するSenderアダプタである。

`when_all`は全ての入力Senderが値完了シグネチャを1個だけ持つことを要求する。 値完了シグネチャが複数存在する場合は`when_all_with_variant`アルゴリズムを利用する。

- 入力Sender全てが値完了のとき、全ての値完了結果を`tuple`に結合して値完了操作を行う。
- いずれかがエラー完了のとき、同エラー値をもってエラー完了操作を行う。このとき停止要求が作成される。
- いずれかが停止完了のとき、停止完了操作を行う。このとき停止要求が作成される。

## 効果

説明用のパック`sndrs`に対してパック`Sndrs`を`decltype((sndrs))...`とする。

下記いずれかが`true`となるとき、呼び出し式`when_all(sndrs...)`は不適格となる。

- `sizeof...(sndrs) == 0`、または
- `(sender<Sndrs> && ...) == false`

そうでなければ、呼び出し式`when_all(sndrs...)`は下記と等価。

    make-sender(when_all, {}, sndrs...)

### Senderアルゴリズムタグ `when_all`

Senderアルゴリズム動作説明用のクラステンプレート`impls-for`に対して、下記の特殊化が定義される。

    namespace std::execution {
      template<>
      struct impls-for<when_all_t> : default-impls {
        static constexpr auto get-env = see below;
        static constexpr auto get-state = see below;
        static constexpr auto start = see below;
        static constexpr auto complete = see below;

        template<class Sndr, class... Env>
        static consteval void check-types();
      };
    }

`impls-for<when_all_t>::get-env`メンバは、下記ラムダ式と等価な関数呼び出し可能なオブジェクトで初期化される。

    []<class State, class Rcvr>(auto&&, State& state, const Receiver& rcvr) noexcept {
      return make-when-all-env(state.stop-src, get_env(rcvr))
    }

`impls-for<when_all_t>::get-state`メンバは、下記ラムダ式と等価な関数呼び出し可能なオブジェクトで初期化される。

    []<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) noexcept(noexcept(e)) -> decltype(e) {
      return e;
    }

ラムダ式が返す式`e`は下記の通り。

    std::forward<Sndr>(sndr).apply(make-state<Rcvr>())

`impls-for<when_all_t>::start`メンバは、下記ラムダ式と等価な関数呼び出し可能なオブジェクトで初期化される。

    []<class State, class Rcvr, class... Ops>(
        State& state, Rcvr& rcvr, Ops&... ops) noexcept -> void {
      state.on_stop.emplace(
        get_stop_token(get_env(rcvr)),
        on-stop-request{state.stop_src});
      (start(ops), ...);
    }

`impls-for<when_all_t>::complete`メンバは、下記ラムダ式と等価な関数呼び出し可能なオブジェクトで初期化される。

    []<class Index, class State, class Rcvr, class Set, class... Args>(
        this auto& complete, Index, State& state, Rcvr& rcvr, Set, Args&&... args) noexcept -> void {
      if constexpr (same_as<Set, set_error_t>) {
        if (disposition::error != state.disp.exchange(disposition::error)) {
          state.stop_src.request_stop();
          TRY-EMPLACE-ERROR(state.errors, std::forward<Args>(args)...);
        }
      } else if constexpr (same_as<Set, set_stopped_t>) {
        auto expected = disposition::started;
        if (state.disp.compare_exchange_strong(expected, disposition::stopped)) {
          state.stop_src.request_stop();
        }
      } else if constexpr (!same_as<decltype(State::values), tuple<>>) {
        if (state.disp == disposition::started) {
          auto& opt = get<Index::value>(state.values);
          TRY-EMPLACE-VALUE(complete, opt, std::forward<Args>(args)...);
        }
      }
      state.arrive(rcvr);
    }

説明用の式`v`, `e`に対して、式`decltype(auto(e))(e)`が潜在的に例外送出するならば、`TRY-EMPLACE-ERROR(v, e)`を下記と等価な式とする。 そうでなければ、`v.template emplace<decltype(auto(e))>(e)`とする。

    try {
      v.template emplace<decltype(auto(e))>(e);
    } catch (...) {
      v.template emplace<exception_ptr>(current_exception());
    }

説明用の式`c`, `o`およびパック`as`に対して、式`decayed-tuple<decltype(as)...>{as...}`が潜在的に例外送出するならば、`TRY-EMPLACE-VALUE(c, o, as...)`を下記と等価な式とする。 そうでなければ、`o.emplace(as...)`とする。

    try {
      o.emplace(as...);
    } catch (...) {
      c(Index(), state, rcvr, set_error, current_exception());
      return;
    }

メンバ関数`impls-for<when_all_t>::check-types`の効果は下記の通り。 説明用のパック`Is`を、`indices-for<Sndr>`で導入される`integer_sequence`クラス特殊化の整数テンプレート引数とする。

    auto fn = []<class Child>() {
      auto cs = get_completion_signatures<Child, when-all-env<Env>...>();
      if constexpr (cs.count-of(set_value) >= 2)
        throw unspecified-exception();
      decay-copyable-result-datums(cs);
    };
    (fn.template operator()<child-type<Sndr, Is>>(), ...);

## 説明専用エンティティ

### 関数テンプレート`make-when-all-env`

    template<class Env>
    constexpr auto make-when-all-env(inplace_stop_source& stop_src,  // exposition only
                                     Env&& env) noexcept;

下記を満たすオブジェクト`e`を返す。

- `decltype(e)`が`queryable`のモデル、かつ
- 式`e.query(get_stop_token)`が`stop_src.get_token()`と等価、かつ
- CV修飾された`get_stop_token`ではないクエリオブジェクト `q`に対して、`forwarding-query`を満たすならば、式`e.query(q)`は`env.query(q)`と等価。そうでなければ不適格となる。

### エイリアステンプレート`when-all-env`

`when-all-env<Env>`は`decltype(make-when-all-env(declval<inplace_stop_source&>(), declval<Env>()))`となる。

### 列挙型`disposition`

    enum class disposition { started, error, stopped };  // exposition only

### クラステンプレート`make-state`

    template<class Rcvr>
    struct make-state {
      template<class... Sndrs>
      auto operator()(auto, auto, Sndrs&&... sndrs) const {
        using values_tuple = see below;
        using errors_variant = see below;
        using stop_callback = stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, on-stop-request>;

        struct state-type {
          void arrive(Rcvr& rcvr) noexcept {               // exposition only
            if (0 == --count) {
              complete(rcvr);
            }
          }

          void complete(Rcvr& rcvr) noexcept;              // exposition only

          atomic<size_t> count{sizeof...(sndrs)};          // exposition only
          inplace_stop_source stop_src{};                  // exposition only
          atomic<disposition> disp{disposition::started};  // exposition only
          errors_variant errors{};                         // exposition only
          values_tuple values{};                           // exposition only
          optional<stop_callback> on_stop{nullopt};        // exposition only
        };

        return state-type{};
      }
    };

説明用の型`copy-fail`を、いずれかの子Senderの値結果データのdecayコピーが潜在的に例外送出するならば`exception_ptr`とする。そうでなければ、未規定の空のクラス型`none-such`とする。

型`values_tuple`は、適格であるならば下記の型とする。そうでなければ、`tuple<>`とする。

    tuple<value_types_of_t<Sndrs, FWD-ENV-T(env_of_t<Rcvr>), decayed-tuple, optional>...>

説明用のパック`Es`を全ての子Senderのエラー結果データのdecayed型としたとき、型`errors_variant`は下記定義において重複削除した型となる。

    variant<none-such, copy-fail, Es...>

メンバ関数`void state-type::complete(Rcvr& rcvr) noexcept`の動作は下記の通り。

- `disp == disposition::started`のとき、下記を評価する。

      auto tie = []<class... T>(tuple<T...>& t) noexcept { return tuple<T&...>(t); };
      auto set = [&](auto&... t) noexcept { set_value(std::move(rcvr), std::move(t)...); };

      on_stop.reset();
      apply(
        [&](auto&... opts) noexcept {
          apply(set, tuple_cat(tie(*opts)...));
        },
        values);

- そうではなく、`disp == disposition::error`のとき、下記を評価する。

      on_stop.reset();
      visit(
        [&]<class Error>(Error& error) noexcept {
          if constexpr (!same_as<Error, none-such>) {
            set_error(std::move(rcvr), std::move(error));
          }
        },
        errors);

- それ以外のとき、下記を評価する。

      if constexpr(sends-stopped) {
        on_stop.reset();
        set_stopped(std::move(rcvr));
      }

  ここで、`Sndrs`の要素`S`において`completion_signatures_of_t<S, when-all-env<Env>>`が`set_stopped_t()`を含む`S`が存在するときに限って`sends-stopped`は`true`に等しい。

## カスタマイゼーションポイント

Receiver接続時に、関連付けられた実行ドメインに対して`execution::transform_sender`経由でSender変換が行われる。 デフォルト実行ドメインでは無変換。

## 例

### 例1: 基本の使い方

    #include <print>
    #include <string>
    #include <execution>
    namespace ex = std::execution;
    using namespace std::string_literals;

    int main()
    {
      // string型の値を送信するSender
      ex::sender auto snd1 = ex::just("C++"s);
      // (int,char)型の値を送信するSender
      ex::sender auto snd2 = ex::just(123, 'X');
      // snd1,snd2両方の完了を待機するSender
      ex::sender auto sndr = ex::when_all(snd1, snd2);

      auto result = std::this_thread::sync_wait(sndr);
      // result := optional<tuple<string,int,char>>型
      std::println("result={}", result.value());
    }

#### 出力

    result=("C++", 123, 'X')

### 例2: 停止要求のハンドリング

    #include <print>
    #include <string>
    #include <execution>
    namespace ex = std::execution;

    // MySenderは下記いずれかの完了操作を行う
    //   値完了     set_value(string)
    //   エラー完了 set_error(int)
    //   停止完了   set_stopped()
    struct MySender {
      using sender_concept = ex::sender_tag;

      using completion_signatures = ex::completion_signatures<
        ex::set_value_t(std::string),
        ex::set_error_t(int),
        ex::set_stopped_t()
      >;
      template <typename Self>
      static consteval auto get_completion_signatures()
      {
        return completion_signatures{};
      }

      template <typename Rcvr>
      struct state {
        using operation_state_concept = ex::operation_state_tag;

        state(Rcvr rcvr, int val)
          : rcvr_{std::move(rcvr)}, val_{val} {}

        void start() noexcept {
          auto stok = ex::get_stop_token(ex::get_env(rcvr_));
          if (stok.stop_requested()) {
            // 接続先Receiverにおいて停止要求が行われていれば
            // 非同期操作も停止完了により早期リターンさせる
            std::println("{}: set_stopped", val_);
            ex::set_stopped(std::move(rcvr_));
            return;
          }
          // MySenderの本体処理
          if (0 <= val_) {
            // 成功: 値完了操作
            using namespace std::string_literals;
            std::println("{}: set_value", val_);
            ex::set_value(std::move(rcvr_), "Hello"s);
          } else {
            // 失敗: エラー完了操作
            std::println("{}: set_error", val_);
            ex::set_error(std::move(rcvr_), val_);
          }
        }

        Rcvr rcvr_;
        int val_;
      };

      template <typename Rcvr>
      auto connect(Rcvr rcvr) noexcept {
        return state{std::move(rcvr), val_};
      }

      int val_;
    };

    int main()
    {
      ex::sender auto snd1 = MySender{1};  // 値完了
      ex::sender auto snd2 = MySender{-2}; // エラー完了 → 停止要求
      ex::sender auto snd3 = MySender{3};  // 停止完了
      ex::sender auto sndr = ex::when_all(snd1, snd2, snd3);
      try {
        auto result = std::this_thread::sync_wait(sndr);
        // result := optional<tuple<string,string,string>>型
        std::println("value={}", *result);
      } catch (int err) {
        std::println("error={}", err);
      }
    }

#### 出力

    1: set_value
    -2: set_error
    3: set_stopped
    error=-2

## バージョン

### 言語

- C++26

### 処理系

- Clang: ??
- GCC: ??
- ICC: ??
- Visual C++: ??

## 関連項目

- `execution::when_all_with_variant`

## 参照

- P2300R10 `std::execution`
- P3396R1 std::execution wording fixes
- P3557R3 High-Quality Sender Diagnostics with Constexpr Exceptions
- P3887R1 Make `when_all` a Ronseal Algorithm
- LWG 4203. Constraints on `get-state` functions are incorrect
- LWG 4227. Missing `noexcept` operator in [exec.when.all]
- LWG 4438. Bad expression in [exec.when.all]
- P3826R5 Fix Sender Algorithm Customization