Condividi tramite


<ranges> Concetti

I concetti sono una funzionalità del linguaggio C++20 che vincola i parametri del modello in fase di compilazione. Consentono di evitare la creazione di istanze di modelli non corretti, specificare i requisiti degli argomenti del modello in un modulo leggibile e fornire altri errori del compilatore correlati al modello.

Si consideri l'esempio seguente, che definisce un concetto per impedire la creazione di istanze di un modello con un tipo che non supporta la divisione:

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

Quando si passa l'opzione /diagnostics:caret del compilatore a Visual Studio 2022 versione 17.4 preview 4 o successiva, l'errore valutato dividable<char*> su false punterà direttamente al requisito (a / b) dell'espressione che non è riuscito.

I concetti relativi agli intervalli vengono definiti nello spazio dei std::ranges nomi e dichiarati nel <ranges> file di intestazione. Vengono usate nelle dichiarazioni di adattatori di intervallo, visualizzazioni e così via.

Esistono sei categorie di intervalli. Sono correlati alle categorie di iteratori elencati nei <iterator> concetti. Per aumentare le capacità, le categorie sono:

Concetto di intervallo Descrizione
output_range
input_range
Specifica un intervallo in cui è possibile scrivere.
Specifica un intervallo da cui è possibile leggere una sola volta.
forward_range Specifica un intervallo che è possibile leggere (ed eventualmente scrivere) più volte.
bidirectional_range Specifica un intervallo che è possibile leggere e scrivere sia avanti che indietro.
random_access_range Specifica un intervallo che è possibile leggere e scrivere per indice.
contiguous_range Specifica un intervallo i cui elementi sono sequenziali in memoria, hanno le stesse dimensioni e possono essere accessibili tramite l'aritmetica del puntatore.

Nella tabella precedente i concetti sono elencati in ordine di aumento delle funzionalità. Un intervallo che soddisfa i requisiti di un concetto in genere soddisfa i requisiti dei concetti nelle righe che lo precedono. Ad esempio, un oggetto random_access_range ha la funzionalità di un bidirectional_rangeoggetto , , forward_rangeinput_rangee output_range. L'eccezione è input_range, che non può essere scritta in , quindi non ha le funzionalità di output_range.

Diagramma della gerarchia dell'iteratore degli intervalli. input_range e output_range sono gli iteratori più di base. forward_range è successivo e affina sia input_range che output_range. bidirectional_range affina forward_range. random_access_range affina bidirectional_range. Infine, contiguous_range affina random_access_range

Altri concetti di intervallo includono:

Concetto di intervallo Descrizione
rangeC++20 Specifica un tipo che fornisce un iteratore e una sentinella.
borrowed_rangeC++20 Specifica che la durata degli iteratori dell'intervallo non è associata alla durata dell'intervallo.
common_rangeC++20 Specifica che il tipo dell'iteratore dell'intervallo e il tipo di sentinella dell'intervallo sono uguali.
Simple_ViewC++20 Non un concetto ufficiale definito come parte della libreria standard, ma usato come concetto di helper su alcune interfacce.
sized_rangeC++20 Specifica un intervallo che può fornire in modo efficiente il numero di elementi.
viewC++20 Specifica un tipo con costruzione, assegnazione e distruzione efficienti (tempo costante) di spostamento.
viewable_rangeC++20 Specifica un tipo che è una visualizzazione o può essere convertito in uno.

bidirectional_range

Un bidirectional_range oggetto supporta la lettura e la scrittura dell'intervallo in avanti e indietro.

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

Parametri

T
Tipo da testare per verificare se si tratta di un oggetto bidirectional_range.

Osservazioni:

Questo tipo di intervallo supporta bidirectional_iterator o maggiore. Un bidirectional_iterator oggetto ha le funzionalità di un forward_iteratoroggetto , ma può anche eseguire un'iterazione all'indietro.

Alcuni esempi di sono bidirectional_range std::set, std::vectore std::list.

borrowed_range

Un modello di tipo borrowed_range se la validità degli iteratori ottenuta dall'oggetto può durare la durata dell'oggetto. Ovvero, gli iteratori per un intervallo possono essere usati anche quando l'intervallo non esiste più.

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

Parametri

T
Tipo da testare per verificare se si tratta di un oggetto borrowed_range.

Osservazioni:

