Ir para o conteúdo

Última atualização:2026-10-12

Biblioteca Ranges do C++20

ranges é um dos principais recursos do C++20, e as "views" são uma parte bastante importante. Antes do C++20, as implementações dos algoritmos da biblioteca padrão eram baseadas em iteradores, por exemplo: std::sort.

std::sort(v.begin() + 2, v.end())

Iteradores + algoritmos conseguem realizar algumas operações complexas; por exemplo: quero ordenar em ordem decrescente:

std::sort(v.rbegin(), v.rend())

Mas isso também vem acompanhado de alguns problemas:

  • É fácil misturar dois iteradores incompatíveis.
  • A capacidade de composição dos algoritmos é fraca demais, sendo necessário armazenar variáveis intermediárias

Por exemplo: agora há um sistema de informações de estudantes e queremos calcular o GPA total dos estudantes com idade no intervalo de 21-25 e GPA >= 3,5 que satisfaçam as condições anteriores. Antes do C++17, poderíamos escrever um pseudocódigo como o seguinte:

std::vector<Student> students
std::vector<Student> selected;
std::copy_if(students.begin(), students.end(), std::back_inserter(selected),
             [](const Student& student) {
               return student.age >= 21 && student.age <= 25 && student.gpa >= 3.5;
             });

double s = std::accumulate(
    selected.begin(), selected.end(), 0.0,
    [](double sum, const Student& student) { return sum + student.gpa; });

Percebe-se que, ao filtrar as informações dos estudantes que satisfazem as condições, precisamos copiá-las para selected e, então, somá-las.

O C++20 introduziu uma forma de escrita mais concisa e eficiente: usando ranges e o operador de pipe | para conectar várias operações, é possível somar diretamente sem variáveis intermediárias; por exemplo:

double s = 0.0;
for (const auto& student :
     students | std::views::filter([](const auto& s) {
       return s.age >= 21 && s.age <= 25 && s.gpa >= 3.5;
     }) | std::views::transform([](const auto& s) { return s.gpa; })) {
  s += student;
}

Vê-se que os vários algoritmos se encaixam de forma contínua, reduzindo as alocações de memória de variáveis temporárias intermediárias; a legibilidade também aumenta muito, com o pipe | conectando as diferentes operações.

A seguir, vamos explorar juntos os conteúdos relacionados a ranges no C++20.

1.Conceitos básicos

1.range

Range é um conceito abstrato que representa uma sequência. Pode ser qualquer contêiner que tenha iteradores, ou um objeto que defina as funções begin() e end(). std::vector, std::list, etc., são exemplos de ranges. Arrays também podem ser tratados como ranges.

2.view

View é um tipo de acesso somente leitura a um Range. É uma forma de avaliação preguiçosa: o cálculo só acontece quando necessário, o que significa que ela não armazena dados de fato. Por exemplo: std::views::filter e std::views::transform são exemplos típicos de views. Elas nos permitem filtrar e transformar um range sem criar de fato um novo contêiner.

3.algorithm

Algoritmos são funções que operam sobre ranges ou views; por exemplo: std::sort, std::find, etc., são exemplos de algoritmos.

4.|

O operador de pipe | pode encadear views e algoritmos, usando o resultado do lado esquerdo como entrada do lado direito. Ele torna o código mais claro e conciso. Por exemplo: students | std::views::filter(...) passa o range students para std::views::filter executar a filtragem e, em seguida, repassa o resultado para as operações seguintes.

Tomando o cálculo dos estudantes acima como exemplo: nesse caso, usamos o range students como entrada da view filter por meio de |; em seguida, o resultado serve de entrada para a view transform e, por fim, é retornado um range, sobre o qual fazemos um loop, obtendo o resultado por meio de um algoritmo de acumulação.

double s = 0.0;
for (const auto& student :
     students | std::views::filter([](const auto& s) {
       return s.age >= 21 && s.age <= 25 && s.gpa >= 3.5;
     }) | std::views::transform([](const auto& s) { return s.gpa; })) {
  s += student;
}

Vê-se que usar ranges traz os seguintes benefícios:

  • Maior legibilidade do código
  • Avaliação preguiçosa: Ranges introduzem o conceito de views, permitindo avaliação preguiçosa, ou seja, calcular o resultado apenas quando necessário.

Por exemplo: só ao fazer *v.begin() é que o cálculo acontece.

auto v = std::views::reverse(vec);
std::cout << *v.begin() << std::endl;
  • Redução de erros: o design de Ranges ajuda a reduzir erros comuns nas operações tradicionais com iteradores, como usar iteradores incompatíveis.
  • etc.

O conceito de range introduz diferentes conceitos para descrever tipos diferentes de ranges. Esses conceitos ajudam a entender e restringir melhor as características dos ranges na programação genérica. A seguir, alguns conceitos de range comumente usados:

https://en.cppreference.com/w/cpp/ranges

Conceito Descrição Exemplos de contêineres
std::ranges::input_range Pode ser iterado ao menos uma vez do início ao fim std::forward_list、std::list、std::duque、std::array、std::vector
std::ranges::forward_range Pode ser iterado várias vezes do início ao fim std::forward_list、std::list、std::duque、std::array、std::vector
std::ranges::bidirectional_range O iterador também pode mover para trás com -- std::list、std::duque、std::array、std::vector
std::ranges::random_access_range Pode-se saltar para um elemento em tempo constante [] std::duque、std::array、std::vector
std::ranges::contiguous_range Elementos sempre armazenados de forma contígua na memória std::array、std::vector

2.Uso

Usar esse recurso é bastante simples: basta incluir o arquivo de cabeçalho e usar as interfaces.

Por exemplo: filtrar os números pares de um conjunto de números.

#include <ranges>
auto evenNumbers = numbers | std::views::filter([](int x) { return x % 2 == 0; });

Compilação: basta especificar o -std.

g++ -std=c++20 main.cc -o main

Para o suporte dos compiladores, consulte a lista a seguir:

https://en.cppreference.com/w/cpp/compiler_support/20

gcc >=10, clang >=13 (parcial), >=15 com suporte completo.

Tópicos relacionados

Comentários