Udostępnij za pośrednictwem


Pojęcia iteracyjne

Pojęcia to funkcja języka C++20, która ogranicza parametry szablonu w czasie kompilacji. Pomagają one zapobiegać wystąpieniu nieprawidłowego szablonu, określać wymagania argumentu szablonu w czytelnym formularzu i zapewniać bardziej zwięzłe błędy kompilatora powiązane z szablonem.

Rozważmy poniższy przykład, który definiuje koncepcję, aby zapobiec utworzeniu wystąpienia szablonu z typem, który nie obsługuje dzielenia:

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

Po przekazaniu przełącznika /diagnostics:caret kompilatora do programu Visual Studio 2022 w wersji 17.4 (wersja zapoznawcza 4 lub nowsza) błąd dividable<char*> oceniany na wartość false będzie wskazywać bezpośrednio na wymaganie (a / b) wyrażenia, które nie powiodło się.

Pojęcia iteracyjne są definiowane w std przestrzeni nazw i są deklarowane w pliku nagłówkowym <iterator> . Są one używane w deklaracjach adapterów zakresów, widoków i tak dalej.

Istnieją sześć kategorii iteratorów. Są one bezpośrednio powiązane z kategoriami zakresów wymienionych w temacie Pojęcia dotyczące zakresu.

Poniższe pojęcia iteracyjne są wymienione w celu zwiększenia możliwości. input_or_output_iterator znajduje się na niskim końcu hierarchii możliwości i contiguous_iterator znajduje się na wysokim poziomie. Iteratory wyższe w hierarchii mogą być zwykle używane zamiast tych, które są niższe, ale nie odwrotnie. Na przykład random_access_iterator iterator może być używany zamiast forward_iteratorelementu , ale nie odwrotnie. Wyjątek to input_iterator, którego nie można użyć zamiast output_iterator , ponieważ nie można go zapisać.

Diagram of the iterator hierarchy. input_or_output_iterator is the base. input_iterator and output_iterator are shown as refining input_or_output_iterator. forward_iterator is next and refines both input_iterator and output_iterator. bidirectional_iterator refines forward_iterator. random_access_iterator refines bidirectional_iterator. Finally, contiguous_iterator refines random_access_iterator

W poniższej tabeli wyrażenie "Multi-pass" odnosi się do tego, czy iterator może ponownie wrócić do tego samego elementu więcej niż raz. Na przykład jest iteratorem wieloprzepustowym, vector::iterator ponieważ można utworzyć kopię iteratora, odczytać elementy w kolekcji, a następnie przywrócić iterator do wartości w kopii i ponownie przejrzeć te same elementy. Jeśli iterator jest jednoprzepustowy, można odwiedzić tylko elementy w kolekcji raz.

W poniższej tabeli "Przykładowe typy" odnosi się do kolekcji/iteratorów, które spełniają koncepcję.

Koncepcja iteratora opis Kierunek Czytaj/zapisz Multi-pass Przykładowe typy
input_or_output_iteratorC++20 Podstawa taksonomii koncepcji iteratora. Do przodu Czytaj/zapisz nie istream_iterator, ostream_iterator
output_iteratorC++20 Określa iterator, do którego można napisać. Do przodu Zapis nie ostream, inserter
input_iteratorC++20 Określa iterator, z którego można odczytać jeden raz. Do przodu Przeczytaj nie istream, istreambuf_iterator
forward_iteratorC++20 Określa iterator, który może odczytywać (i ewentualnie zapisywać) wiele razy. Do przodu Czytaj/zapisz tak vector, list
bidirectional_iteratorC++20 Określa iterator, który można odczytywać i zapisywać zarówno do przodu, jak i do tyłu. W przód lub w tył Czytaj/zapisz tak list, set, multiset, map i multimap.
random_access_iteratorC++20 Określa iterator, który można odczytywać i zapisywać według indeksu. W przód lub w tył Czytaj/zapisz tak vector, array, deque
contiguous_iteratorC++20 Określa iterator, którego elementy są sekwencyjne w pamięci, mają ten sam rozmiar i można uzyskać do niego dostęp przy użyciu arytmetyki wskaźnika. W przód lub w tył Czytaj/zapisz tak array, vectorstring.

Inne pojęcia iteracyjne obejmują:

Koncepcja iteratora opis
sentinel_forC++20 Określa, że typ jest sentynel dla typu iteratora.
sized_sentinel_forC++20 Określa, że iterator i jego sentinel można odjąć (przy użyciu ) -w celu znalezienia różnicy w stałym czasie.

bidirectional_iterator

Element bidirectional_iterator obsługuje odczytywanie i zapisywanie do przodu i do tyłu.

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>;
};

Parametry

I
Iterator do testowania, aby sprawdzić, czy jest to element bidirectional_iterator.

Uwagi

Element ma bidirectional_iterator możliwości forward_iteratorelementu , ale może również iterować do tyłu.

Niektóre przykłady kontenerów, których można używać z elementami bidirectional_iterator to set, , multiset, mapmultimap, , vectori list.

Przykład: bidirectional_iterator

W poniższym przykładzie użyto bidirectional_iterator koncepcji , aby pokazać, że vector<int> ma wartość 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

