Compartilhar via


<ranges>

Em um alto nível, 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. O sentinela pode ser do mesmo tipo que o iterador begin 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(). Você passa 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ê tivesse chamado std::sort(myVector.begin(), myVector.end());. Nas bibliotecas de intervalos, 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 incluem <algorithm> , , , for_eachcountequalfindfind_iffind_if_notfor_each_nmismatchany_ofcount_ifall_ofnone_ofcopy_ifcopy_ncopy

Mas talvez o benefício mais importante dos intervalos seja 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, seria necessário 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. Também permite compor duas operações.

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

O resultado, output, é em si um tipo de intervalo chamado de visão.

Exibições

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

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árias exibições, consulte Exibir classes.

A aparência dos elementos no modo de exibição depende do adaptador de intervalo usado 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 combiná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 exibições, consulte <ranges> classes de exibição.

Adaptadores de intervalo

Os adaptadores de alcance pegam um intervalo e produzem uma visualizaçã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 na exibição quando acessa esse elemento.

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

Os adaptadores de alcance podem ser encadeados (compostos), o que é o coração do poder e da flexibilidade dos intervalos. A composição de adaptadores de intervalo permite que você supere o problema de que os algoritmos STL anteriores não são facilmente combináveis.

Para obter mais informações sobre como criar exibições, 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 de iteradores correspondentes no std namespace. A diferença é que eles têm restrições impostas por conceito 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 alcance

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 a qual iterador eles dão suporte.

No 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 refinam todos os veículos.

Alguns conceitos de intervalo refletem 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 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 seguintes modelos de alias 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 a range se refere a um intervalo cujo tempo de vida terminou.
borrowed_subrange_tC++20 Determine se um iterador retornado para a subrange se refere a um subintervalo cujo tempo de vida terminou.
danglingC++20 Indica que o iterador retornado de um range/subrange sobrevive ao tempo de vida do qual ele range/subrange 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 no intervalo.
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