Compartilhar via


<ranges>

Em um nível alto, um intervalo é algo que você pode iterar. Um intervalo é representado por um iterador que marca o início do intervalo e uma sentinela que marca o fim do intervalo. A sentinela pode ser do mesmo tipo que o iterador inicial, ou pode ser diferente. Os contêineres, como vector e list, na Biblioteca Padrão do C++ são intervalos. Um intervalo abstrai iteradores de uma maneira que simplifica e amplifica sua capacidade de usar a STL (Biblioteca de Modelos Padrão).

Os algoritmos STL geralmente usam iteradores que apontam para a parte da coleção na qual eles devem operar. Por exemplo, considere como você classifica um vector usando std::sort()o . Você passa por dois iteradores que marcam o início e o fim do vector. Isso fornece flexibilidade, mas passar os iteradores para o algoritmo é um trabalho extra porque você provavelmente só quer classificar a coisa toda.

Com intervalos, você pode chamar std::ranges::sort(myVector);, que é tratado como se você chamasse std::sort(myVector.begin(), myVector.end());. Em bibliotecas de intervalo, os algoritmos usam intervalos como parâmetros (embora também possam usar iteradores, se você quiser). Eles podem operar diretamente nas coleções. Exemplos de algoritmos de intervalo disponíveis em <algorithm> , , , , , all_ofnone_ofany_of, , countcopy_ifcopy_nfindfor_eachequalfind_iffind_if_notcount_iffor_each_ne .mismatchcopy

Mas talvez o benefício mais importante dos intervalos é que você pode compor algoritmos STL que operam em intervalos em um estilo que lembra a programação funcional.

Um exemplo de intervalos

Antes dos intervalos, se você quisesse transformar os elementos de uma coleção que atendesse a um determinado critério, precisaria introduzir uma etapa intermediária para manter os resultados entre as operações. Por exemplo, se você quisesse construir um vetor de quadrados a partir dos elementos em outro vetor que são divisíveis por três, você poderia escrever algo como:

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

Com intervalos, você pode realizar a mesma coisa sem precisar do vetor intermediate:

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

Além de ser mais fácil de ler, esse código evita a alocação de memória necessária para o intermediate vetor e seu conteúdo. Ele também permite que você componha duas operações.

No código anterior, cada elemento divisível por três é combinado com uma operação para quadrar esse elemento. O símbolo de pipe (|) encadeia as operações e é lido da esquerda para a direita.

O resultado, output, é em si uma espécie de intervalo chamado de visão.

Exibições

Uma exibição é um intervalo leve. As operações de exibição, como construção padrão, construção/atribuição de movimento, construção/atribuição de cópia (se houver), destruição, início e fim, acontecem em tempo constante, independentemente do número de elementos na exibição.

As exibições são criadas por adaptadores de intervalo, que são discutidos na seção a seguir. Para obter mais informações sobre as classes que implementam vários modos de exibição, consulte Exibir classes.

A forma como os elementos no modo de exibição aparecem depende do adaptador de intervalo que você usa para criar o modo de exibição. No exemplo anterior, um adaptador de intervalo usa um intervalo e retorna uma exibição dos elementos divisíveis por três. O intervalo subjacente é inalterado.

As visualizações são componíveis, o que é poderoso. No exemplo anterior, a exibição de elementos vetoriais divisíveis por três é combinada com a exibição que eleva esses elementos ao quadrado.

Os elementos de uma exibição não são avaliados automaticamente. Ou seja, as transformações que você aplica a cada elemento em uma exibição não são avaliadas até que você solicite o elemento. Por exemplo, se você executar o código a seguir em um depurador e colocar um ponto de interrupção nas linhas auto divisible_by_three = ... e auto square = ..., você verá que atingiu o ponto de interrupção lambda divisible_by_three à medida que cada elemento em input é testado para divisibilidade por três. O ponto de interrupção lambda square será alcançado conforme os elementos divisíveis por três forem elevados ao quadrado.

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

Para obter mais informações sobre modos de exibição, consulte <ranges> exibir classes.

Adaptadores de intervalo

Os adaptadores de alcance pegam um intervalo e produzem uma exibição. Adaptadores de intervalo produzem exibições avaliadas de maneira não automática. Ou seja, você não incorre no custo de transformar cada elemento no intervalo para produzir a exibição. Você só paga o custo para processar um elemento no modo de exibição quando acessa esse elemento.

No exemplo anterior, o filter adaptador de intervalo cria um modo de exibição chamado input que contém os elementos que são divisíveis por três. O transform adaptador de alcance tem a visão de elementos divisíveis por três e cria uma visão desses elementos ao quadrado.

Os adaptadores de alcance podem ser encadeados (compostos), o que é o coração da potência e flexibilidade das gamas. Compor adaptadores de faixa permite que você supere o problema de que os algoritmos STL anteriores não são facilmente componíveis.

Para obter mais informações sobre como criar modos de exibição, consulte Adaptadores de intervalo.

