Condividi tramite


<ranges>

A livello generale, un intervallo è qualcosa che è possibile scorrere. Un intervallo è rappresentato da un iteratore che contrassegna l'inizio dell'intervallo e un sentinella che contrassegna la fine dell'intervallo. Sentinel può essere lo stesso tipo dell'iteratore di inizio oppure può essere diverso. I contenitori, ad esempio vector e list, nella libreria standard C++ sono intervalli. Un intervallo astrae gli iteratori in modo da semplificare e amplificare la possibilità di usare la libreria di modelli standard (STL).

Gli algoritmi STL in genere accettano iteratori che puntano alla parte della raccolta su cui devono operare. Si consideri, ad esempio, come ordinare un vector oggetto usando std::sort(). Si passano due iteratori che contrassegnano l'inizio e la fine dell'oggetto vector. Ciò offre flessibilità, ma il passaggio degli iteratori all'algoritmo è un lavoro aggiuntivo perché probabilmente si vuole solo ordinare l'intera cosa.

Con gli intervalli, è possibile chiamare std::ranges::sort(myVector);, che viene considerato come se si chiamasse std::sort(myVector.begin(), myVector.end());. Nelle librerie di intervalli, gli algoritmi accettano intervalli come parametri (anche se possono accettare iteratori, se lo si desidera). Possono operare direttamente sulle raccolte. Esempi di algoritmi di intervallo disponibili in <algorithm> includono copy, copy_ncopy_if, all_of, , any_ofnone_of, find, countfind_iffind_if_notfor_eachfor_each_ncount_if, equale .mismatch

Ma forse il vantaggio più importante degli intervalli è che è possibile comporre algoritmi STL che operano su intervalli in uno stile che ricorda la programmazione funzionale.

Esempio di intervalli

Prima degli intervalli, se si desidera trasformare gli elementi di una raccolta che soddisfa un determinato criterio, è necessario introdurre un passaggio intermedio per contenere i risultati tra le operazioni. Ad esempio, se si vuole creare un vettore di quadrati dagli elementi in un altro vettore divisibile per tre, è possibile scrivere qualcosa di simile al seguente:

std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::vector<int> intermediate, output;

std::copy_if(input.begin(), input.end(), std::back_inserter(intermediate), [](const int i) { return i%3 == 0; });
std::transform(intermediate.begin(), intermediate.end(), std::back_inserter(output), [](const int i) {return i*i; });

Con gli intervalli, è possibile eseguire la stessa operazione senza bisogno del intermediate vettore:

// requires /std:c++20
std::vector<int> input = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

auto output = input
    | std::views::filter([](const int n) {return n % 3 == 0; })
    | std::views::transform([](const int n) {return n * n; });

Oltre a essere più facile da leggere, questo codice evita l'allocazione di memoria necessaria per il vettore e il intermediate relativo contenuto. Consente inoltre di comporre due operazioni.

Nel codice precedente, ogni elemento divisibile per tre viene combinato con un'operazione al quadrato di tale elemento. Il simbolo pipe (|) concatena le operazioni e viene letto da sinistra a destra.

Il risultato, output, è stesso un tipo di intervallo denominato visualizzazione.

Visualizzazioni

Una visualizzazione è un intervallo leggero. Visualizzare operazioni, ad esempio costruzione predefinita, costruzione/assegnazione di spostamento, costruzione/assegnazione di copia (se presente), distruzione, inizio e fine-tutto avviene in tempo costante indipendentemente dal numero di elementi nella visualizzazione.

Le visualizzazioni vengono create da adattatori di intervallo, descritti nella sezione seguente. Per altre informazioni sulle classi che implementano varie visualizzazioni, vedere Visualizzare le classi.

La modalità di visualizzazione degli elementi nella visualizzazione dipende dall'adattatore di intervallo usato per creare la visualizzazione. Nell'esempio precedente, un adattatore di intervallo accetta un intervallo e restituisce una visualizzazione degli elementi divisibile per tre. L'intervallo sottostante rimane invariato.

Le visualizzazioni sono componibili, che è potente. Nell'esempio precedente, la visualizzazione degli elementi vettoriali divisibile per tre viene combinata con la visualizzazione che quadratia tali elementi.

Gli elementi di una vista vengono valutati in modo differito. Ovvero, le trasformazioni applicate a ogni elemento in una visualizzazione non vengono valutate fino a quando non si richiede l'elemento. Ad esempio, se si esegue il codice seguente in un debugger e si inserisce un punto di interruzione nelle righe auto divisible_by_three = ... e auto square = ..., si noterà che si raggiunge il divisible_by_three punto di interruzione lambda perché ogni elemento in input viene testato per la divisibilebilità per tre. Il square punto di interruzione lambda verrà raggiunto come gli elementi divisibile per tre sono quadrati.

// requires /std:c++20
#include <ranges>
#include <vector>
#include <iostream>

