<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_range
i output_range
. Wyjątek to input_range
, którego nie można zapisać, więc nie ma możliwości programu output_range
.
Inne pojęcia dotyczące zakresu obejmują:
Koncepcja zakresu | opis |
---|---|
range C++20 |
Określa typ, który udostępnia iterator i sentinel. |
borrowed_range C++20 |
Określa, że okres istnienia iteratorów zakresu nie jest powiązany z okresem istnienia zakresu. |
common_range C++20 |
Określa, że typ iteratora zakresu i typ sentinel zakresu są takie same. |
Simple_View C++20 |
Nie jest to oficjalna koncepcja zdefiniowana jako część standardowej biblioteki, ale używana jako koncepcja pomocnika w niektórych interfejsach. |
sized_range C++20 |
Określa zakres, który może efektywnie podać jego liczbę elementów. |
view C++20 |
Określa typ, który ma wydajny (stały czas) przenoszenie konstrukcji, przypisania i zniszczenia. |
viewable_range C++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_iterator
elementu , ale może również iterować do tyłu.
Niektóre przykłady elementów to bidirectional_range
std::set
, std::vector
i 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_range
to 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_range
obiektu , na przykład dla obiektu view
iota_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_range
std::array
, std::vector
i 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łaniebegin()
więcej niż raz w wynikuinput_range
niezdefiniowanego zachowania. - Można wielokrotnie wyłudzać
input_iterator
, co daje tę samą wartość za każdym razem. Elementinput_range
nie jest wieloprzepustowy. Inkrementacja iteratora unieważnia wszystkie kopie. - Można go używać z
ranges::for_each
programem . - 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_range
forward_range
i bidirectional_range
. Element jest random_access_range
sortowalny.
Niektóre przykłady elementów to random_access_range
std::vector
, std::array
i 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()
istd::ranges::end()
ranges::begin()
iranges::end()
uruchamiać w amortyzowanym stałym czasie i nie modyfikuj parametrurange
. 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 widokiemconst V
jest zakresem- Oba
v
typy iteratora iconst 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 vector
int
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. Łączenie widoków razem z elementem |
jest nazywane tworzeniem 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ę wtrue
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.