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

履歴 編集

customization point object
<execution>

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

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_valuelet_errorlet_stoppedの動作仕様を包括的に説明するため、以降のセクションにおいては、let-cpo, set-cpoをそれぞれ下記の通りとする。

let-cpo set-cpo
let_value set_value
let_error set_error
let_stopped set_stopped

効果

説明用の式sndrfに対して、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で利用される。
    • fn : Senderアルゴリズム構築時に指定した関数呼び出し可能オブジェクトf
    • env : 入力Sendersndrに関連付けられた属性
    • args : f呼び出し時の引数リスト格納用変数(空値monostateで初期化)
    • ops : fが返すSenderに対応する非同期操作の格納用変数(空値monostateで初期化)

[]<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), {}, {}};
}

  • 説明用のパックSigscompletion_signatures_of_t<child-type<Sndr>, env_of_t<Rcvr>>によるcompletion_signatures特殊化のテンプレートパラメータとし、パックLetSigsSigsに含まれる型のうち戻り値型がdecayed-typeof<set-cpo>に等しいものと定義する。説明用のエイリアステンプレートas-tuple<Tag(Args...)>decayed-tuple<Args...>と定義する。型args_variant_tは下記定義において重複削除した型となる。

    variant<monostate, as-tuple<LetSigs>...>
    

  • 説明用の型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)...);
    }
  }

説明用の式sndrenvに対して、型Sndrdecltype((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)を下記リストのうち最初に適格となる式と定義する。

説明専用のlet-bindテンプレート関数を下記の通り定義する。

  • 入力Senderの完了結果から引数リストstate.argsを構築し、Senderアルゴリズム構築時に指定した関数呼び出し可能オブジェクトstate.fnを呼び出す。
  • 上記呼び出しでstate.fnから返されたSenderと、完了結果をSenderアルゴリズムの接続先ReceiverRcvrへ転送するヘルパreceiver2接続(connect)する。
  • 接続結果Operation Statestate.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_sndrlet-cpo(sndr, f)戻り値Senderとし、式rcvrを式connect(out_sndr, rcvr)適格となるReceiverとする。式connect(out_sndr, rcvr)開始(start)時に下記を満たす非同期操作を生成しない場合、動作は未定義となる。

  • 入力Sendersndrの完了結果で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

処理系

関連項目

参照