Algoritmos de intervalo

Alguns algoritmos de intervalo usam um argumento de intervalo. Um exemplo é std::ranges::sort(myVector);.

Os algoritmos de intervalo são quase idênticos aos algoritmos de par iterador correspondentes no std namespace. A diferença é que eles têm restrições impostas por conceitos, e aceitam argumentos de intervalo ou mais pares de argumentos iterador-sentinela. Eles podem trabalhar diretamente em um contêiner e podem ser facilmente encadeados.

Funções <ranges>

As funções a seguir são usadas para criar iteradores e sentinelas para intervalos e para obter o tamanho de um intervalo.

Função Descrição
beginC++20 Obtenha um iterador para o primeiro elemento no intervalo.
cbeginC++20 Obtenha um iterador const para o primeiro elemento no intervalo.
cendC++20 Obtenha o sentinela no final do intervalo qualificado por const.
cdataC++20 Obtenha um ponteiro const para o primeiro elemento no intervalo contíguo.
crbeginC++20 Obtenha um iterador reverso const para o início do intervalo.
crendC++20 Obtenha o sentinela no final do que crbegin() retorna.
dataC++20 Obtenha um ponteiro para o primeiro elemento no intervalo contíguo.
emptyC++20 Determine se o intervalo está vazio.
endC++20 Obtenha o sentinela no final do intervalo.
rbeginC++20 Obtenha um iterador reverso para o início do intervalo.
rendC++20 Obtenha um iterador reverso para o sentinela no final do intervalo.
sizeC++20 Obtenha o tamanho do intervalo como um valor sem sinal.
ssizeC++20 Obtenha o tamanho do intervalo como um valor com sinal.

Para obter mais informações, consulte <ranges> funções.

Conceitos de gama

A forma como você itera sobre os elementos de um intervalo depende de seu tipo de iterador subjacente. Os intervalos usam conceitos C++ que especificam qual iterador eles suportam.

Em C++20, dizer que o conceito X refina o conceito Y significa que tudo o que satisfaz o conceito Y também satisfaz o conceito X. Por exemplo: carro, ônibus e caminhão todos refinam o veículo.

Alguns conceitos de intervalo espelham a hierarquia das categorias do iterador. A tabela a seguir lista os conceitos de intervalo, juntamente com os tipos de contêineres aos quais eles podem ser aplicados.

Conceito de intervalo Descrição Contêineres compatíveis
std::ranges::output_range Pode iterar para a frente.
std::ranges::input_range Pode iterar do início ao fim pelo menos uma vez. std::forward_list
std::unordered_map
std::unordered_multimap
std::unordered_set
std::unordered_multiset
basic_istream_view
std::ranges::forward_range Pode iterar do início ao fim mais de uma vez. std::forward_list
std::unordered_map
std::unordered_multimap
std::unordered_set
std::unordered_multiset
std::ranges::bidirectional_range Pode iterar para frente e para trás mais de uma vez. std::list
std::map
std::multimap
std::multiset
std::set
std::ranges::random_access_range Pode acessar um elemento arbitrário (em tempo constante) usando o [] operador. std::deque
std::ranges::contiguous_range Os elementos são armazenados na memória consecutivamente. std::array
std::string
std::vector

Consulte <ranges> conceitos para obter mais informações sobre esses conceitos.

Modelos de alias de <ranges>

Os modelos de alias a seguir determinam os tipos de iteradores e sentinelas para um intervalo:

Modelo de alias Descrição
borrowed_iterator_tC++20 Determine se um iterador retornado para um refere-se a um range intervalo cujo tempo de vida terminou.
borrowed_subrange_tC++20 Determine se um iterador retornado para um refere-se a um subrange subintervalo cujo tempo de vida terminou.
danglingC++20 Indica que o iterador retornado de um range/subrange sobrevive ao tempo de vida do range/subrange que ele se refere.
iterator_tC++20 Retorna o tipo de iterador do tipo de intervalo especificado.
range_difference_tC++20 Retorna o tipo de diferença do tipo de iterador do intervalo especificado.
range_reference_tC++20 Retorna o tipo de referência do tipo de iterador do intervalo especificado.
range_rvalue_reference_tC++20 Retorna o tipo de referência rvalue para o tipo de iterador do intervalo especificado. Em outras palavras, o tipo de referência rvalue dos elementos do intervalo.
range_size_tC++20 Retorna o tipo usado para relatar o tamanho do intervalo especificado.
range_value_tC++20 Retorna o tipo de valor do tipo de iterador do intervalo especificado. Ou, em outras palavras, o tipo dos elementos na faixa.
sentinel_tC++20 Retorna o tipo sentinela do intervalo especificado.

Para obter mais informações sobre esses modelos de alias, consulte <ranges> modelos de alias.

Confira também

Funções <ranges>
<ranges> Conceitos
Adaptadores de gama
Referência de arquivos de cabeçalho