反復子の概念

概念は、コンパイル時にテンプレート パラメーターを制約する 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 プレビュー 4 以降に渡すと、概念が false に評価 dividable<char*> エラーは、失敗した式要件 (a / b) を直接示します。

反復子の概念は、 std 名前空間で定義され、 <iterator> ヘッダー ファイルで宣言されます。 これらは、範囲アダプタービューなどの宣言で使用されます。

反復子には 6 つのカテゴリがあります。 これらは、 Range の概念に記載されている範囲のカテゴリに直接関連しています。

次の反復子の概念は、機能の向上順に示されています。 input_or_output_iterator は機能階層の下端にあり、 contiguous_iterator はハイエンドです。 階層内の上位の反復子は、通常、下位の反復子の代わりに使用できますが、その逆は使用できません。 たとえば、 random_access_iterator 反復子は forward_iteratorの代わりに使用できますが、逆の方法では使用できません。 例外は input_iteratorであり、 output_iterator の代わりに使用することはできません。これは書き込みできないためです。

反復子階層の図。input_or_output_iteratorがベースです。input_iteratorとoutput_iteratorは、調整input_or_output_iteratorとして表示されます。forward_iterator次に、input_iteratorとoutput_iteratorの両方を絞り込みます。bidirectional_iterator forward_iteratorを絞り込みます。random_access_iterator bidirectional_iteratorを絞り込みます。最後に、contiguous_iterator random_access_iteratorを絞り込みます。

次の表の "Multi-pass" は、反復子が同じ要素を複数回再訪できるかどうかを示しています。 たとえば、 vector::iterator は、反復子のコピーを作成し、コレクション内の要素を読み取り、コピー内の値に反復子を復元し、同じ要素をもう一度再検討できるため、マルチパス反復子です。 反復子が単一パスの場合、コレクション内の要素にアクセスできるのは 1 回だけです。

次の表では、"型の例" は、概念を満たすコレクション/反復子を参照します。

反復子の概念 説明 Direction 読み取り/書き込み マルチパス 型の例
input_or_output_iteratorC++20 反復子の概念分類の基礎。 転送 読み取り/書き込み いいえ istream_iterator, ostream_iterator
output_iteratorC++20 書き込み可能な反復子を指定します。 転送 書き込み いいえ ostream, inserter
input_iteratorC++20 1 回から読み取ることができる反復子を指定します。 転送 読み込み いいえ istream, istreambuf_iterator
forward_iteratorC++20 複数回読み取り (および場合によっては書き込み) できる反復子を指定します。 転送 読み取り/書き込み はい vector, list
bidirectional_iteratorC++20 前方と後方の両方で読み書きできる反復子を指定します。 次方向または前方向 読み取り/書き込み はい listsetmultisetmapmultimap
random_access_iteratorC++20 インデックスで読み書きできる反復子を指定します。 次方向または前方向 読み取り/書き込み はい vectorarraydeque
contiguous_iteratorC++20 要素がメモリ内でシーケンシャルであり、同じサイズであり、ポインター算術演算を使用してアクセスできる反復子を指定します。 次方向または前方向 読み取り/書き込み はい arrayvector string

その他の反復子の概念は次のとおりです。

反復子の概念 説明
sentinel_forC++20 型が反復子型のセンチネルであることを指定します。
sized_sentinel_forC++20 反復子とそのセンチネルを ( - を使用して) 減算して、定数時間の差を見つけることを指定します。

bidirectional_iterator

bidirectional_iteratorは、前後の読み取りと書き込みをサポートします。

template<class I>
concept bidirectional_iterator =
    forward_iterator<I> &&
    derived_from<ITER_CONCEPT(I), bidirectional_iterator_tag> &&
    requires(I i) {
        {--i} -> same_as<I&>;
        {i--} -> same_as<I>;
};

パラメーター

I
bidirectional_iteratorかどうかをテストする反復子。

解説

bidirectional_iteratorにはforward_iteratorの機能がありますが、後方に反復処理することもできます。

bidirectional_iteratorで使用できるコンテナーの例としては、setmultisetmapmultimapvectorlistがあります。

例: bidirectional_iterator

次の例では、 bidirectional_iterator の概念を使用して、 vector<int>bidirectional_iteratorがあることを示します。

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    std::cout << std::boolalpha << std::bidirectional_iterator<std::vector<int>::iterator> << '\n'; // outputs "true"

    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::contiguous_iterator<decltype(v)::iterator>; // outputs true
}

contiguous_iterator

