<ranges>
大まかに言えば、 range は反復処理できるものです。 範囲は、範囲の先頭をマークする反復子と、範囲の末尾をマークするセンチネルによって表されます。 センチネルは、開始反復子と同じ型であるか、異なる場合があります。 C++ 標準ライブラリの vector
や list
などのコンテナーは範囲です。 範囲は、標準テンプレート ライブラリ (STL) をさらに簡単に利用できるように、反復子を抽象化します。
通常、STL アルゴリズムは、操作する必要があるコレクションの部分を指す反復子を受け取ります。 たとえば、std::sort()
を使用してvector
を並べ替える方法を考えてみましょう。 vector
の先頭と末尾を示す 2 つの反復子を渡します。 これは柔軟性を提供しますが、反復子をアルゴリズムに渡すことは、おそらく全体を並べ替えたいだけなので、余分な作業です。
範囲を使用すると、 std::ranges::sort(myVector);
を呼び出すことができます。これは、 std::sort(myVector.begin(), myVector.end());
を呼び出したかのように扱われます。 範囲ライブラリでは、アルゴリズムは範囲をパラメーターとして受け取ります (ただし、必要に応じて反復子を受け取ることもできます)。 コレクションに対して直接操作できます。 <algorithm>
で使用できる範囲アルゴリズムの例としては、copy
、copy_n
、copy_if
、all_of
、any_of
、none_of
、find
、find_if
、find_if_not
、count
、count_if
、for_each
、for_each_n
、equal
、mismatch
などがあります。
しかし、範囲の最も重要な利点は、関数型プログラミングを思い起こさせるスタイルで範囲を操作する STL アルゴリズムを作成できることです。
範囲の例
範囲の前に、特定の条件を満たすコレクションの要素を変換する場合は、操作間の結果を保持する中間ステップを導入する必要があります。 たとえば、3 つで割り切れる別のベクトル内の要素から四角形のベクトルを作成する場合は、次のように記述できます。
std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::vector<int> intermediate, output;
std::copy_if(input.begin(), input.end(), std::back_inserter(intermediate), [](const int i) { return i%3 == 0; });
std::transform(intermediate.begin(), intermediate.end(), std::back_inserter(output), [](const int i) {return i*i; });
範囲を使用すると、intermediate
ベクターを必要とせずに同じことを実現できます。
// requires /std:c++20
std::vector<int> input = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto output = input
| std::views::filter([](const int n) {return n % 3 == 0; })
| std::views::transform([](const int n) {return n * n; });
このコードは、読みやすくするだけでなく、 intermediate
ベクターとその内容に必要なメモリ割り当てを回避します。 また、2 つの操作を作成することもできます。
前のコードでは、3 で割り切れる各要素は、その要素を 2 乗する操作と組み合わされています。 パイプ (|
) シンボルは操作を連結し、左から右に読み取られます。
結果 output
自体は、 viewと呼ばれる範囲の一種です。
ビュー
ビューは軽量な範囲です。 既定の構築、移動の構築/割り当て、コピーの構築/割り当て (存在する場合)、破棄、開始、終了などの操作は、ビュー内の要素の数に関係なく、一定の時間で行われます。
ビューは範囲アダプターによって作成されます。これについては、次のセクションで説明します。 さまざまなビューを実装するクラスの詳細については、「 View クラスを参照してください。
ビュー内の要素の表示方法は、ビューの作成に使用する範囲アダプターによって異なります。 前の例では、範囲アダプターは範囲を受け取り、3 で割り切れる要素のビューを返します。 基になる範囲は変更されません。
ビューは構成可能であり、強力です。 前の例では、3 つで割り切れるベクター要素のビューが、それらの要素を正方形にするビューと組み合わされています。
ビューの要素は、遅れて評価されます。 つまり、ビュー内の各要素に適用する変換は、要素を要求するまで評価されません。 たとえば、デバッガーで次のコードを実行し、auto divisible_by_three = ...
行とauto square = ...
行にブレークポイントを配置すると、input
の各要素が 3 つ分の 1 でテストされるため、divisible_by_three
ラムダ ブレークポイントにヒットすることがわかります。 3 で割り切れる要素が 2 乗されると、square
ラムダ ブレークポイントにヒットします。
// requires /std:c++20
#include <ranges>
#include <vector>
#include <iostream>
int main()
{
std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto divisible_by_three = [](const int n) {return n % 3 == 0; };
auto square = [](const int n) {return n * n; };
auto x = input | std::views::filter(divisible_by_three)
| std::views::transform(square);
for (int i : x)
{
std::cout << i << '\n';
}
return 0;
}
ビューの詳細については、「ビュー クラス <ranges>
参照してください。
範囲アダプター
範囲アダプターは範囲を取得し、ビューを生成します。 範囲アダプターは、遅延評価ビューを生成します。 つまり、範囲内のすべての要素を変換してビューを生成するコストは発生しません。 ビュー内の要素を処理するコストは、その要素にアクセスしたときにのみ支払います。
前の例では、 filter
範囲アダプターは、3 で割り切れる要素を含む input
という名前のビューを作成します。 transform
範囲アダプターは、3 で割り切れる要素のビューを取得し、それらの要素の 2 乗ビューを作成します。
範囲アダプターは、範囲のパワーと柔軟性の中核となるチェーン (構成) できます。 範囲アダプターを構成すると、以前の STL アルゴリズムが簡単に構成できないという問題を克服できます。
ビューの作成の詳細については、 アダプターを参照してください。
範囲アルゴリズム
一部の範囲アルゴリズムは、範囲引数を受け取ります。 たとえば std::ranges::sort(myVector);
です。
範囲アルゴリズムは、 std
名前空間の対応する反復子ペア アルゴリズムとほぼ同じです。 違いは、 concept によって強制される制約があり、範囲引数または複数の反復子と sentinel の引数のペアを受け入れることです。 これらはコンテナーで直接機能し、簡単に連結できます。
<ranges>
関数
次の関数は、範囲の反復子とセンチネルを作成し、範囲のサイズを取得するために使用されます。
関数 | 説明 |
---|---|
begin C++20 |
範囲内の最初の要素を指す反復子を取得します。 |
cbegin C++20 |
範囲内の最初の要素を指す const 反復子を取得します。 |
cend C++20 |
const 修飾範囲の末尾にある sentinel を取得します。 |
cdata C++20 |
連続した範囲内の最初の要素への const ポインターを取得します。 |
crbegin C++20 |
範囲の先頭を指す反転 const 反復子を取得します。 |
crend C++20 |
crbegin() が返す内容の最後にセンチネルを取得します。 |
data C++20 |
連続する範囲内の最初の要素へのポインターを取得します。 |
empty C++20 |
範囲が空かどうかを確認します。 |
end C++20 |
範囲の末尾にあるセンチネルを取得します。 |
rbegin C++20 |
範囲の先頭を指す逆反復子を取得します。 |
rend C++20 |
範囲の末尾にあるセンチネルを指す逆反復子を取得します。 |
size C++20 |
範囲のサイズを符号なし値として取得します。 |
ssize C++20 |
範囲のサイズを符号付き値として取得します。 |
詳細については、「<ranges>
関数」を参照してください。
範囲の概念
範囲の要素を反復処理する方法は、基になる反復子の型によって異なります。 範囲では、サポートする反復子を指定する C++ の概念が使用されます。
C++20 では、概念 X が概念 Y を調整すると言うと 概念を満たすすべてのもの Y も概念 Xを満たすことを意味します。たとえば、 car、 bus、 truck はすべて 乗り物を絞り込みます。
一部の範囲の概念は、反復子カテゴリの階層を反映しています。 次の表に、範囲の概念と、適用できるコンテナーの種類を示します。
範囲の概念 | 説明 | サポートされているコンテナー |
---|---|---|
std::ranges::output_range |
繰り返し処理できます。 | |
std::ranges::input_range |
最初から最後まで少なくとも 1 回は反復処理できます。 | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset basic_istream_view |
std::ranges::forward_range |
最初から最後まで繰り返し繰り返すことができます。 | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset |
std::ranges::bidirectional_range |
前後に複数回繰り返し繰り返すことができます。 | std::list std::map std::multimap std::multiset std::set |
std::ranges::random_access_range |
[] 演算子を使用して、任意の要素に (一定の時間で) アクセスできます。 |
std::deque |
std::ranges::contiguous_range |
要素はメモリに連続して格納されます。 | std::array std::string std::vector |
これらの概念の詳細については<ranges>
概念を参照してください。
<ranges>
エイリアスのテンプレート
次のエイリアス テンプレートは、範囲の反復子とセンチネルの種類を決定します。
エイリアス テンプレート | 説明 |
---|---|
borrowed_iterator_t C++20 |
range に対して返される反復子が、有効期間が終了した範囲を参照しているかどうかを判断します。 |
borrowed_subrange_t C++20 |
subrange に対して返される反復子が、有効期間が終了したサブ範囲を参照しているかどうかを判断します。 |
dangling C++20 |
range /subrange の返された反復子が、それが参照するrange /subrange の有効期間を上回っていることを示します。 |
iterator_t C++20 |
指定した範囲の型の反復子型を返します。 |
range_difference_t C++20 |
指定した範囲の反復子型の差分型を返します。 |
range_reference_t C++20 |
指定した範囲の反復子型の参照型を返します。 |
range_rvalue_reference_t C++20 |
指定した範囲の反復子型の右辺値参照型を返します。 つまり、範囲の要素の右辺値参照型です。 |
range_size_t C++20 |
指定した範囲のサイズを報告するために使用される型を返します。 |
range_value_t C++20 |
指定した範囲の反復子型の値型を返します。 または、範囲内の要素の型です。 |
sentinel_t C++20 |
指定した範囲のセンチネルの種類を返します。 |
これらのエイリアス テンプレートの詳細については、「エイリアス テンプレート<ranges>
を参照してください。