最終更新日時(UTC):
が更新

履歴 編集

customization point object
<execution>

std::execution::split(C++26)

namespace std::execution {
  struct split_t { unspecified };
  inline constexpr split_t split{};
}

概要

splitは、任意の入力Senderを複数回接続(connect)可能とするSenderアダプタである。

splitパイプ可能Senderアダプタオブジェクトであり、パイプライン記法をサポートする。

効果

説明用の型split-envを、インスタンスenvに対して式get_stop_token(env)適格かつ型inplace_stop_tokenをもつ型とする。

説明用の式sndrに対して、型Sndrdecltype((sndr))とする。sender_in<Sndr, split-env> == falseのとき、呼び出し式split(sndr)不適格となる。

そうでなければ、呼び出し式split(sndr)sndrが1回だけ評価されることを除いて、下記と等価。

Senderアルゴリズムタグ split

説明用のSenderアルゴリズムタグ型split-impl-tagを空の型とする。説明用の式sndrに対して、式split.transform_sender(sndr)は下記と等価。

auto&& [tag, _, child] = sndr;
auto* sh_state = new shared-state{std::forward_like<decltype((sndr))>(child)};
return make-sender(split-impl-tag(), shared-wrapper{sh_state, tag});

説明用の型shared-wrapperは、sh_stateが指すshared-stateオブジェクトの参照カウントを管理するクラスである。

  • shared-wrappercopyableのモデルである。
  • ムーブ操作 : 移動済みオブジェクトをヌルとする。
  • コピー操作 : sh_state->inc-ref()を呼び出して参照カウントをインクリメントする。
  • 代入操作 : Copy-And-Swap操作を行う。
  • デストラクタ : sh_stateがヌルのときは何もしない。そうでないとき、sh_state->dec-ref()を評価して参照カウントをデクリメントする。

Senderアルゴリズムタグ split-impl-tag

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

namespace std::execution {
  template<>
  struct impls-for<split-impl-tag> : default-impls {
    static constexpr auto get-state = see below;
    static constexpr auto start = see below;
  };
}

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

[]<class Sndr>(Sndr&& sndr, auto& rcvr) noexcept {
  return local-state{std::forward<Sndr>(sndr), rcvr};
}

impls-for<split-impl-tag>::startメンバは、下記の関数呼び出し演算子をもつオブジェクトで初期化される。

template<class Sndr, class Rcvr>
void operator()(local-state<Sndr, Rcvr>& state, Rcvr& rcvr) const noexcept;

効果 : state.sh_state->completed == trueのとき、state.notify()を評価してリターンする。そうでなければ、下記を順番に行う。

  • 以下を評価する。

    state.on_stop.emplace(
      get_stop_token(get_env(rcvr)),
      on-stop-request{state.sh_state->stop_src});
    

  • 下記をアトミックに行う。

    • state.sh_state->completedの値cを読み取り
    • c == falseのとき、state.sh_state->waiting_stateaddressof(state)を挿入する
  • c == trueならば、state.notify()を呼び出してリターンする。
  • そうではなく、addressof(state)state.sh_state->waiting_stateに最初に追加されるアイテムならば、state.sh_state->start-op()を評価する。

説明専用エンティティ

クラステンプレートlocal-state

namespace std::execution {
  struct local-state-base {                // exposition only
    virtual ~local-state-base() = default;
    virtual void notify() noexcept = 0;    // exposition only
  };

  template<class Sndr, class Rcvr>
  struct local-state : local-state-base {  // exposition only
    using on-stop-callback =               // exposition only
      stop_callback_for_t<stop_token_of_t<env_of_t<Rcvr>>, on-stop-request>;

    local-state(Sndr&& sndr, Rcvr& rcvr) noexcept;
    ~local-state();

    void notify() noexcept override;

  private:
    optional<on-stop-callback> on_stop;    // exposition only
    shared-state<Sndr>* sh_state;          // exposition only
    Rcvr* rcvr;                            // exposition only
  };
}

local-state(Sndr&& sndr, Rcvr& rcvr) noexcept;

  • 効果 : 下記と等価。

    auto& [_, data, _] = sndr;
    this->sh_state = data.sh_state.get();
    this->sh_state->inc-ref();
    this->rcvr = addressof(rcvr);
    

~local-state();

  • 効果 : 下記と等価。

    sh_state->dec-ref();
    

void notify() noexcept override;

  • 効果 : 下記と等価。

    on_stop.reset();
    visit(
      [this](const auto& tupl) noexcept -> void {
        apply(
          [this](auto tag, const auto&... args) noexcept -> void {
            tag(std::move(*rcvr), args...);
          },
          tupl);
      },
      sh_state->result);
    

クラステンプレートsplit-receiver

namespace std::execution {
  template<class Sndr>
  struct split-receiver {  // exposition only
    using receiver_concept = receiver_t;

