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_iteratorIterators verwendet werden, aber nicht umgekehrt. Eine Ausnahme ist input_iterator, die nicht anstelle von output_iterator Schreibvorgängen verwendet werden kann.

Diagramm der Iteratorhierarchie. input_or_output_iterator ist die Basis. input_iterator und output_iterator werden als Einschränkung input_or_output_iterator angezeigt. forward_iterator ist als Nächstes und optimiert sowohl input_iterator als auch output_iterator. bidirectional_iterator optimiert forward_iterator. random_access_iterator optimiert bidirectional_iterator. Schließlich werden contiguous_iterator random_access_iterator

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_iteratorC++20 Die Grundlage der Iteratorkonzepttaxonomie. Weiterleiten Lesen/Schreiben Nein istream_iterator, ostream_iterator
output_iteratorC++20 Gibt einen Iterator an, in den Sie schreiben können. Weiterleiten Schreiben Nein ostream, inserter
input_iteratorC++20 Gibt einen Iterator an, den Sie einmal lesen können. Weiterleiten Lesen Sie Nein istream, istreambuf_iterator
forward_iteratorC++20 Gibt einen Iterator an, der mehrmals lesen (und möglicherweise schreiben) kann. Weiterleiten Lesen/Schreiben ja vector, list
bidirectional_iteratorC++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_iteratorC++20 Gibt einen Iterator an, den Sie nach Index lesen und schreiben können. Vorwärts oder rückwärts Lesen/Schreiben ja vector, arraydeque
contiguous_iteratorC++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_forC++20 Gibt an, dass ein Typ ein Sentinel für einen Iteratortyp ist.
sized_sentinel_forC++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 vectorlist.

Beispiel: bidirectional_iterator

Im folgenden Beispiel wird das bidirectional_iterator Konzept verwendet, um folgendes anzuzeigenbidirectional_iteratorvector<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_iteratorhat:

// 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_mapunordered_multimap.

Beispiel: forward_iterator

Im folgenden Beispiel wird das forward_iterator Konzept verwendet, um zu zeigen, dass ein vector<int> Folgendes forward_iteratorhat:

// 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_iteratorhat:

// 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_iteratorund 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_iteratorhat:

// 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_iteratorund 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
}    

Siehe auch

Bereichskonzepte
Bereichsadapter
Anzeigen von Klassen