Udostępnij za pośrednictwem


<ranges> Pojęcia

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 dotyczące zakresu są definiowane w std::ranges przestrzeni nazw i deklarowane w pliku nagłówka <ranges> . Są one używane w deklaracjach adapterów zakresów, widoków i tak dalej.

Istnieją sześć kategorii zakresów. Są one powiązane z kategoriami iteratorów wymienionych w <iterator> pojęciach. Aby zwiększyć możliwości, kategorie to:

Koncepcja zakresu opis
output_range
input_range
Określa zakres, do którego można napisać.
Określa zakres, z którego można odczytać jeden raz.
forward_range Określa zakres, który można odczytać (i ewentualnie zapisywać) wiele razy.
bidirectional_range Określa zakres, który można odczytywać i zapisywać zarówno do przodu, jak i do tyłu.
random_access_range Określa zakres, który można odczytywać i zapisywać według indeksu.
contiguous_range Określa zakres, którego elementy są sekwencyjne w pamięci, mają ten sam rozmiar i mogą być dostępne przy użyciu arytmetyki wskaźnika.

W poprzedniej tabeli pojęcia są wymienione w celu zwiększenia możliwości. Zakres, który spełnia wymagania koncepcji, zwykle spełnia wymagania pojęć w wierszach poprzedzających je. Na przykład element ma random_access_range funkcję bidirectional_range, forward_range, input_rangei output_range. Wyjątek to input_range, którego nie można zapisać, więc nie ma możliwości programu output_range.

Diagram of the ranges iterator hierarchy. input_range and output_range are the most basic iterators. forward_range is next and refines both input_range and output_range. bidirectional_range refines forward_range. random_access_range refines bidirectional_range. Finally, contiguous_range refines random_access_range

Inne pojęcia dotyczące zakresu obejmują:

Koncepcja zakresu opis
rangeC++20 Określa typ, który udostępnia iterator i sentinel.
borrowed_rangeC++20 Określa, że okres istnienia iteratorów zakresu nie jest powiązany z okresem istnienia zakresu.
common_rangeC++20 Określa, że typ iteratora zakresu i typ sentinel zakresu są takie same.
Simple_ViewC++20 Nie jest to oficjalna koncepcja zdefiniowana jako część standardowej biblioteki, ale używana jako koncepcja pomocnika w niektórych interfejsach.
sized_rangeC++20 Określa zakres, który może efektywnie podać jego liczbę elementów.
viewC++20 Określa typ, który ma wydajny (stały czas) przenoszenie konstrukcji, przypisania i zniszczenia.
viewable_rangeC++20 Określa typ, który jest widokiem lub można go przekonwertować na jeden.

bidirectional_range

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

template<class T>
concept bidirectional_range =
    forward_range<T> && bidirectional_iterator<iterator_t<T>>;

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to bidirectional_range.

Uwagi

Ten rodzaj zakresu obsługuje bidirectional_iterator lub większą. Element ma bidirectional_iterator możliwości forward_iteratorelementu , ale może również iterować do tyłu.

Niektóre przykłady elementów to bidirectional_rangestd::set, std::vectori std::list.

borrowed_range

Modele borrowed_range typów, jeśli ważność iteratorów uzyskanych z obiektu może przetrwać okres istnienia obiektu. Oznacza to, że iteratory dla zakresu mogą być używane nawet wtedy, gdy zakres już nie istnieje.

template<class T>
concept borrowed_range =
    range<T> &&
    (is_lvalue_reference_v<T> || enable_borrowed_range<remove_cvref_t<T>>);

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to borrowed_range.

Uwagi

Okres istnienia zakresu rvalue może zakończyć się po wywołaniu funkcji niezależnie od tego, czy modele borrowed_range zakresu. Jeśli jest borrowed_rangeto element , możesz nadal używać iteratorów z dobrze zdefiniowanym zachowaniem niezależnie od tego, kiedy okres istnienia zakresu kończy się.

Przypadki, w których tak nie jest, na przykład w przypadku kontenerów, takich jak vector lub list dlatego, że po zakończeniu okresu istnienia kontenera iteratory odwołują się do elementów, które zostały zniszczone.

Można nadal używać iteratorów dla borrowed_rangeobiektu , na przykład dla obiektu viewiota_view<int>{0, 42} lubiego, którego iteratory są ponad zestawem wartości, które nie podlegają niszczeniu, ponieważ są generowane na żądanie.

common_range

Typ iteratora obiektu common_range jest taki sam jak typ sentinel. Oznacza to, begin() że zwraca end() ten sam typ.