    template<class Tag, class... Args>
    void complete(Tag, Args&&... args) noexcept {  // exposition only
      using tuple_t = decayed-tuple<Tag, Args...>;
      try {
        sh_state->result.template emplace<tuple_t>(Tag(), std::forward<Args>(args)...);
      } catch (...) {
        using tuple_t = tuple<set_error_t, exception_ptr>;
        sh_state->result.template emplace<tuple_t>(set_error, current_exception());
      }
      sh_state->notify();
    }

    template<class... Args>
    void set_value(Args&&... args) && noexcept {
      complete(execution::set_value, std::forward<Args>(args)...);
    }

    template<class Error>
    void set_error(Error&& err) && noexcept {
      complete(execution::set_error, std::forward<Error>(err));
    }

    void set_stopped() && noexcept {
      complete(execution::set_stopped);
    }

    struct env {                     // exposition only
      shared-state<Sndr>* sh-state;  // exposition only

      inplace_stop_token query(get_stop_token_t) const noexcept {
        return sh-state->stop_src.get_token();
      }
    };

    env get_env() const noexcept {
      return env{sh_state};
    }

    shared-state<Sndr>* sh_state;    // exposition only
  };
}

クラステンプレートshared-state

namespace std::execution {
  template<class Sndr>
  struct shared-state {
    using variant-type = see below;     // exposition only
    using state-list-type = see below;  // exposition only

    explicit shared-state(Sndr&& sndr);

    void start-op() noexcept;           // exposition only
    void notify() noexcept;             // exposition only
    void inc-ref() noexcept;            // exposition only
    void dec-ref() noexcept;            // exposition only

    inplace_stop_source stop_src{};     // exposition only
    variant-type result{};              // exposition only
    state-list-type waiting_states;     // exposition only
    atomic<bool> completed{false};      // exposition only
    atomic<size_t> ref_count{1};        // exposition only
    connect_result_t<Sndr, split-receiver<Sndr>> op_state;  // exposition only
  };
}

  • 説明用のパックSigscompletion_signatures_of_t<Sndr>によるcompletion_signatures特殊化のテンプレートパラメータと定義する。説明用の型TagとパックArgsに対して、説明用のエイリアステンプレートas-tuple<Tag(Args...)>decayed-tuple<Tag, Args...>と定義する。型variant-typeは下記定義において重複削除した型となる。

  • state-list-typeを、local-state-baseオブジェクトへのポインタのリストを格納し、アトミックに要素挿入できる型とする。

explicit shared-state(Sndr&& sndr);

  • 効果 : op_stateconnect(std::forward<Sndr>(sndr), split-receiver{this})の結果で初期化する。
  • 事後条件 : waiting_statesが空、かつcompleted == false

void start-op() noexcept;

  • 効果 : inc-ref()を評価する。stop_src.stop_requested() == tureのときnotify()を評価する。そうでなければ、start(op_state)を評価する。

void notify() noexcept;

  • 効果 : 下記をアトミックに行い、ローカル変数prior_statesの各ポインタpに対してp->notify()を評価し、最後にdec-ref()を評価する。
    • completedtrueを設定し、
    • waiting_statesを空のリストと交換し、古い値をローカル変数prior_statesに格納する。

void inc-ref() noexcept;

  • 効果 : ref_countをインクリメントする。

void dec-ref() noexcept;

  • 効果 : ref_countをデクリメントする。ref_countの新たな値が0のとき、delete thisを呼び出す。
  • 同期操作 : dec-ref()の評価がref_countを値0にデクリメントしないとき、ref_countを値0へデクリメントするdec-ref()の評価に対して同期する

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

Senderアルゴリズム構築時に、Sendersndr関連付けられた実行ドメインに対してexecution::transform_sender経由でSender変換が行われる。 デフォルト実行ドメインではsplit.transform_sender(sndr)が呼ばれ、前述仕様通りのSenderへと変換される。

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

int main()
{
  { // 関数呼び出し
    ex::sender auto snd0 = ex::just(21);
    ex::sender auto snd1 = ex::then(snd0, [](int n) {
        std::println("then");
        return 2 * n;
      });
    ex::sender auto sndr = ex::split(snd1);

    auto [val1] = std::this_thread::sync_wait(sndr).value();
    std::println("{}", val1);
    auto [val2] = std::this_thread::sync_wait(sndr).value();
    std::println("{}", val2);
  }

  { // パイプライン記法
    ex::sender auto sndr =
      ex::just(21)
      | ex::then([](int n) {
          std::println("then");
          return 2 * n;
        })
      | ex::split();

    auto [val1] = std::this_thread::sync_wait(sndr).value();
    std::println("{}", val1);
    auto [val2] = std::this_thread::sync_wait(sndr).value();
    std::println("{}", val2);
  }
}

出力

then
42
42
then
42
42

バージョン

言語

  • C++26

処理系

参照