La durata di un intervallo rvalue può terminare dopo una chiamata di funzione indipendentemente dal fatto che i modelli borrowed_range di intervallo siano o meno. Se si tratta di , borrowed_rangeè possibile continuare a usare gli iteratori con un comportamento ben definito indipendentemente dal termine della durata dell'intervallo.

I casi in cui questo non è vero sono, ad esempio, per contenitori come vector o list perché al termine della durata del contenitore, gli iteratori fanno riferimento a elementi eliminati definitivamente.

È possibile continuare a usare gli iteratori per un borrowed_rangeoggetto , ad esempio per un view oggetto come iota_view<int>{0, 42} i cui iteratori sono sovraimpostati su set di valori che non sono soggetti a essere eliminati definitivamente perché vengono generati su richiesta.

common_range

Il tipo dell'iteratore per un common_range è uguale al tipo di sentinel. Ovvero, begin() e end() restituiscono lo stesso tipo.

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

Parametri

T
Tipo da testare per verificare se si tratta di un oggetto common_range.

Osservazioni:

Ottenere il tipo da std::ranges::begin() e std::ranges::end() è importante per gli algoritmi che calcolano la distanza tra due iteratori e per gli algoritmi che accettano intervalli denotati dalle coppie iteratore.

I contenitori standard (ad esempio, vector) soddisfano i requisiti di common_range.

contiguous_range

Gli elementi di un contiguous_range oggetto vengono archiviati in modo sequenziale in memoria e possono essere accessibili usando l'aritmetica del puntatore. Ad esempio, una matrice è un oggetto 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>>>;};

Parametri

T
Tipo da testare per verificare se si tratta di un oggetto contiguous_range.

Osservazioni:

È possibile accedere a un contiguous_range oggetto tramite aritmetica del puntatore perché gli elementi sono disposti in modo sequenziale in memoria e hanno le stesse dimensioni. Questo tipo di intervallo supporta continguous_iterator, che è il più flessibile di tutti gli iteratori.

Alcuni esempi di sono contiguous_range std::array, std::vectore std::string.

Esempio: contiguous_range

L'esempio seguente illustra l'uso dell'aritmetica del puntatore per accedere a :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

Un forward_range oggetto supporta la lettura (ed eventualmente la scrittura) dell'intervallo più volte.

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

Parametri

T
Tipo da testare per verificare se si tratta di un oggetto forward_range.

Osservazioni:

Questo tipo di intervallo supporta forward_iterator o maggiore. Un forward_iterator oggetto può scorrere più volte un intervallo.

input_range

È input_range un intervallo che può essere letto da una sola volta.

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

Parametri

T
Tipo da testare per verificare se si tratta di un oggetto input_range.

Osservazioni:

Quando un tipo soddisfa i requisiti di input_range:

  • La ranges::begin() funzione restituisce un oggetto input_iterator. La chiamata begin() di più volte a un risultato input_range è un comportamento non definito.
  • È possibile dereferenziare ripetutamente un input_iterator oggetto, che restituisce lo stesso valore ogni volta. Un input_range oggetto non è multi-pass. L'incremento di un iteratore invalida tutte le copie.
  • Può essere usato con ranges::for_each.
  • Supporta input_iterator o versione successiva.

output_range

È output_range un intervallo in cui è possibile scrivere.

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

Parametri

R
Tipo dell'intervallo.

T
Tipo di dati da scrivere nell'intervallo.

Osservazioni:

Il significato di output_iterator<iterator_t<R>, T> è che il tipo fornisce un iteratore in grado di scrivere valori di tipo T in un intervallo di tipo R. In altre parole, supporta output_iterator o maggiore.

random_access_range

Un random_access_range oggetto può leggere o scrivere un intervallo in base all'indice.

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

Parametri

T
Tipo da testare per verificare se si tratta di un oggetto sized_range.

Osservazioni:

Questo tipo di intervallo supporta random_access_iterator o maggiore. Un random_access_range oggetto ha le funzionalità di un input_rangeoggetto , output_range, forward_rangee bidirectional_range. Un random_access_range oggetto è ordinabile.

Alcuni esempi di sono random_access_range std::vector, std::arraye std::deque.

range

Definisce i requisiti che un tipo deve soddisfare per essere un oggetto range. Un range oggetto fornisce un iteratore e una sentinella, in modo che sia possibile scorrere i relativi elementi.

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

