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のオブジェクトを以下の通りに構築して返す。
C
がinput_range
ではないか、convertible_to<range_reference_t<R>,range_value_t<C>>
である(R
の要素への参照がC
の要素に変換できる)場合:
constructible_from<C, R, Args...>
である(C
がR
と残りの引数で構築できる)場合C(std::forward<R>(r),std::forward<Args>(args)...)
constructible_from<C,from_range_t, R, Args...>
である(C
がfrom_range_t
、R
、残りの引数で構築できる)場合C(from_range,std::forward<R>(r),std::forward<Args>(args)...)
common_range<R>
がtrue
で、iterator_traits<iterator_t<R>>::iterator_category
がinput_iterator_tag
から派生する有効な型であり、constructible_from<C,iterator_t<R>,sentinel_t<R>, Args...>
である(C
がR
のイテレータおよび番兵と残りの引数で構築できる)場合C(ranges::begin(r),ranges::end(r),std::forward<Args>(args)...)
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
を次のように定義する。
- 有効な式ならば、
C(declval<R>(),declval<Args>()...)
- 有効な式ならば、
C(from_range,declval<R>(),declval<Args>()...)
- 有効な式ならば、
C(declval<input-iterator>(),declval<input-iterator>(),declval<Args>()...)
- 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...)
である。ただし、r
はf
の関数呼び出し式で使用される引数
備考
本説明に用いる説明専用要素を以下のように定義する。
// 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
処理系
- Clang: 19.0 (at least) ✅
- GCC: ??
- ICC: ??
- Visual C++: ??