<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_range
oggetto , , forward_range
input_range
e output_range
. L'eccezione è input_range
, che non può essere scritta in , quindi non ha le funzionalità di output_range
.
Altri concetti di intervallo includono:
Concetto di intervallo | Descrizione |
---|---|
range C++20 |
Specifica un tipo che fornisce un iteratore e una sentinella. |
borrowed_range C++20 |
Specifica che la durata degli iteratori dell'intervallo non è associata alla durata dell'intervallo. |
common_range C++20 |
Specifica che il tipo dell'iteratore dell'intervallo e il tipo di sentinella dell'intervallo sono uguali. |
Simple_View C++20 |
Non un concetto ufficiale definito come parte della libreria standard, ma usato come concetto di helper su alcune interfacce. |
sized_range C++20 |
Specifica un intervallo che può fornire in modo efficiente il numero di elementi. |
view C++20 |
Specifica un tipo con costruzione, assegnazione e distruzione efficienti (tempo costante) di spostamento. |
viewable_range C++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_iterator
oggetto , ma può anche eseguire un'iterazione all'indietro.
Alcuni esempi di sono bidirectional_range
std::set
, std::vector
e 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_range
oggetto , 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::vector
e 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 oggettoinput_iterator
. La chiamatabegin()
di più volte a un risultatoinput_range
è un comportamento non definito. - È possibile dereferenziare ripetutamente un
input_iterator
oggetto, che restituisce lo stesso valore ogni volta. Uninput_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_range
oggetto , output_range
, forward_range
e bidirectional_range
. Un random_access_range
oggetto è ordinabile.
Alcuni esempi di sono random_access_range
std::vector
, std::array
e 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()
estd::ranges::end()
ranges::begin()
eranges::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 visualizzazioneconst V
è un intervallo- Sia
v
checonst 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_base
vuota , 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.