要素がメモリ内でシーケンシャルであり、同じサイズであり、ポインター算術演算を使用してアクセスできる反復子を指定します。

template<class I>
    concept contiguous_iterator =
        random_access_iterator<I> &&
        derived_from<ITER_CONCEPT(I), contiguous_iterator_tag> &&
        is_lvalue_reference_v<iter_reference_t<I>> &&
        same_as<iter_value_t<I>, remove_cvref_t<iter_reference_t<I>>> &&
        requires(const I& i) {
            { to_address(i) } -> same_as<add_pointer_t<iter_reference_t<I>>>;
        };

パラメーター

I
contiguous_iteratorかどうかをテストする型。

解説

要素はメモリ内に順番に配置され、サイズが同じであるため、ポインターの算術演算によって contiguous_iterator にアクセスできます。 contiguous_iteratorの例としては、arrayvectorstringがあります。

例: contiguous_iterator

次の例では、 contiguous_iterator の概念を使用して、 vector<int>contiguous_iteratorがあることを示します。

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    // Show that vector<int> has a contiguous_iterator
    std::cout << std::boolalpha << std::contiguous_iterator<std::vector<int>::iterator> << '\n'; // outputs "true"
    
    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::contiguous_iterator<decltype(v)::iterator>; // outputs true
}

forward_iterator

input_iteratoroutput_iteratorの機能があります。 コレクションの反復処理を複数回サポートします。

template<class I>
    concept forward_iterator =
        input_iterator<I> &&
        derived_from<ITER_CONCEPT(I), forward_iterator_tag> &&
        incrementable<I> &&
        sentinel_for<I, I>;

パラメーター

I
forward_iteratorかどうかをテストする反復子。

解説

forward_iteratorは前に進むだけです。

forward_iteratorで使用できるコンテナーの例としては、vectorlistunordered_setunordered_multisetunordered_mapunordered_multimapがあります。

例: forward_iterator

次の例では、 forward_iterator の概念を使用して、 vector<int>forward_iteratorがあることを示します。

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    // Show that vector has a forward_iterator
    std::cout << std::boolalpha << std::forward_iterator<std::vector<int>::iterator> << '\n'; // outputs "true"

    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::forward_iterator<decltype(v)::iterator>; // outputs true
}

input_iterator

input_iteratorは、少なくとも 1 回読み取ることができる反復子です。

template<class I>
concept input_iterator =
    input_or_output_iterator<I> &&
    indirectly_readable<I> &&
    requires { typename ITER_CONCEPT(I); } &&
    derived_from<ITER_CONCEPT(I), input_iterator_tag>;

パラメーター

I
input_iteratorかどうかをテストする型。

解説

input_iteratorbegin()を複数回呼び出すと、未定義の動作が発生します。 input_iteratorモデルのみの型はマルチパスではありません。 たとえば、標準入力 (cin) からの読み取りを検討してください。 この場合、現在の要素は 1 回だけ読み取ることができ、既に読み取った文字を再読み取りすることはできません。 input_iteratorは前方にのみ読み取り、後方には読み取りません。

例: input_iterator

次の例では、 input_iterator の概念を使用して、 istream_iteratorinput_iteratorがあることを示します。

// requires /std:c++20 or later
#include <iostream>

int main()
{
    // Show that a istream_iterator has an input_iterator
    std::cout << std::boolalpha << std::input_iterator<std::istream_iterator<int>>; // outputs true
}

input_or_output_iterator

input_or_output_iteratorは、反復子の概念分類の基礎です。 これは、反復子の逆参照とインクリメントをサポートします。 すべての反復子モデル input_or_output_iterator

template<class I>
concept input_or_output_iterator =
    requires(I i) {
        { *i } -> can-reference;
    } &&
    weakly_incrementable<I>;

パラメーター

I
input_or_output_iteratorかどうかをテストする型。

解説

can-reference概念は、I型が参照、ポインター、または参照に暗黙的に変換できる型であることを意味します。

例: input_or_output_iterator

次の例では、 input_or_output_iterator の概念を使用して、 vector<int>input_or_output_iteratorがあることを示します。

// requires /std:c++20 or later
#include <iostream>

int main()
{
    // Show that a vector has an input_or_output_iterator
    std::cout << std::boolalpha << std::input_or_output_iterator<std::vector<int>::iterator> << '\n'; // outputs true

    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::input_or_output_iterator<decltype(v)::iterator>; // outputs true
}

output_iterator

output_iteratorは、書き込み可能な反復子です。

template<class I, class T>
concept output_iterator =
    input_or_output_iterator<I> &&
    indirectly_writable<I, T> &&
    requires(I i, T&& t) {
        *i++ = std::forward<T>(t);
    };

