<ranges>
概念
概念は、コンパイル時にテンプレート パラメーターを制約する C++20 言語機能です。 テンプレートのインスタンス化の誤りを防ぎ、テンプレート引数の要件を読み取り可能な形式で指定し、より簡潔なテンプレート関連のコンパイラ エラーを提供するのに役立ちます。
除算をサポートしていない型を使用してテンプレートをインスタンス化しないようにする概念を定義する次の例を考えてみましょう。
// requires /std:c++20 or later
#include <iostream>
// Definition of dividable concept which requires
// that arguments a & b of type T support division
template <typename T>
concept dividable = requires (T a, T b)
{
a / b;
};
// Apply the concept to a template.
// The template will only be instantiated if argument T supports division.
// This prevents the template from being instantiated with types that don't support division.
// This could have been applied to the parameter of a template function, but because
// most of the concepts in the <ranges> library are applied to classes, this form is demonstrated.
template <class T> requires dividable<T>
class DivideEmUp
{
public:
T Divide(T x, T y)
{
return x / y;
}
};
int main()
{
DivideEmUp<int> dividerOfInts;
std::cout << dividerOfInts.Divide(6, 3); // outputs 2
// The following line will not compile because the template can't be instantiated
// with char* because char* can be divided
DivideEmUp<char*> dividerOfCharPtrs; // compiler error: cannot deduce template arguments
}
コンパイラ スイッチ /diagnostics:caret
を Visual Studio 2022 バージョン 17.4 Preview 4 以降に渡すと、概念 dividable<char*>
false に評価されたエラーは、失敗した式要件 (a / b)
を直接示します。
範囲の概念は、 std::ranges
名前空間で定義され、 <ranges>
ヘッダー ファイルで宣言されます。 これらは、範囲アダプタービューなどの宣言で使用されます。
範囲には 6 つのカテゴリがあります。 これらは、 <iterator>
の概念に記載されている反復子のカテゴリに関連しています。 機能を向上させるために、カテゴリは次のとおりです。
範囲の概念 | 説明 |
---|---|
output_range input_range |
書き込み可能な範囲を指定します。 1 回から読み取ることができる範囲を指定します。 |
forward_range |
複数回読み取り (場合によっては書き込み) できる範囲を指定します。 |
bidirectional_range |
前後の両方で読み書きできる範囲を指定します。 |
random_access_range |
インデックスで読み書きできる範囲を指定します。 |
contiguous_range |
メモリ内の要素がシーケンシャルで、サイズが同じで、ポインター算術演算を使用してアクセスできる範囲を指定します。 |
前の表では、機能を向上させる順に概念を示します。 概念の要件を満たす範囲は、通常、その前の行の概念の要件を満たしています。 たとえば、 random_access_range
には、 bidirectional_range
、 forward_range
、 input_range
、および output_range
の機能があります。 例外は input_range
であり、書き込むことができないため、 output_range
の機能はありません。
その他の範囲の概念は次のとおりです。
範囲の概念 | 説明 |
---|---|
range C++20 |
反復子とセンチネルを提供する型を指定します。 |
borrowed_range C++20 |
範囲の反復子の有効期間が範囲の有効期間に関連付けられていないことを指定します。 |
common_range C++20 |
範囲の反復子の型と範囲のセンチネルの型が同じであることを指定します。 |
Simple_View C++20 |
標準ライブラリの一部として定義されている公式の概念ではありませんが、一部のインターフェイスでヘルパーの概念として使用されます。 |
sized_range C++20 |
要素の数を効率的に提供できる範囲を指定します。 |
view C++20 |
移動の構築、割り当て、および破棄が効率的な (一定時間) 型を指定します。 |
viewable_range C++20 |
ビューであるか、またはビューに変換できる型を指定します。 |
bidirectional_range
bidirectional_range
では、範囲の前後の読み取りと書き込みがサポートされます。
template<class T>
concept bidirectional_range =
forward_range<T> && bidirectional_iterator<iterator_t<T>>;
パラメーター
T
bidirectional_range
かどうかをテストする型。
解説
この種類の範囲では、 bidirectional_iterator
以上がサポートされます。
bidirectional_iterator
にはforward_iterator
の機能がありますが、後方に反復処理することもできます。
bidirectional_range
の例としては、std::set
、std::vector
、std::list
があります。
borrowed_range
型モデルは、オブジェクトから取得した反復子の有効性がオブジェクトの有効期間を上回る場合に borrowed_range
します。 つまり、範囲が存在しなくなった場合でも、範囲の反復子を使用できます。
template<class T>
concept borrowed_range =
range<T> &&
(is_lvalue_reference_v<T> || enable_borrowed_range<remove_cvref_t<T>>);
パラメーター
T
borrowed_range
かどうかをテストする型。
解説
右辺値範囲の有効期間は、範囲モデルが borrowed_range
かどうかに関係なく、関数呼び出しの後で終了できます。 borrowed_range
の場合、範囲の有効期間がいつ終了しても、適切に定義された動作で反復子を引き続き使用できる場合があります。
たとえば、コンテナーの有効期間が終了すると、反復子は破棄された要素を参照するため、 vector
や list
などのコンテナーでは、これが当てはまらない場合です。
たとえば、borrowed_range
の反復子を引き続き使用できます。たとえば、反復子がオンデマンドで生成されるために破棄されない値のセットを超えるiota_view<int>{0, 42}
などのview
に対して使用できます。
common_range
common_range
の反復子の型は、sentinel の型と同じです。 つまり、 begin()
と end()
は同じ型を返します。
template<class T>
concept common_range =
ranges::range<T> && std::same_as<ranges::iterator_t<T>, ranges::sentinel_t<T>>;
パラメーター
T
common_range
かどうかをテストする型。
解説
std::ranges::begin()
とstd::ranges::end()
から型を取得することは、2 つの反復子間の距離を計算するアルゴリズムと、反復子ペアで示される範囲を受け入れるアルゴリズムにとって重要です。
標準コンテナー (たとえば、 vector
) は、 common_range
の要件を満たしています。
contiguous_range
contiguous_range
の要素はメモリに順番に格納され、ポインターの算術演算を使用してアクセスできます。 たとえば、配列は contiguous_range
です。
template<class T>
concept contiguous_range =
random_access_range<T> && contiguous_iterator<iterator_t<T>> &&
requires(T& t) {{ ranges::data(t) } -> same_as<add_pointer_t<range_reference_t<T>>>;};
パラメーター
T
contiguous_range
かどうかをテストする型。
解説
要素はメモリ内に順番に配置され、サイズが同じであるため、ポインターの算術演算によって contiguous_range
にアクセスできます。 この種類の範囲は、すべての反復子の中で最も柔軟な continguous_iterator
をサポートします。
contiguous_range
の例としては、std::array
、std::vector
、std::string
があります。
例: contiguous_range
次の例は、ポインター算術演算を使用して contiguous_range
にアクセスする方法を示しています。
// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>
int main()
{
// Show that vector is a contiguous_range
std::vector<int> v = {0,1,2,3,4,5};
std::cout << std::boolalpha << std::ranges::contiguous_range<decltype(v)> << '\n'; // outputs true
// Show that pointer arithmetic can be used to access the elements of a contiguous_range
auto ptr = v.data();
ptr += 2;
std::cout << *ptr << '\n'; // outputs 2
}
true
2
forward_range
forward_range
では、範囲の読み取り (場合によっては書き込み) が複数回サポートされます。
template<class T>
concept forward_range = input_range<T> && forward_iterator<iterator_t<T>>;
パラメーター
T
forward_range
かどうかをテストする型。
解説
この種類の範囲では、 forward_iterator
以上がサポートされます。 forward_iterator
は、範囲を複数回反復処理できます。
input_range
input_range
は、1 回から読み取ることができる範囲です。
template<class T>
concept input_range = range<T> && input_iterator<iterator_t<T>>;
パラメーター
T
input_range
かどうかをテストする型。
解説
型が input_range
の要件を満たしている場合:
ranges::begin()
関数はinput_iterator
を返します。input_range
でbegin()
を複数回呼び出すと、未定義の動作が発生します。input_iterator
を繰り返し逆参照すると、毎回同じ値が生成されます。input_range
はマルチパスではありません。 反復子をインクリメントすると、すべてのコピーが無効になります。ranges::for_each
と共に使用できます。input_iterator
以上をサポートします。
output_range
output_range
は、書き込み可能な範囲です。
template<class R, class T>
concept output_range = range<R> && output_iterator<iterator_t<R>, T>;
パラメーター
R
範囲の型。
T
範囲に書き込むデータの型。
解説
output_iterator<iterator_t<R>, T>
の意味は、型がT
型の値をR
型の範囲に書き込むことができる反復子を提供することです。 つまり、 output_iterator
以上をサポートします。
random_access_range
random_access_range
は、インデックスによって範囲を読み書きできます。
template<class T>
concept random_access_range =
bidirectional_range<T> && random_access_iterator<iterator_t<T>>;
パラメーター
T
sized_range
かどうかをテストする型。
解説
この種類の範囲では、 random_access_iterator
以上がサポートされます。 random_access_range
には、input_range
、output_range
、forward_range
、およびbidirectional_range
の機能があります。 random_access_range
は並べ替え可能です。
random_access_range
の例としては、std::vector
、std::array
、std::deque
があります。
range
型が満たす必要がある要件を range
に定義します。 range
は反復子とセンチネルを提供し、要素を反復処理できるようにします。
template<class T>
concept range = requires(T& rg)
{
ranges::begin(rg);
ranges::end(rg);
};
パラメーター
T
range
かどうかをテストする型。
解説
range
の要件は次のとおりです。
std::ranges::begin()
を使用して反復処理できます。std::ranges::end()
ranges::begin()
ranges::end()
償却定数時間で実行され、range
は変更されません。 償却定数時間は O(1) を意味するわけではありませんが、最悪の場合でも、一連の呼び出しの平均コストは O(n^2) ではなく O(n) です。[ranges::begin(), ranges::end())
は有効な範囲を表します。
Simple_View
Simple_View
は、一部のranges
インターフェイスで使用される、公開専用の概念です。 ライブラリでは定義されていません。 仕様では、一部の範囲アダプターの動作を説明するためにのみ使用されます。
template<class V>
concept Simple_View = // exposition only
ranges::view<V> && ranges::range<const V> &&
std::same_as<std::ranges::iterator_t<V>, std::ranges::iterator_t<const V>> &&
std::same_as<std::ranges::sentinel_t<V>, std::ranges::sentinel_t<const V>>;
パラメーター
V
Simple_View
かどうかをテストする型。
解説
ビュー V
は、次のすべてが当てはまる場合の Simple_View
です。
V
はビューですconst V
は範囲ですv
とconst V
のどちらも、同じ反復子とセンチネル型を持っています。
sized_range
sized_range
は、償却定数時間の範囲内の要素の数を提供します。
template<class T>
concept sized_range = range<T> &&
requires(T& t) { ranges::size(t); };
パラメーター
T
sized_range
かどうかをテストする型。
解説
sized_range
の要件は、ranges::size
を呼び出すことです。
- 範囲は変更されません。
- 償却定数時間の要素数を返します。 償却定数時間は O(1) を意味するわけではありませんが、最悪の場合でも、一連の呼び出しの平均コストは O(n^2) ではなく O(n) です。
sized_range
の例としては、std::list
とstd::vector
があります。
例: sized_range
次の例は、int
のvector
がsized_range
であることを示しています。
// requires /std:c++20 or later
#include <ranges>
#include <iostream>
#include <vector>
int main()
{
std::cout << std::boolalpha << std::ranges::sized_range<std::vector<int>> << '\n'; // outputs "true"
}
view
view
には、要素の数に関係なく、一定の時間移動の構築、割り当て、および破棄操作があります。 ビューはコピー構築可能またはコピー割り当て可能である必要はありませんが、割り当て可能な場合は、それらの操作も一定の時間で実行する必要があります。
一定の時間要件があるため、ビューを効率的に作成できます。 たとえば、input
と呼ばれるint
のベクトル、数値が 3 で割り切れるかどうかを判断する関数、および数値を 2 乗する関数を指定すると、ステートメントauto x = input | std::views::filter(divisible_by_three) | std::views::transform(square);
、入力内の数値の 2 乗を 3 で割り切るビューを効率的に生成します。 ビューと |
の接続は、ビューの作成と呼ばれます。 型が view
の概念を満たしている場合は、効率的に構成できます。
template<class T>
concept view = ranges::range<T> && std::movable<T> && ranges::enable_view<T>;
パラメーター
T
ビューであるかどうかを確認するためにテストする型。
解説
ビューを構成可能にする重要な要件は、移動/コピーが安いということです。 これは、ビューが別のビューで構成されるときに移動またはコピーされるためです。 可動範囲でなければなりません。
ranges::enable_view<T>
は、 view
概念のセマンティック要件への準拠を要求するために使用される特性です。 型は次の方法でオプトインできます。
- の専門化から明確に導き出す
ranges::view_interface
- 空のクラス
ranges::view_base
から明示的に派生する、または - に特化した
ranges::enable_view<T>
true
オプション 1 は、 view_interface
で記述する必要のある定型コードを保存する既定の実装も提供するため、推奨されます。
失敗した場合、オプション 2 はオプション 3 よりも少し簡単です。
オプション 3 の利点は、型の定義を変更せずに実行できる点です。
viewable_range
viewable_range
は、ビューであるか、1 つに変換できる型です。
template<class T>
concept viewable_range =
range<T> && (borrowed_range<T> || view<remove_cvref_t<T>>);
パラメーター
T
ビューであるか、1 に変換できるかを確認するためにテストする型。
解説
範囲をビューに変換するには、 std::ranges::views::all()
を使用します。