Sdílet prostřednictvím


<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_rangea output_range. Výjimkou je input_range, do kterého nelze zapisovat, takže nemá funkce output_range.

Diagram hierarchie iterátoru rozsahů input_range a output_range jsou nejzásadnější iterátory. forward_range je další a upřesňuje input_range i output_range. bidirectional_range upřesňuje forward_range. random_access_range upřesňuje bidirectional_range. Nakonec contiguous_range upřesnit random_access_range

Mezi další koncepty rozsahu patří:

Koncept rozsahu Popis
rangeC++20 Určuje typ, který poskytuje iterátor a sentinel.
borrowed_rangeC++20 Určuje, že životnost iterátorů rozsahu není svázaná s životností rozsahu.
common_rangeC++20 Určuje, že typ iterátoru rozsahu a typu sentinelu rozsahu jsou stejné.
Simple_ViewC++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_rangeC++20 Určuje rozsah, který může efektivně poskytnout počet prvků.
viewC++20 Určuje typ, který má efektivní (konstantní čas) přesunutí konstrukce, přiřazení a zničení.
viewable_rangeC++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::vectora 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::vectora std::string.

Příklad: contiguous_range

Následující příklad ukazuje použití aritmetické ukazatele pro přístup contiguous_rangek :

// 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í hodnotu input_iterator. Výsledkem volání begin() více než jednou je input_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_rangea bidirectional_range. A random_access_range je možné řadit.

Některé příklady random_access_range jsou std::vector, std::arraya 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() a std::ranges::end()
  • ranges::begin() a ranges::end() spusťte v amortizovaném konstantním čase a neupravujte range. 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 Oba v 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> na true

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í.

Viz také

<ranges>
Adaptéry rozsahu
Zobrazit třídy