Iteratorkonzepte
Konzepte sind ein C++20-Sprachfeature, das Vorlagenparameter zur Kompilierungszeit einschränkt. Sie helfen dabei, falsche Vorlageninstanziierung zu verhindern, Vorlagenargumentanforderungen in lesbarer Form anzugeben und prägnantere Compilerfehler bereitzustellen.
Betrachten Sie das folgende Beispiel, das ein Konzept definiert, um zu verhindern, dass die Vorlage mit einem Typ instanziiert wird, der keine Division unterstützt:
// 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
}
Wenn Sie den Compilerwechsel /diagnostics:caret
an Visual Studio 2022, Version 17.4 Preview 4 oder höher, übergeben, verweist der auf "false" ausgewertete Fehler dividable<char*>
direkt auf die fehlgeschlagene Ausdrucksanforderung (a / b)
.
Iteratorkonzepte werden im std
Namespace definiert und in der <iterator>
Headerdatei deklariert. Sie werden in den Deklarationen von Bereichsadaptern, Ansichten usw. verwendet.
Es gibt sechs Kategorien von Iteratoren. Sie beziehen sich direkt auf die Kategorien von Bereichen, die unter Range-Konzepten aufgeführt sind.
Die folgenden Iteratorkonzepte werden aufgelistet, um die Funktion zu erhöhen. input_or_output_iterator
befindet sich am unteren Ende der Funktionshierarchie und contiguous_iterator
ist am hohen Ende. Iteratoren, die sich weiter oben in der Hierarchie befinden, können in der Regel anstelle derjenigen verwendet werden, die niedriger sind, aber nicht umgekehrt. Ein Iterator kann z random_access_iterator
. B. anstelle eines forward_iterator
Iterators verwendet werden, aber nicht umgekehrt. Eine Ausnahme ist input_iterator
, die nicht anstelle von output_iterator
Schreibvorgängen verwendet werden kann.
In der folgenden Tabelle bezieht sich "Multidurchlauf" darauf, ob der Iterator dasselbe Element mehrmals überarbeiten kann. Ist z. B. ein Multi-Pass-Iterator, vector::iterator
da Sie eine Kopie des Iterators erstellen, die Elemente in der Auflistung lesen und dann den Iterator auf den Wert in der Kopie wiederherstellen und dieselben Elemente erneut aufrufen können. Wenn ein Iterator single pass ist, können Sie die Elemente in der Sammlung nur einmal besuchen.
In der folgenden Tabelle bezieht sich "Beispieltypen" auf Auflistungen/Iteratoren, die das Konzept erfüllen.
Iterator-Konzept | Beschreibung | Direction | Lesen/Schreiben | Mehrfachdurchlauf | Beispieltypen |
---|---|---|---|---|---|
input_or_output_iterator C++20 |
Die Grundlage der Iteratorkonzepttaxonomie. | Weiterleiten | Lesen/Schreiben | Nein | istream_iterator , ostream_iterator |
output_iterator C++20 |
Gibt einen Iterator an, in den Sie schreiben können. | Weiterleiten | Schreiben | Nein | ostream , inserter |
input_iterator C++20 |
Gibt einen Iterator an, den Sie einmal lesen können. | Weiterleiten | Lesen Sie | Nein | istream , istreambuf_iterator |
forward_iterator C++20 |
Gibt einen Iterator an, der mehrmals lesen (und möglicherweise schreiben) kann. | Weiterleiten | Lesen/Schreiben | ja | vector , list |
bidirectional_iterator C++20 |
Gibt einen Iterator an, den Sie sowohl vorwärts als auch rückwärts lesen und schreiben können. | Vorwärts oder rückwärts | Lesen/Schreiben | ja | list , set , multiset , map , und multimap . |
random_access_iterator C++20 |
Gibt einen Iterator an, den Sie nach Index lesen und schreiben können. | Vorwärts oder rückwärts | Lesen/Schreiben | ja | vector , array deque |
contiguous_iterator C++20 |
Gibt einen Iterator an, dessen Elemente im Arbeitsspeicher sequenziell sind, die gleiche Größe aufweisen und mithilfe von Zeigerarithmetik darauf zugegriffen werden kann. | Vorwärts oder rückwärts | Lesen/Schreiben | ja | array , vector string . |
Weitere Iteratorkonzepte umfassen:
Iterator-Konzept | Beschreibung |
---|---|
sentinel_for C++20 |
Gibt an, dass ein Typ ein Sentinel für einen Iteratortyp ist. |
sized_sentinel_for C++20 |
Gibt an, dass ein Iterator und sein Sentinel subtrahiert werden können (mithilfe von - ) um ihre Differenz in konstanter Zeit zu finden. |
bidirectional_iterator
A bidirectional_iterator
unterstützt das Lesen und Schreiben vorwärts und rückwärts.
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>;
};
Parameter
I
Der Iterator, der überprüft, ob es sich um ein bidirectional_iterator
.
Hinweise
A bidirectional_iterator
verfügt über die Funktionen eines forward_iterator
, kann aber auch rückwärts durchlaufen.
Einige Beispiele für Container, die mit einem bidirectional_iterator
verwendet werden können, sind set
: , multiset
, map
, , multimap
, und vector
list
.
Beispiel: bidirectional_iterator
Im folgenden Beispiel wird das bidirectional_iterator
Konzept verwendet, um folgendes anzuzeigenbidirectional_iterator
vector<int>
:
// 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
Gibt einen Iterator an, dessen Elemente im Arbeitsspeicher sequenziell sind, die gleiche Größe aufweisen und mithilfe von Zeigerarithmetik darauf zugegriffen werden kann.
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>>>;
};
Parameter
I
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine contiguous_iterator
.
Hinweise
Der Zugriff auf eine contiguous_iterator
Zeigerarithmetik ist möglich, da die Elemente sequenziell im Speicher angeordnet sind und die gleiche Größe aufweisen. Einige Beispiele für eine contiguous_iterator
sind array
, vector
, und string
.
Beispiel: contiguous_iterator
Im folgenden Beispiel wird das contiguous_iterator
Konzept verwendet, um zu zeigen, dass ein vector<int>
Folgendes contiguous_iterator
hat:
// 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
Verfügt über die Funktionen eines input_iterator
und eines output_iterator
. Unterstützt das Durchlaufen einer Auflistung mehrmals.
template<class I>
concept forward_iterator =
input_iterator<I> &&
derived_from<ITER_CONCEPT(I), forward_iterator_tag> &&
incrementable<I> &&
sentinel_for<I, I>;
Parameter
I
Der Iterator, der überprüft, ob es sich um ein forward_iterator
.
Hinweise
A forward_iterator
kann nur vorwärts gehen.
Einige Beispiele für Container, die mit einem forward_iterator
verwendet werden können, sind vector
: , list
, unordered_set
, , unordered_multiset
, und unordered_map
unordered_multimap
.
Beispiel: forward_iterator
Im folgenden Beispiel wird das forward_iterator
Konzept verwendet, um zu zeigen, dass ein vector<int>
Folgendes forward_iterator
hat:
// 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
Ein input_iterator
Iterator ist ein Iterator, den Sie mindestens einmal lesen können.
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>;
Parameter
I
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine input_iterator
.
Hinweise
Das Aufrufen eines input_iterator
mehr als einmal ausgeführten Aufrufs begin()
führt zu einem nicht definierten Verhalten. Ein Typ, der nur Modelle input_iterator
ist, ist kein Multidurchlauf. Ziehen Sie z. B. das Lesen von Standardeingaben (cin
) in Betracht. In diesem Fall können Sie das aktuelle Element nur einmal lesen, und Sie können die bereits gelesenen Zeichen nicht erneut lesen. Eine input_iterator
nur vorgelesene, nicht rückwärts.
Beispiel: input_iterator
Im folgenden Beispiel wird das input_iterator
Konzept verwendet, um zu zeigen, dass ein Folgendes istream_iterator
input_iterator
hat:
// 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
An input_or_output_iterator
ist die Grundlage der Iteratorkonzepttaxonomie. Es unterstützt das Ableiten und Erhöhen eines Iterators. Alle Iteratormodelle input_or_output_iterator
.
template<class I>
concept input_or_output_iterator =
requires(I i) {
{ *i } -> can-reference;
} &&
weakly_incrementable<I>;
Parameter
I
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine input_or_output_iterator
.
Hinweise
Das Konzept can-reference
bedeutet, dass der Typ I
ein Bezug, ein Zeiger oder ein Typ ist, der implizit in einen Verweis konvertiert werden kann.
Beispiel: input_or_output_iterator
Im folgenden Beispiel wird das input_or_output_iterator
Konzept verwendet, um zu zeigen, dass vector<int>
ein 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
Ein output_iterator
Iterator, in den Sie schreiben können.
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);
};
Parameter
I
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine output_iterator
.
T
Der Typ der zu schreibenden Werte.
Hinweise
Ein einzelner output_iterator
Durchlauf. Das heißt, es kann nur einmal in dasselbe Element geschrieben werden.
Beispiel: output_iterator
Im folgenden Beispiel wird das output_iterator
Konzept verwendet, um zu zeigen, dass vector<int>
ein 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
Ein random_access_iterator
Kann nach Index lesen oder schreiben.
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>>;
};
Parameter
I
Der Typ, der getestet werden soll, um festzustellen, ob es sich um eine random_access_iterator
.
Hinweise
A random_access_iterator
verfügt über die Funktionen eines input_iterator
, output_iterator
, , forward_iterator
und bidirectional_iterator
.
Einige Beispiele für eine random_access_iterator
sind vector
, array
, und deque
.
Beispiel: random_access_iterator
Das folgende Beispiel zeigt, dass ein Objekt folgendes vector<int>
random_access_iterator
hat:
// 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
Gibt an, dass ein Typ ein Sentinel für einen Iterator ist.
template<class S, class I>
concept sentinel_for =
semiregular<S> &&
input_or_output_iterator<I> &&
weakly-equality-comparable-with <S, I>;
Parameter
I
Der Iteratortyp.
S
Der Typ, der getestet werden soll, um festzustellen, ob es sich um einen Sentinel handelt I
.
Hinweise
Ein Sentinel ist ein Typ, der mit einem Iterator verglichen werden kann, um festzustellen, ob der Iterator das Ende erreicht hat. Dieses Konzept bestimmt, ob ein Typ ein Sentinel für einen der input_or_output_iterator
Typen ist, einschließlich input_iterator
, output_iterator
, , , forward_iterator
, bidirectional_iterator
, random_access_iterator
und contiguous_iterator
.
Beispiel: sentinel_for
Im folgenden Beispiel wird das sentinel_for
Konzept verwendet, um zu zeigen, dass vector<int>::iterator
es sich um einen Sentinel für 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
Testen Sie, ob ein Iterator und sein Sentinel unter Verwendung -
des Unterschieds in konstanter Zeit subtrahiert werden können.
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>>;
};
Parameter
I
Der Iteratortyp.
S
Der zu testende Sentineltyp.
Hinweise
Beispiel: sized_sentinel_for
Im folgenden Beispiel wird das sized_sentinel_for
Konzept verwendet, um zu überprüfen, ob der Sentinel für einen vector<int>
von den Vektoren iterator in konstanter Zeit subtrahiert werden kann:
// 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
}