template<class T>
concept common_range =
   ranges::range<T> && std::same_as<ranges::iterator_t<T>, ranges::sentinel_t<T>>;

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to common_range.

Uwagi

Pobieranie typu z std::ranges::begin() i std::ranges::end() jest ważne dla algorytmów, które obliczają odległość między dwoma iteratorami, oraz algorytmy, które akceptują zakresy oznaczone przez pary iteracyjne.

Standardowe kontenery (na przykład vector) spełniają wymagania programu common_range.

contiguous_range

Elementy elementu contiguous_range są przechowywane sekwencyjnie w pamięci i mogą być dostępne przy użyciu arytmetyki wskaźnika. Na przykład tablica to contiguous_range.

template<class T>
concept contiguous_range =
    random_access_range<T> && contiguous_iterator<iterator_t<T>> &&
    requires(T& t) {{ ranges::data(t) } -> same_as<add_pointer_t<range_reference_t<T>>>;};

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to contiguous_range.

Uwagi

Dostęp contiguous_range do elementu można uzyskać za pomocą arytmetyki wskaźnika, ponieważ elementy są rozmieszczone sekwencyjnie w pamięci i mają ten sam rozmiar. Ten rodzaj zakresu obsługuje funkcję continguous_iterator, która jest najbardziej elastyczna dla wszystkich iteratorów.

Niektóre przykłady elementów to contiguous_rangestd::array, std::vectori std::string.

Przykład: contiguous_range

W poniższym przykładzie pokazano użycie arytmetyki wskaźnika w celu uzyskania dostępu do elementu contiguous_range:

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

int main()
{
    // Show that vector is a contiguous_range
    std::vector<int> v = {0,1,2,3,4,5};
    std::cout << std::boolalpha << std::ranges::contiguous_range<decltype(v)> << '\n'; // outputs true

    // Show that pointer arithmetic can be used to access the elements of a contiguous_range
    auto ptr = v.data();
    ptr += 2;
    std::cout << *ptr << '\n'; // outputs 2
}
true
2

forward_range

Element forward_range obsługuje odczytywanie (i ewentualnie pisanie) zakres wiele razy.

template<class T>
concept forward_range = input_range<T> && forward_iterator<iterator_t<T>>;

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to forward_range.

Uwagi

Ten rodzaj zakresu obsługuje forward_iterator lub większą. Element forward_iterator może iterować w zakresie wiele razy.

input_range

Element input_range to zakres, z którego można odczytać jeden raz.

template<class T>
concept input_range = range<T> && input_iterator<iterator_t<T>>;

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to .input_range

Uwagi

Gdy typ spełnia wymagania :input_range

  • Funkcja ranges::begin() zwraca wartość input_iterator. Wywołanie begin() więcej niż raz w wyniku input_range niezdefiniowanego zachowania.
  • Można wielokrotnie wyłudzać input_iterator , co daje tę samą wartość za każdym razem. Element input_range nie jest wieloprzepustowy. Inkrementacja iteratora unieważnia wszystkie kopie.
  • Można go używać z ranges::for_eachprogramem .
  • Obsługuje input_iterator lub nowsze.

output_range

Jest output_range to zakres, do którego można napisać.

template<class R, class T>
concept output_range = range<R> && output_iterator<iterator_t<R>, T>;

Parametry

R
Typ zakresu.

T
Typ danych do zapisu w zakresie.

Uwagi

Oznacza output_iterator<iterator_t<R>, T> to, że typ udostępnia iterator, który może zapisywać wartości typu T w zakresie typu R. Innymi słowy, obsługuje output_iterator lub większą.

random_access_range

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

template<class T>
concept random_access_range =
bidirectional_range<T> && random_access_iterator<iterator_t<T>>;

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to sized_range.

Uwagi

Ten rodzaj zakresu obsługuje random_access_iterator lub większą. Obiekt random_access_range ma możliwości elementów input_range, , output_rangeforward_rangei bidirectional_range. Element jest random_access_range sortowalny.

Niektóre przykłady elementów to random_access_rangestd::vector, std::arrayi std::deque.

range

Definiuje wymagania, które musi spełniać typ, aby był .range Element A range udostępnia iterator i sentinel, dzięki czemu można iterować jego elementy.

template<class T>
concept range = requires(T& rg)
{
  ranges::begin(rg);
  ranges::end(rg);
};

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to range.

Uwagi