Określa iterator, którego elementy są sekwencyjne w pamięci, mają ten sam rozmiar i można uzyskać do niego dostęp przy użyciu arytmetyki wskaźnika.

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>>>;
        };

Parametry

I
Typ do przetestowania, aby sprawdzić, czy jest to contiguous_iterator.

Uwagi

Dostęp contiguous_iterator do elementu można uzyskać za pomocą arytmetyki wskaźnika, ponieważ elementy są rozmieszczone sekwencyjnie w pamięci i mają ten sam rozmiar. Niektóre przykłady elementów to contiguous_iteratorarray, vectori string.

Przykład: contiguous_iterator

W poniższym przykładzie użyto contiguous_iterator koncepcji, aby pokazać, że element vector<int> ma wartość 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

Ma możliwości elementu input_iterator i .output_iterator Obsługuje iteracji w kolekcji wiele razy.

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

Parametry

I
Iterator do testowania, aby sprawdzić, czy jest to element forward_iterator.

Uwagi

Element forward_iterator może iść do przodu tylko do przodu.

Niektóre przykłady kontenerów, których można używać z elementami forward_iterator to vector, , list, unordered_setunordered_multiset, , unordered_mapi unordered_multimap.

Przykład: forward_iterator

W poniższym przykładzie użyto forward_iterator koncepcji, aby pokazać, że element vector<int> ma wartość 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

Jest input_iterator to iterator, który można odczytać z co najmniej raz.

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>;

Parametry

I
Typ do przetestowania, aby sprawdzić, czy jest to .input_iterator

Uwagi

Wywołanie begin() metody na więcej input_iterator niż raz powoduje niezdefiniowane zachowanie. Typ, który tylko modele input_iterator nie jest wieloprzepustowy. Rozważ odczytywanie ze standardowych danych wejściowych (cin) na przykład. W takim przypadku można tylko odczytać bieżący element raz i nie można ponownie odczytać znaków, które zostały już przeczytane. Tylko input_iterator odczytuje do przodu, a nie do tyłu.

Przykład: input_iterator

W poniższym przykładzie użyto input_iterator koncepcji, aby pokazać, że element istream_iterator ma element 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

Jest input_or_output_iterator podstawą taksonomii koncepcji iteratora. Obsługuje wyłudanie i zwiększanie iteratora. Każdy iterator modeluje input_or_output_iterator.

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

Parametry

I
Typ do przetestowania, aby sprawdzić, czy jest to .input_or_output_iterator

Uwagi

Koncepcja can-reference oznacza, że typ I jest odwołaniem, wskaźnikiem lub typem, który można niejawnie przekonwertować na odwołanie.

Przykład: input_or_output_iterator

W poniższym przykładzie użyto input_or_output_iterator koncepcji , aby pokazać, że vector<int> ma element 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

An output_iterator to iterator, do którego można napisać.

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);
    };

Parametry

I
Typ do przetestowania, aby sprawdzić, czy jest to .output_iterator

T
Typ wartości do zapisania.

Uwagi

Element output_iterator jest pojedynczym przekazywaniem. Oznacza to, że może on zapisywać tylko w tym samym elemecie raz.

Przykład: output_iterator

W poniższym przykładzie użyto output_iterator koncepcji , aby pokazać, że vector<int> ma element 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

Element random_access_iterator może odczytywać lub zapisywać według indeksu.

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>>;
    };

Parametry

I
Typ do przetestowania, aby sprawdzić, czy jest to random_access_iterator.

Uwagi

Obiekt random_access_iterator ma możliwości elementów input_iterator, , output_iteratorforward_iteratori bidirectional_iterator.

Niektóre przykłady elementów to random_access_iteratorvector, arrayi deque.

Przykład: random_access_iterator

W poniższym przykładzie pokazano, że element vector<int> ma wartość 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

Określa, że typ jest sentynelem dla iteratora.

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

Parametry

I
Typ iteratora.

S
Typ do przetestowania, aby sprawdzić, czy jest to sentinel dla elementu I.

Uwagi

Sentinel to typ, który można porównać z iteratorem w celu określenia, czy iterator osiągnął koniec. Ta koncepcja określa, czy typ jest sentinel dla jednego z input_or_output_iterator typów, w tym input_iterator, , output_iterator, forward_iteratorbidirectional_iterator, random_access_iterator, i contiguous_iterator.

Przykład: sentinel_for

W poniższym przykładzie użyto sentinel_for koncepcji, aby pokazać, że vector<int>::iterator jest to sentinel dla vector<int>elementu :

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

Przetestuj, czy iterator i jego sentynel można odjąć za pomocą polecenia - , aby znaleźć różnicę w stałym czasie.

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>>;
    };

Parametry

I
Typ iteratora.

S
Typ sentinel do przetestowania.

Uwagi

Przykład: sized_sentinel_for

W poniższym przykładzie sized_sentinel_for użyto koncepcji w celu sprawdzenia, czy sentinel dla obiektu vector<int> można odjąć od iteratora wektorów w stałym czasie:

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

Zobacz też

Pojęcia dotyczące zakresu
Adaptery zakresowe
Klasy widoków