反復子の概念
概念は、コンパイル時にテンプレート パラメーターを制約する 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
の代わりに使用することはできません。これは書き込みできないためです。
次の表の "Multi-pass" は、反復子が同じ要素を複数回再訪できるかどうかを示しています。 たとえば、 vector::iterator
は、反復子のコピーを作成し、コレクション内の要素を読み取り、コピー内の値に反復子を復元し、同じ要素をもう一度再検討できるため、マルチパス反復子です。 反復子が単一パスの場合、コレクション内の要素にアクセスできるのは 1 回だけです。
次の表では、"型の例" は、概念を満たすコレクション/反復子を参照します。
反復子の概念 | 説明 | Direction | 読み取り/書き込み | マルチパス | 型の例 |
---|---|---|---|---|---|
input_or_output_iterator C++20 |
反復子の概念分類の基礎。 | 転送 | 読み取り/書き込み | いいえ | istream_iterator , ostream_iterator |
output_iterator C++20 |
書き込み可能な反復子を指定します。 | 転送 | 書き込み | いいえ | ostream , inserter |
input_iterator C++20 |
1 回から読み取ることができる反復子を指定します。 | 転送 | 読み込み | いいえ | istream , istreambuf_iterator |
forward_iterator C++20 |
複数回読み取り (および場合によっては書き込み) できる反復子を指定します。 | 転送 | 読み取り/書き込み | はい | vector , list |
bidirectional_iterator C++20 |
前方と後方の両方で読み書きできる反復子を指定します。 | 次方向または前方向 | 読み取り/書き込み | はい | list 、set 、multiset 、map 、multimap 。 |
random_access_iterator C++20 |
インデックスで読み書きできる反復子を指定します。 | 次方向または前方向 | 読み取り/書き込み | はい | vector 、 array 、 deque |
contiguous_iterator C++20 |
要素がメモリ内でシーケンシャルであり、同じサイズであり、ポインター算術演算を使用してアクセスできる反復子を指定します。 | 次方向または前方向 | 読み取り/書き込み | はい | array 、 vector string 。 |
その他の反復子の概念は次のとおりです。
反復子の概念 | 説明 |
---|---|
sentinel_for C++20 |
型が反復子型のセンチネルであることを指定します。 |
sized_sentinel_for C++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
で使用できるコンテナーの例としては、set
、multiset
、map
、multimap
、vector
、list
があります。
例: 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
の例としては、array
、vector
、string
があります。
例: 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_iterator
とoutput_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
で使用できるコンテナーの例としては、vector
、list
、unordered_set
、unordered_multiset
、unordered_map
、unordered_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_iterator
でbegin()
を複数回呼び出すと、未定義の動作が発生します。 input_iterator
モデルのみの型はマルチパスではありません。 たとえば、標準入力 (cin
) からの読み取りを検討してください。 この場合、現在の要素は 1 回だけ読み取ることができ、既に読み取った文字を再読み取りすることはできません。 input_iterator
は前方にのみ読み取り、後方には読み取りません。
例: input_iterator
次の例では、 input_iterator
の概念を使用して、 istream_iterator
に input_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_iterator
、output_iterator
、forward_iterator
、およびbidirectional_iterator
の機能があります。
random_access_iterator
の例としては、vector
、array
、deque
があります。
例: 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_iterator
、output_iterator
、forward_iterator
、bidirectional_iterator
、random_access_iterator
、およびcontiguous_iterator
を含む、input_or_output_iterator
型のセンチネルであるかどうかを決定します。
例: sentinel_for
次の例では、 sentinel_for
の概念を使用して、 vector<int>::iterator
が vector<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
}