<ranges>
koncepty
Koncepty jsou funkce jazyka C++20, která omezuje parametry šablony v době kompilace. Pomáhají zabránit nesprávné instanci šablony, zadat požadavky na argument šablony ve čitelné podobě a poskytnout více stručnější chyby kompilátoru související se šablonou.
Podívejte se na následující příklad, který definuje koncept, který brání vytvoření instance šablony s typem, který nepodporuje dělení:
// 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
}
Když předáte přepínač /diagnostics:caret
kompilátoru do sady Visual Studio 2022 verze 17.4 Preview 4 nebo novější, chyba, která se dividable<char*>
vyhodnotí jako nepravda, bude odkazovat přímo na požadavek (a / b)
výrazu, který selhal.
Koncepty rozsahu jsou definovány v std::ranges
oboru názvů a deklarovány v <ranges>
souboru hlaviček. Používají se v deklaraci adaptérů, zobrazení a tak dále.
Existuje šest kategorií rozsahů. Souvisí s kategoriemi iterátorů uvedených v <iterator>
konceptech. V pořadí zvýšení schopnosti jsou kategorie:
Koncept rozsahu | Popis |
---|---|
output_range input_range |
Určuje rozsah, do kterého můžete zapisovat. Určuje rozsah, ze kterého můžete číst jednou. |
forward_range |
Určuje rozsah, který můžete číst (a případně zapisovat) vícekrát. |
bidirectional_range |
Určuje rozsah, který můžete číst a zapisovat dopředu i dozadu. |
random_access_range |
Určuje rozsah, který lze číst a zapisovat podle indexu. |
contiguous_range |
Určuje oblast, jejíž prvky jsou sekvenční v paměti, mají stejnou velikost a lze k němu přistupovat pomocí aritmetické metody ukazatele. |
V předchozí tabulce jsou koncepty uvedené v pořadí, ve kterém se zvyšuje schopnost. Rozsah, který splňuje požadavky konceptu, obecně splňuje požadavky konceptů v řádcích, které mu předchází. Například random_access_range
má schopnost bidirectional_range
, forward_range
, input_range
a output_range
. Výjimkou je input_range
, do kterého nelze zapisovat, takže nemá funkce output_range
.
Mezi další koncepty rozsahu patří:
Koncept rozsahu | Popis |
---|---|
range C++20 |
Určuje typ, který poskytuje iterátor a sentinel. |
borrowed_range C++20 |
Určuje, že životnost iterátorů rozsahu není svázaná s životností rozsahu. |
common_range C++20 |
Určuje, že typ iterátoru rozsahu a typu sentinelu rozsahu jsou stejné. |
Simple_View C++20 |
Nejedná se o oficiální koncept definovaný jako součást standardní knihovny, ale používá se jako pomocný koncept v některých rozhraních. |
sized_range C++20 |
Určuje rozsah, který může efektivně poskytnout počet prvků. |
view C++20 |
Určuje typ, který má efektivní (konstantní čas) přesunutí konstrukce, přiřazení a zničení. |
viewable_range C++20 |
Určuje typ, který je buď zobrazení, nebo lze převést na jeden. |
bidirectional_range
A bidirectional_range
podporuje čtení a zápis rozsahu dopředu a dozadu.
template<class T>
concept bidirectional_range =
forward_range<T> && bidirectional_iterator<iterator_t<T>>;
Parametry
T
Typ, který chcete otestovat, abyste zjistili, jestli se jedná o bidirectional_range
.
Poznámky
Tento druh rozsahu podporuje bidirectional_iterator
nebo je větší.
A bidirectional_iterator
má schopnosti forward_iterator
, ale může také iterovat zpět.
Některé příklady bidirectional_range
jsou std::set
, std::vector
a std::list
.
borrowed_range
Model typů borrowed_range
, pokud platnost iterátorů, které z objektu získáte, může prožít životnost objektu. To znamená, že iterátory rozsahu lze použít i v případě, že oblast již neexistuje.
template<class T>
concept borrowed_range =
range<T> &&
(is_lvalue_reference_v<T> || enable_borrowed_range<remove_cvref_t<T>>);
Parametry
T
Typ, který chcete otestovat, abyste zjistili, jestli se jedná o borrowed_range
.
Poznámky
Životnost rozsahu rvalue může končit voláním funkce bez ohledu na to, jestli modely borrowed_range
rozsahu nebo ne. Pokud se jedná o borrowed_range
, můžete být schopni dál používat iterátory s dobře definovaným chováním bez ohledu na to, kdy skončí životnost rozsahu.
Případy, kdy to není pravda, jsou například pro kontejnery podobné vector
nebo list
proto, že když skončí životnost kontejneru, iterátory by odkazovaly na prvky, které byly zničeny.
Iterátory můžete dál používat pro view
iota_view<int>{0, 42}
příkladborrowed_range
, jehož iterátory jsou nad sadou hodnot, které nejsou předmětem zničení, protože se generují na vyžádání.
common_range
Typ iterátoru pro určitou common_range
hodnotu je stejný jako typ sentinelu. To znamená, begin()
že vrátí end()
stejný typ.
template<class T>
concept common_range =
ranges::range<T> && std::same_as<ranges::iterator_t<T>, ranges::sentinel_t<T>>;
Parametry
T
Typ, který chcete otestovat, abyste zjistili, jestli se jedná o common_range
.
Poznámky
Získání typu z std::ranges::begin()
a std::ranges::end()
je důležité pro algoritmy, které vypočítají vzdálenost mezi dvěma iterátory, a pro algoritmy, které přijímají rozsahy označené dvojicemi iterátoru.
Standardní kontejnery (například vector
) splňují požadavky common_range
.
contiguous_range
Prvky aritmetické prvky jsou contiguous_range
uloženy postupně v paměti a lze k němu přistupovat pomocí aritmetické metody ukazatele. Například pole je .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, který chcete otestovat, abyste zjistili, jestli se jedná o contiguous_range
.
Poznámky
K Aritmetice contiguous_range
ukazatele lze přistupovat, protože prvky jsou rozloženy postupně v paměti a mají stejnou velikost. Tento druh rozsahu podporuje continguous_iterator
, což je nejflexibilnější ze všech iterátorů.
Některé příklady contiguous_range
jsou std::array
, std::vector
a std::string
.
Příklad: contiguous_range
Následující příklad ukazuje použití aritmetické ukazatele pro přístup contiguous_range
k :
// 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
A forward_range
podporuje čtení (a případně zápis) rozsah vícekrát.
template<class T>
concept forward_range = input_range<T> && forward_iterator<iterator_t<T>>;
Parametry
T
Typ, který chcete otestovat, abyste zjistili, jestli se jedná o forward_range
.
Poznámky
Tento druh rozsahu podporuje forward_iterator
nebo je větší. A forward_iterator
může iterovat v rozsahu vícekrát.
input_range
Jedná se input_range
o oblast, ze které lze číst jednou.
template<class T>
concept input_range = range<T> && input_iterator<iterator_t<T>>;
Parametry
T
Typ, který chcete otestovat, abyste zjistili, jestli se jedná o .input_range
Poznámky
Pokud typ splňuje požadavky input_range
:
- Funkce
ranges::begin()
vrátí hodnotuinput_iterator
. Výsledkem voláníbegin()
více než jednou jeinput_range
nedefinované chování. - Můžete se odvodit opakovaným
input_iterator
způsobem, který pokaždé získá stejnou hodnotu. Neníinput_range
vícenásobný. Zvýšení iterátoru zneplatní všechny kopie. - Lze jej použít s
ranges::for_each
. input_iterator
Podporuje nebo je vyšší.
output_range
Oblast output_range
, do které můžete psát.
template<class R, class T>
concept output_range = range<R> && output_iterator<iterator_t<R>, T>;
Parametry
R
Typ rozsahu.
T
Typ dat, která se mají zapisovat do oblasti.
Poznámky
output_iterator<iterator_t<R>, T>
Významem je, že typ poskytuje iterátor, který může zapisovat hodnoty typu T
do rozsahu typu R
. Jinými slovy, podporuje output_iterator
nebo je větší.
random_access_range
Oblast random_access_range
může číst nebo zapisovat podle indexu.
template<class T>
concept random_access_range =
bidirectional_range<T> && random_access_iterator<iterator_t<T>>;
Parametry
T
Typ, který chcete otestovat, abyste zjistili, jestli se jedná o sized_range
.
Poznámky
Tento druh rozsahu podporuje random_access_iterator
nebo je větší. A random_access_range
má schopnosti input_range
, output_range
, forward_range
a bidirectional_range
. A random_access_range
je možné řadit.
Některé příklady random_access_range
jsou std::vector
, std::array
a std::deque
.
range
Definuje požadavky, které musí typ splňovat, aby byl .range
Poskytuje range
iterátor a sentinel, abyste mohli iterovat jeho prvky.
template<class T>
concept range = requires(T& rg)
{
ranges::begin(rg);
ranges::end(rg);
};
Parametry
T
Typ, který chcete otestovat, abyste zjistili, jestli se jedná o range
.
Poznámky
Požadavky na:range
- Může být iterated pomocí
std::ranges::begin()
astd::ranges::end()
ranges::begin()
aranges::end()
spusťte v amortizovaném konstantním čase a neupravujterange
. Amortizovaný konstantní čas neznamená O(1), ale že průměrné náklady na řadu volání, a to i v nejhorším případě, je O(n) místo O(n^2) nebo horší.[ranges::begin(), ranges::end())
označuje platnou oblast.
Simple_View
A Simple_View
je koncept jen pro expozici používaný v některých ranges
rozhraních. Není definován v knihovně. Používá se pouze ve specifikaci k popisu chování některých adaptérů rozsahu.
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, který chcete otestovat, abyste zjistili, jestli se jedná o Simple_View
.
Poznámky
Zobrazení V
je pravdivé Simple_View
:
V
je zobrazeníconst V
je rozsah.const V
Obav
typy iterátoru a sentinelu mají stejné typy iterátoru a sentinelu.
sized_range
A sized_range
poskytuje počet prvků v rozsahu v amortizovaném konstantním čase.
template<class T>
concept sized_range = range<T> &&
requires(T& t) { ranges::size(t); };
Parametry
T
Typ, který chcete otestovat, abyste zjistili, jestli se jedná o sized_range
.
Poznámky
Požadavky sized_range
, které na ni volají ranges::size
:
- Neupravuje rozsah.
- Vrátí počet prvků v amortizovaném konstantním čase. Amortizovaný konstantní čas neznamená O(1), ale že průměrné náklady na řadu volání, a to i v nejhorším případě, je O(n) místo O(n^2) nebo horší.
Některé příklady jsou sized_range
std::list
a std::vector
.
Příklad: sized_range
Následující příklad ukazuje, že a vector
of int
je 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
A view
má konstantní čas přesunutí konstrukce, přiřazení a zničení operací - bez ohledu na počet prvků, které má. Zobrazení nemusí být konstruktovatelná ani kopírovat, ale pokud ano, musí se tyto operace spouštět i v konstantním čase.
Z důvodu konstantního časového požadavku můžete efektivně vytvářet zobrazení. Například vzhledem k vektoru int
volaného input
, funkce, která určuje, zda je číslo dělitelné třemi a funkce, která číslu čtverce, příkaz auto x = input | std::views::filter(divisible_by_three) | std::views::transform(square);
efektivně vytvoří zobrazení, které obsahuje čtverce čísel ve vstupu, které jsou dělitelné třemi. Propojení zobrazení se |
označuje jako vytváření zobrazení. Pokud typ splňuje view
tento koncept, lze jej efektivně sestavit.
template<class T>
concept view = ranges::range<T> && std::movable<T> && ranges::enable_view<T>;
Parametry
T
Typ, který chcete otestovat, abyste zjistili, jestli se jedná o zobrazení.
Poznámky
Základním požadavkem, díky kterému je zobrazení kompozibilní, je, že je levné přesunout/kopírovat. Je to proto, že se zobrazení přesune nebo zkopíruje, když se sestaví s jiným zobrazením. Musí to být pohyblivý rozsah.
ranges::enable_view<T>
je vlastnost, která se používá k deklaraci shody s sémantických požadavků konceptu view
. Typ může vyjádřit výslovný souhlas:
- veřejně a jednoznačně odvozené od specializace
ranges::view_interface
- veřejně a jednoznačně odvozené od prázdné třídy
ranges::view_base
, nebo - specializující se
ranges::enable_view<T>
natrue
Možnost 1 je upřednostňovaná, protože view_interface
poskytuje také výchozí implementaci, která ukládá některé často používané kódy, které musíte napsat.
Pokud se to nepodaří, možnost 2 je trochu jednodušší než možnost 3.
Výhodou možnosti 3 je, že je to možné beze změny definice typu.
viewable_range
A viewable_range
je typ, který je zobrazení nebo lze převést na jeden.
template<class T>
concept viewable_range =
range<T> && (borrowed_range<T> || view<remove_cvref_t<T>>);
Parametry
T
Typ, který chcete otestovat a zjistit, jestli se jedná o zobrazení, nebo ho můžete převést na jeden.
Poznámky
Slouží std::ranges::views::all()
k převodu oblasti na zobrazení.