int main()
{
    std::vector<int> input =  { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    auto divisible_by_three = [](const int n) {return n % 3 == 0; };
    auto square = [](const int n) {return n * n; };

    auto x = input | std::views::filter(divisible_by_three)
                   | std::views::transform(square);

    for (int i : x)
    {
        std::cout << i << '\n';
    }
    return 0;
}

Per altre informazioni sulle visualizzazioni, vedere <ranges> Visualizzare le classi.

Adattatori di intervallo

Gli adattatori di intervallo accettano un intervallo e producono una visualizzazione. Gli adattatori di intervallo producono visualizzazioni valutate in modo differito. Ciò significa che non si comporta il costo della trasformazione di ogni elemento nell'intervallo per produrre la visualizzazione. Si paga solo il costo per elaborare un elemento nella visualizzazione quando si accede a tale elemento.

Nell'esempio precedente, l'adattatore dell'intervallo filter crea una vista denominata input che contiene gli elementi divisibile per tre. L'adattatore transform di intervallo visualizza gli elementi divisibile per tre e crea una visualizzazione di tali elementi quadrati.

Gli adattatori di intervallo possono essere concatenati (composti), che è il cuore della potenza e della flessibilità degli intervalli. La composizione degli adattatori di intervallo consente di risolvere il problema che gli algoritmi STL precedenti non sono facilmente componibili.

Per altre informazioni sulla creazione di visualizzazioni, vedere Adattatori di intervallo.

Algoritmi di intervallo

Alcuni algoritmi di intervallo accettano un argomento di intervallo. Un esempio è std::ranges::sort(myVector);.

Gli algoritmi di intervallo sono quasi identici agli algoritmi di coppia di iteratori corrispondenti nello spazio dei std nomi . La differenza è che hanno vincoli applicati dal concetto e accettano argomenti di intervallo o più coppie di argomenti iteratore-sentinel. Possono lavorare direttamente in un contenitore e possono essere facilmente concatenati.

<ranges> funzioni

Le funzioni seguenti vengono usate per creare iteratori e sentinelle per intervalli e per ottenere le dimensioni di un intervallo.

Funzione Descrizione
beginC++20 Ottenere un iteratore al primo elemento dell'intervallo.
cbeginC++20 Ottenere un const iteratore al primo elemento dell'intervallo.
cendC++20 Ottenere la sentinella alla fine dell'intervallo constqualificato.
cdataC++20 Ottenere un const puntatore al primo elemento nell'intervallo contiguo.
crbeginC++20 Ottiene un iteratore inverso const all'inizio dell'intervallo.
crendC++20 Ottenere l'sentinel alla fine di ciò che crbegin() restituisce.
dataC++20 Ottenere un puntatore al primo elemento nell'intervallo contiguo.
emptyC++20 Determinare se l'intervallo è vuoto.
endC++20 Ottenere la sentinella alla fine dell'intervallo.
rbeginC++20 Ottiene un iteratore inverso all'inizio dell'intervallo.
rendC++20 Ottenere un iteratore inverso alla sentinella alla fine dell'intervallo.
sizeC++20 Ottenere le dimensioni dell'intervallo come valore senza segno.
ssizeC++20 Ottenere le dimensioni dell'intervallo come valore con segno.

Per altre informazioni, vedere <ranges> Funzioni.

Concetti relativi all'intervallo

La modalità di iterazione degli elementi di un intervallo dipende dal tipo di iteratore sottostante. Gli intervalli usano concetti C++ che specificano l'iteratore supportato.

In C++20, per dire che il concetto X affina il concetto Y significa che tutto ciò che soddisfa il concetto Y soddisfa anche il concetto X. Ad esempio: auto, autobus e camion tutti affinano il veicolo.

Alcuni concetti di intervallo rispecchiano la gerarchia delle categorie di iteratori. Nella tabella seguente sono elencati i concetti relativi all'intervallo, insieme ai tipi di contenitori a cui possono essere applicati.

Concetto di intervallo Descrizione Contenitori supportati
std::ranges::output_range Può scorrere in avanti.
std::ranges::input_range Può eseguire l'iterazione dall'inizio alla fine almeno una volta. std::forward_list
std::unordered_map
std::unordered_multimap
std::unordered_set
std::unordered_multiset
basic_istream_view
std::ranges::forward_range Può eseguire l'iterazione dall'inizio alla fine più di una volta. std::forward_list
std::unordered_map
std::unordered_multimap
std::unordered_set
std::unordered_multiset
std::ranges::bidirectional_range Può eseguire l'iterazione in avanti e indietro più volte. std::list
std::map
std::multimap
std::multiset
std::set
std::ranges::random_access_range È possibile accedere a un elemento arbitrario (in tempo costante) usando l'operatore [] . std::deque
std::ranges::contiguous_range Gli elementi vengono archiviati in memoria consecutivamente. std::array
std::string
std::vector

Per altre informazioni su questi concetti, vedere <ranges> concetti .

<ranges> modelli alias

I modelli di alias seguenti determinano i tipi di iteratori e sentinelle per un intervallo:

Modello di alias Descrizione
borrowed_iterator_tC++20 Determinare se un iteratore restituito per un range fa riferimento a un intervallo la cui durata è terminata.
borrowed_subrange_tC++20 Determinare se un iteratore restituito per un subrange fa riferimento a un intervallo secondario la cui durata è terminata.
danglingC++20 Indica che l'iteratore restituito di un range/subrange oggetto dura la durata dell'oggetto range/subrange a cui fa riferimento.
iterator_tC++20 Restituisce il tipo di iteratore del tipo di intervallo specificato.
range_difference_tC++20 Restituisce il tipo di differenza del tipo iteratore dell'intervallo specificato.
range_reference_tC++20 Restituisce il tipo riferimento del tipo iteratore dell'intervallo specificato.
range_rvalue_reference_tC++20 Restituisce il tipo di riferimento rvalue per il tipo di iteratore dell'intervallo specificato. In altre parole, il tipo di riferimento rvalue degli elementi dell'intervallo.
range_size_tC++20 Restituisce il tipo utilizzato per segnalare le dimensioni dell'intervallo specificato.
range_value_tC++20 Restituisce il tipo di valore del tipo iteratore dell'intervallo specificato. In altre parole, il tipo degli elementi nell'intervallo.
sentinel_tC++20 Restituisce il tipo sentinel dell'intervallo specificato.

Per altre informazioni su questi modelli di alias, vedere <ranges> Modelli di alias.

Vedi anche

Funzioni <ranges>
<ranges> Concetti
Adattatori di intervallo
Informazioni di riferimento per i file di intestazione