パラメーター

I
output_iteratorかどうかをテストする型。

T
書き込む値の型。

解説

output_iteratorはシングル パスです。 つまり、同じ要素に書き込むことができるのは 1 回だけです。

例: output_iterator

次の例では、 output_iterator の概念を使用して、 vector<int>output_iteratorがあることを示します。

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    // Show that vector<int> has an output_iterator
    std::cout << std::boolalpha << std::output_iterator<std::vector<int>::iterator, int> << "\n"; // outputs "true"

    // another way to test
    std::vector<int> v = {0,1,2,3,4,5};
    std::cout << std::boolalpha << std::output_iterator<decltype(v)::iterator, int>; // outputs true
}

random_access_iterator

random_access_iteratorは、インデックスによって読み取りまたは書き込みを行うことができます。

template<class I>
concept random_access_iterator =
    bidirectional_iterator<I> &&
    derived_from<ITER_CONCEPT(I), random_access_iterator_tag> &&
    totally_ordered<I> &&
    sized_sentinel_for<I, I> &&
    requires(I i, const I j, const iter_difference_t<I> n) {
        { i += n } -> same_as<I&>;
        { j + n } -> same_as<I>;
        { n + j } -> same_as<I>;
        { i -= n } -> same_as<I&>;
        { j - n } -> same_as<I>;
        { j[n] } -> same_as<iter_reference_t<I>>;
    };

パラメーター

I
random_access_iteratorかどうかをテストする型。

解説

random_access_iteratorには、input_iteratoroutput_iteratorforward_iterator、およびbidirectional_iteratorの機能があります。

random_access_iteratorの例としては、vectorarraydequeがあります。

例: random_access_iterator

次の例は、 vector<int>random_access_iteratorがあることを示しています。

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    // Show that vector<int> has a random_access_iterator
    std::cout << std::boolalpha << std::random_access_iterator<std::vector<int>::iterator> << '\n'; // outputs "true"

    // another way to test
    std::vector<int> v = {0,1,2};
    std::cout << std::boolalpha << std::random_access_iterator<decltype(v)::iterator>; // outputs true
}    

sentinel_for

型が反復子のセンチネルであることを指定します。

template<class S, class I>
concept sentinel_for =
    semiregular<S> &&
    input_or_output_iterator<I> &&
    weakly-equality-comparable-with <S, I>;

パラメーター

I
反復子の型。

S
Iのセンチネルであるかどうかを確認するためにテストする型。

解説

Sentinel は、反復子と比較して、反復子が末尾に達したかどうかを判断できる型です。 この概念では、型が、input_iteratoroutput_iteratorforward_iteratorbidirectional_iteratorrandom_access_iterator、およびcontiguous_iteratorを含む、input_or_output_iterator型のセンチネルであるかどうかを決定します。

例: sentinel_for

次の例では、 sentinel_for の概念を使用して、 vector<int>::iteratorvector<int>のセンチネルであることを示します。

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v = {0, 1, 2};
    std::vector<int>::iterator i = v.begin();
    // show that vector<int>::iterator is a sentinel for vector<int>
    std::cout << std::boolalpha << std::sentinel_for<std::vector<int>::iterator, decltype(i)>; // outputs true
}    

sized_sentinel_for

-を使用して反復子とそのセンチネルを減算して、一定の時間で差を見つけることができることをテストします。

template<class S, class I>
concept sized_sentinel_for =
    sentinel_for<S, I> &&
    !disable_sized_sentinel_for<remove_cv_t<S>, remove_cv_t<I>> &&
    requires(const I& i, const S& s) {
        {s - i} -> same_as<iter_difference_t<I>>;
        {i - s} -> same_as<iter_difference_t<I>>;
    };

パラメーター

I
反復子の型。

S
テストする Sentinel の種類。

解説

例: sized_sentinel_for

次の例では、 sized_sentinel_for の概念を使用して、 vector<int> のセンチネルをベクター反復子から一定の時間で減算できることを確認します。

// requires /std:c++20 or later
#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v = { 1, 2, 3 };
    std::vector<int>::iterator i = v.begin();
    std::vector<int>::iterator end = v.end();
    // use the sized_sentinel_for concept to verify that i can be subtracted from end in constant time
    std::cout << std::boolalpha << std::sized_sentinel_for<decltype(end), decltype(i)> << "\n"; // outputs true
    std::cout << end - i; // outputs 3
}    

関連項目

範囲の概念
範囲アダプター
クラスの表示