最終更新日時(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-taglet_valuelet_errorlet_stoppedそれぞれに対して一意な空のクラスとする。

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))

説明用のクラステンプレートlet-dataを下記の通り定義する。

template<class Sndr, class Fn>
struct let-data {
  Sndr sndr; // exposition only
  Fn fn;     // exposition only
};

let-cpo.transform_sender(s, es...)は、sが1回だけ評価されることを除いて、下記と等価。

make-sender(let-tag{}, let-data{s.template get<2>(), s.template get<1>()})

Senderアルゴリズムタグ let-tag

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

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

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

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

[]<class Sndr, class Rcvr>(Sndr&& sndr, Rcvr& rcvr) requires see below {
  auto& [_, data] = sndr;
  auto& [child, fn] = data;
  using child_t = decltype(std::forward_like<Sndr>(child));
  using fn_t = decay_t<decltype(fn)>;
  using args_variant_t = see below;
  using ops_variant_t = see below;
  using state_t = let-state<decayed-typeof<set-cpo>, child_t, fn_t, Rcvr,
                            args_variant_t, ops_variant_t>;
  return state_t(std::forward_like<Sndr>(child), std::forward_like<Sndr>(fn), rcvr);
}

  • 説明用のパックSigscompletion_signatures_of_t<child-type<Sndr>, FWD-ENV-T(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>&...>と定義する。型ops_variant_tは下記定義において重複削除した型となる。

    variant<monostate,
            connect_result_t<child_t, let-state::receiver,
            connect_result_t<as-sndr2<LetSigs>, receiver2<Rcvr, env_t>>...>
    

  • args_variant_tおよびops2_variant_t適格なときに限って、上記ラムダ式のrequires節が満たされる。

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

[]<class State, class Rcvr>(State& state, Rcvr&) noexcept {
  start(get<typename State::op_t>(state.ops));
}

メンバ関数impls-for<let-tag>::check-typesの効果は下記の通り。

using LetFn = remove_cvref_t<data-type<Sndr>>;
auto cs = get_completion_signatures<child-type<Sndr>, FWD-ENV-T(Env)...>();
auto fn = []<class... Ts>(decayed-typeof<set-cpo>(*)(Ts...)) {
  if constexpr (!is-valid-let-sender) // see below
    throw unspecified-exception();
};
cs.for-each(overload-set(fn, [](auto){}));

説明用の式sndrenvに対して、型Sndrdecltype((sndr))とする。sender-for<Sndr, decayed-typeof<let-cpo>> == falseのとき、式let-cpo.transform_env(sndr, env)不適格となる。

そうでなければ、式let-cpo.transform_env(sndr, env)は下記と等価。

auto& [_, _, child] = sndr;
return JOIN-ENV(let-env(child), FWD-ENV(env));

説明専用エンティティ

let-env

説明用の式sndrを用いて、let-env(sndr)を下記リストのうち最初に適格となる式と定義する。

変数is-valid-let-sender

説明用の変数is-valid-let-senderは下記を全て満たす時に限ってtrueとなる。

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

template<class Cpo, class Sndr, class Fn, class Rcvr, class ArgsVariant,
         class OpsVariant>
struct let-state {
  using env_t = decltype(let-env(declval<Sndr>())); // exposition only
  Fn fn;                                            // exposition only
  env_t env;                                        // exposition only
  ArgsVariant args;                                 // exposition only
  OpsVariant ops;                                   // exposition only

  template<class Tag, class... Ts>
  constexpr void impl(Rcvr& rcvr, Tag tag, Ts&&... ts) noexcept
  {                                                 // exposition only
    using args_t = decayed-tuple<Ts...>;
    using receiver_type = receiver2<Rcvr, env_t>;
    using sender_type = apply_result_t<Fn, args_t&>;
    if constexpr (is_same_v<Tag, Cpo>) {
      try {
        auto& tuple = args.template emplace<args_t>(std::forward<Ts>(ts)...);
        ops.template emplace<monostate>();
        auto&& sndr = apply(std::move(fn), tuple);
        using op_t = connect_result_t<sender_type, receiver_type>;
        auto mkop2 = [&] {
          return connect(std::forward<sender_type>(sndr),
                          receiver_type{rcvr, env});
        };
        auto& op = ops.template emplace<op_t>(emplace-from{mkop2});
        start(op);
      } catch (...) {
        constexpr bool nothrow =
          is_nothrow_constructible_v<args_t, Ts...> &&
          is_nothrow_applicable_v<Fn, args_t&> &&
          noexcept(
            connect(
              declval<sender_type>(),
              receiver_type{rcvr, env}));
        if constexpr (!nothrow) {
          set_error(std::move(rcvr), current_exception());
        }
      }
    } else {
      tag(std::move(rcvr), std::forward<Ts>(ts)...);
    }
  }

  struct receiver {   // exposition only
    let-state& state; // exposition only
    Rcvr& rcvr;       // exposition only

    using receiver_concept = receiver_t;

    template<class... Args>
    constexpr void set_value(Args&&... args) noexcept {
      state.impl(rcvr, execution::set_value, std::forward<Args>(args)...);
    }
    template<class... Args>
    constexpr void set_error(Args&&... args) noexcept {
      state.impl(rcvr, execution::set_error, std::forward<Args>(args)...);
    }
    template<class... Args>
    constexpr void set_stopped(Args&&... args) noexcept {
      state.impl(rcvr, execution::set_stopped, std::forward<Args>(args)...);
    }

    constexpr env_of_t<const Rcvr&> get_env() const noexcept {
      return execution::get_env(rcvr);
    }
  };

  using op_t = connect_result_t<Sndr, receiver>;      // exposition only
  constexpr let-state(Sndr&& sndr, Fn fn, Rcvr& rcvr) // exposition only
    : fn(std::move(fn)), env(let-env(sndr)),
      ops(in_place_type<op_t>, std::forward<Sndr>(sndr),
          receiver{*this, rcvr})
  {}
};

クラステンプレート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)が有効ならばその式と等価。そうではなく、qの型がforwarding-queryを満たすならば式e.query(q)get_env(rcvr).query(q)と等価。そうでなければ、式e.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

処理系

関連項目

参照