Wymagania elementu range to:

  • Można go iterować przy użyciu funkcji std::ranges::begin() i std::ranges::end()
  • ranges::begin() i ranges::end() uruchamiać w amortyzowanym stałym czasie i nie modyfikuj parametru range. Amortyzowany stały czas nie oznacza O(1), ale średni koszt w przypadku serii wywołań, nawet w najgorszym przypadku, to O(n), a nie O(n^2) lub gorszy.
  • [ranges::begin(), ranges::end()) określa prawidłowy zakres.

Simple_View

A Simple_View to pojęcie przeznaczone tylko do ekspozycji używane w niektórych ranges interfejsach. Nie jest ona zdefiniowana w bibliotece. Jest on używany tylko w specyfikacji, aby pomóc opisać zachowanie niektórych adapterów zakresu.

template<class V>
  concept Simple_View = // exposition only
    ranges::view<V> && ranges::range<const V> &&
    std::same_as<std::ranges::iterator_t<V>, std::ranges::iterator_t<const V>> &&
    std::same_as<std::ranges::sentinel_t<V>, std::ranges::sentinel_t<const V>>;

Parametry

V
Typ do przetestowania, aby sprawdzić, czy jest to Simple_View.

Uwagi

Widok V ma Simple_View wartość , jeśli wszystkie następujące elementy są spełnione:

  • V jest widokiem
  • const V jest zakresem
  • Oba v typy iteratora i const V sentinel mają te same typy.

sized_range

Element A sized_range zapewnia liczbę elementów w zakresie w zamortyzowanym stałym czasie.

template<class T>
  concept sized_range = range<T> &&
    requires(T& t) { ranges::size(t); };

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to sized_range.

Uwagi

Wymagania elementu sized_range są wywoływane przez element ranges::size :

  • Nie modyfikuje zakresu.
  • Zwraca liczbę elementów w zamortyzowanym stałym czasie. Amortyzowany stały czas nie oznacza O(1), ale średni koszt w przypadku serii wywołań, nawet w najgorszym przypadku, to O(n), a nie O(n^2) lub gorszy.

Niektóre przykłady elementów sized_range to std::list i std::vector.

Przykład: sized_range

W poniższym przykładzie pokazano, że element a vectorint to :sized_range

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

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

view

Element view ma stałą konstrukcję, przypisanie i zniszczenie — niezależnie od liczby elementów, które ma. Widoki nie muszą być kopiowalne ani przypisywane do kopiowania, ale jeśli są, te operacje muszą być również uruchamiane w stałym czasie.

Ze względu na stałe wymaganie dotyczące czasu można efektywnie tworzyć widoki. Na przykład, biorąc pod uwagę wektor int o nazwie input, funkcja, która określa, czy liczba jest podzielna przez trzy, a funkcja, która kwadratuje liczbę, instrukcja auto x = input | std::views::filter(divisible_by_three) | std::views::transform(square); skutecznie generuje widok zawierający kwadraty liczb w danych wejściowych, które są podzielne przez trzy. Połączenie widoków razem z | jest określany jako komponowanie widoków. Jeśli typ spełnia view koncepcję, można ją efektywnie skomponować.

template<class T>
concept view = ranges::range<T> && std::movable<T> && ranges::enable_view<T>;

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to widok.

Uwagi

Podstawowym wymaganiem, który sprawia, że widok jest komponowalny, jest to, że jest tanie przenoszenie/kopiowanie. Dzieje się tak, ponieważ widok jest przenoszony/kopiowany, gdy jest on komponowany z innym widokiem. Musi to być zakres ruchomy.

ranges::enable_view<T> jest cechą używaną do oświadczeń zgodności z semantycznymi wymaganiami view koncepcji. Typ może wyrazić zgodę na:

  • publicznie i jednoznacznie wynikające ze specjalizacji ranges::view_interface
  • publicznie i jednoznacznie wyprowadzając z pustej klasy ranges::view_base, lub
  • specjalizujące ranges::enable_view<T> się w true

Opcja 1 jest preferowana, ponieważ view_interface zapewnia również domyślną implementację, która zapisuje jakiś standardowy kod, który trzeba napisać.

W przeciwnym razie opcja 2 jest nieco prostsza niż opcja 3.

Zaletą opcji 3 jest możliwość bez zmiany definicji typu.

viewable_range

Typ viewable_range jest typem, który jest widokiem lub można go przekonwertować na jeden.

template<class T>
  concept viewable_range =
    range<T> && (borrowed_range<T> || view<remove_cvref_t<T>>);

Parametry

T
Typ do przetestowania, aby sprawdzić, czy jest to widok, czy można go przekonwertować na jeden.

Uwagi

Użyj std::ranges::views::all() polecenia , aby przekonwertować zakres na widok.

Zobacz też

<ranges>
Adaptery zakresowe
Klasy widoków