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

履歴 編集

function template
<ranges>

std::ranges::to(C++23)

namespace std::ranges {
  template<class C, input_range R, class... Args> requires (!view<C>)
  constexpr C to(R&& r, Args&&... args);     // (1)

  template<template<class...> class C, input_range R, class... Args>
  constexpr auto to(R&& r, Args&&... args);  // (2)

  template<class C, class... Args> requires (!view<C>)
  constexpr auto to(Args&&... args);         // (3)

  template<template<class...> class C, class... Args>
  constexpr auto to(Args&&... args);         // (4)
}

概要

Rangeの各要素を要素とするコンテナを構築する。 この関数は再帰的に適用され、ネストされたRangeにも対応する。

  • (1): 処理の実体となる関数テンプレート
  • (2): (1)のテンプレートテンプレート版
  • (3): (1)に対応するRangeアダプタ
  • (4): (2)に対応するRangeアダプタ

(2), (4) の存在により、コンテナ型を指定するときにその要素の型を省略して(=テンプレートとして)渡すことができる。

to は他のRangeアダプタとは異なり先行評価される。コンテナもRangeなのでtoをRangeパイプラインの中間に組み込むこともできるが、viewだけで構成する場合とは振る舞いが異なることに注意。

適格要件

  • (1), (3): C はCV修飾のないクラス型であること。

戻り値

(1) Rangeの各要素を要素とするコンテナCのオブジェクトを以下の通りに構築して返す。

Cinput_rangeではないか、convertible_to<range_reference_t<R>,range_value_t<C>> である(Rの要素への参照がCの要素に変換できる)場合:

  1. constructible_from<C, R, Args...>である(CRと残りの引数で構築できる)場合
  2. constructible_from<C,from_range_t, R, Args...>である(Cfrom_range_tR、残りの引数で構築できる)場合
  3. common_range<R>trueで、iterator_traits<iterator_t<R>>::iterator_categoryinput_iterator_tag から派生する有効な型であり、constructible_from<C,iterator_t<R>,sentinel_t<R>, Args...>である(CRのイテレータおよび番兵と残りの引数で構築できる)場合
  4. constructible_from<C, Args...>trueで、container-insertable<C,range_reference_t<R>>trueである場合
    • 以下のコードで初期化する

C c(std::forward<Args>(args)...);
if constexpr (sized_range<R> && reservable-container<C>) {
  c.reserve(static_cast<range_size_t<C>>(ranges::size(r)));
}
ranges::copy(r, container-inserter<range_reference_t<R>>(c));

input_range<range_reference_t<R>>である場合:

to<C>(r | views::transform([](auto&& elem) {
  return to<range_value_t<C>>(std::forward<decltype(elem)>(elem));
}), std::forward<Args>(args)...);

どの条件にもあてはまらない場合、プログラムは不適格である。

(2) input-iterator を次のように定義する。

struct input-iterator {
  using iterator_category = input_iterator_tag;
  using value_type = range_value_t<R>;
  using difference_type = ptrdiff_t;
  using pointer = add_pointer_t<range_reference_t<R>>;
  using reference = range_reference_t<R>;
  reference operator*() const;
  pointer operator->() const;
  input-iterator& operator++();
  input-iterator operator++(int);
  bool operator==(const input-iterator&) const;
};

また、DEDUCE_EXPR を次のように定義する。

  1. 有効な式ならば、C(declval<R>(),declval<Args>()...)
  2. 有効な式ならば、C(from_range,declval<R>(),declval<Args>()...)
  3. 有効な式ならば、C(declval<input-iterator>(),declval<input-iterator>(),declval<Args>()...)
  4. 1-3が有効でなければ、ill-formed

このとき、戻り値は to<decltype(DEDUCE_EXPR)>(std::forward<R>(r),std::forward<Args>(args)...)

(3), (4): 次の性質をもつ完全転送呼び出しラッパー(perfect forwarding call wrapper)であるようなRangeアダプタクロージャオブジェクトfを返す。

  • ターゲットオブジェクトを持たない
  • バインドされた引数bound_argsは、decay_t<Args>...型のオブジェクトであり、それぞれstd::forward<Args>(args)...直接非リスト初期化(direct-non-list-initialize)される
  • 呼び出しパターンはto<C>(r, bound_args...)である。ただし、rfの関数呼び出し式で使用される引数

備考

本説明に用いる説明専用要素を以下のように定義する。

// reservable-container: 容量をあらかじめ確保できることを要求するコンセプト
template<class Container>
constexpr bool reservable-container =
  sized_range<Container> &&
  requires(Container& c, range_size_t<Container> n) {
    c.reserve(n);                              // コンテナのサイズ型を引数とするreserveメンバ関数がある
    { c.capacity() } -> same_as<decltype(n)>;  // コンテナのサイズ型を返すcapacityメンバ関数がある
    { c.max_size() } -> same_as<decltype(n)>;  // コンテナのサイズ型を返すmax_sizeメンバ関数がある
  };

// container-insertable: push_backまたはinsertが使えることを要求するコンセプト
template<class Container, class Ref>
constexpr bool container-insertable =
  requires(Container& c, Ref&& ref) {
    requires (requires { c.push_back(std::forward<Ref>(ref)); } ||
              requires { c.insert(c.end(), std::forward<Ref>(ref)); });
  };

// container-inserter: push_backが使えればback_inserter, そうでなければinserterを返す関数
template<class Ref, class Container>
constexpr auto container-inserter(Container& c) {
  if constexpr (requires { c.push_back(declval<Ref>()); }) {
    return back_inserter(c);
  } else {
    return inserter(c, c.end());
  }
}

この機能が必要になった背景・経緯

Rangeの各要素を要素とするコンテナを構築するには、従来、次のような方法があった。

#include <ranges>
#include <vector>
#include <string>
#include <string_view>
#include <print>
#include <algorithm>

int main() {
  using namespace std;
  using namespace std::literals;
  auto str = "the quick brown fox"sv;
  vector<string> words;
  for(auto&& i : views::split(str, ' ')) {
    words.emplace_back(i.begin(), i.end());
  }
  println("{}", words);
}

これを簡潔に書くために、to関数が提案された。

#include <ranges>
#include <vector>
#include <string>
#include <string_view>
#include <print>

int main() {
  using namespace std;
  using namespace std::literals;
  auto str = "the quick brown fox"sv;
  auto words = views::split(str, ' ') | ranges::to<vector<string>>();
  println("{}", words);
}

出力

["the", "quick", "brown", "fox"]

バージョン

言語

  • C++23

処理系

参照