次の方法で共有


<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_rangeforward_rangeinput_range、および output_rangeの機能があります。 例外は input_rangeであり、書き込むことができないため、 output_rangeの機能はありません。

範囲反復子階層の図。input_rangeとoutput_rangeが最も基本的な反復子です。forward_range次に、input_rangeとoutput_rangeの両方を絞り込みます。bidirectional_range forward_rangeを絞り込みます。random_access_range bidirectional_rangeを絞り込みます。最後に、contiguous_range random_access_rangeを絞り込みます。

その他の範囲の概念は次のとおりです。

範囲の概念 説明
rangeC++20 反復子とセンチネルを提供する型を指定します。
borrowed_rangeC++20 範囲の反復子の有効期間が範囲の有効期間に関連付けられていないことを指定します。
common_rangeC++20 範囲の反復子の型と範囲のセンチネルの型が同じであることを指定します。
Simple_ViewC++20 標準ライブラリの一部として定義されている公式の概念ではありませんが、一部のインターフェイスでヘルパーの概念として使用されます。
sized_rangeC++20 要素の数を効率的に提供できる範囲を指定します。
viewC++20 移動の構築、割り当て、および破棄が効率的な (一定時間) 型を指定します。
viewable_rangeC++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::setstd::vectorstd::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の場合、範囲の有効期間がいつ終了しても、適切に定義された動作で反復子を引き続き使用できる場合があります。

たとえば、コンテナーの有効期間が終了すると、反復子は破棄された要素を参照するため、 vectorlist などのコンテナーでは、これが当てはまらない場合です。

たとえば、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::arraystd::vectorstd::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_rangebegin()を複数回呼び出すと、未定義の動作が発生します。
  • 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_rangeoutput_rangeforward_range、およびbidirectional_rangeの機能があります。 random_access_rangeは並べ替え可能です。

random_access_rangeの例としては、std::vectorstd::arraystd::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 は範囲です
  • vconst 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::liststd::vectorがあります。

例: sized_range

次の例は、intvectorsized_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() を使用します。

関連項目

<ranges>
範囲アダプター
クラスの表示