Parametri

T
Tipo da testare per verificare se si tratta di un oggetto range.

Osservazioni:

I requisiti di un range sono:

  • Può essere iterato usando std::ranges::begin() e std::ranges::end()
  • ranges::begin()e ranges::end() vengono eseguiti in tempo costante ammortizzato e non modificano .range Il tempo costante ammortizzato non significa O(1), ma che il costo medio per una serie di chiamate, anche nel peggiore dei casi, è O(n) anziché O(n^2) o peggio.
  • [ranges::begin(), ranges::end()) indica un intervallo valido.

Simple_View

Un Simple_View è un concetto di sola esposizione usato su alcune ranges interfacce. Non è definito nella libreria. Viene usato solo nella specifica per descrivere il comportamento di alcuni adattatori di intervallo.

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

Parametri

V
Tipo da testare per verificare se si tratta di un oggetto Simple_View.

Osservazioni:

Una visualizzazione V è un Simple_View valore se sono soddisfatte tutte le condizioni seguenti:

  • V è una visualizzazione
  • const V è un intervallo
  • Sia v che const V hanno gli stessi tipi di iteratore e sentinella.

sized_range

Un sized_range oggetto fornisce il numero di elementi nell'intervallo in tempo costante ammortizzato.

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

Parametri

T
Tipo da testare per verificare se si tratta di un oggetto sized_range.

Osservazioni:

I requisiti di un oggetto sized_range sono che chiamano ranges::size su di esso:

  • Non modifica l'intervallo.
  • Restituisce il numero di elementi in tempo costante ammortizzato. Il tempo costante ammortizzato non significa O(1), ma che il costo medio per una serie di chiamate, anche nel peggiore dei casi, è O(n) anziché O(n^2) o peggio.

Alcuni esempi di sono sized_range std::list e std::vector.

Esempio: sized_range

L'esempio seguente mostra che un vector di int è un oggetto 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

Un oggetto view ha una costruzione, un'assegnazione e operazioni di distruzione in tempo costanti, indipendentemente dal numero di elementi di cui dispone. Le viste non devono essere copiabili o copiabili assegnabili, ma in caso affermativo, tali operazioni devono essere eseguite anche in tempo costante.

A causa del requisito di tempo costante, è possibile comporre in modo efficiente le visualizzazioni. Ad esempio, dato un vettore di int denominato input, una funzione che determina se un numero è divisibile per tre e una funzione che quadra un numero, l'istruzione auto x = input | std::views::filter(divisible_by_three) | std::views::transform(square); produce in modo efficiente una visualizzazione che contiene i quadrati dei numeri di input divisibile per tre. La connessione delle visualizzazioni con | viene definita composizione delle visualizzazioni. Se un tipo soddisfa il view concetto, può essere composto in modo efficiente.

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

Parametri

T
Tipo da testare per verificare se si tratta di una visualizzazione.

Osservazioni:

Il requisito essenziale che rende componibile una visualizzazione è che è economico spostare/copiare. Ciò è dovuto al fatto che la visualizzazione viene spostata/copiata quando è composta con un'altra visualizzazione. Deve essere un intervallo mobile.

ranges::enable_view<T> è un tratto usato per rivendicare la conformità ai requisiti semantici del view concetto. Un tipo può acconsentire esplicitamente:

  • derivazione pubblica e non ambigua da una specializzazione di ranges::view_interface
  • derivare pubblicamente e senza ambiguità dalla classe ranges::view_basevuota , o
  • ranges::enable_view<T> specializzato intrue

L'opzione 1 è preferibile perché view_interface fornisce anche l'implementazione predefinita che consente di salvare il codice boilerplate da scrivere.

In caso contrario, l'opzione 2 è leggermente più semplice dell'opzione 3.

Il vantaggio dell'opzione 3 è che è possibile senza modificare la definizione del tipo.

viewable_range

Un viewable_range è un tipo che è una visualizzazione o può essere convertito in uno.

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

Parametri

T
Tipo da testare per verificare se è una visualizzazione o può essere convertito in uno.

Osservazioni:

Utilizzare std::ranges::views::all() per convertire un intervallo in una visualizzazione.

Vedi anche

<ranges>
Adattatori di intervallo
Visualizzare le classi