Partilhar via


Conceitos de <ranges>

Os conceitos são um recurso da linguagem C++20 que restringe os parâmetros de modelo em tempo de compilação. Eles ajudam a evitar a instanciação incorreta do modelo, especificam os requisitos de argumento do modelo em um formato legível e fornecem erros de compilador relacionados ao modelo mais sucintos.

Considere o exemplo a seguir, que define um conceito para impedir a instanciação de um modelo com um tipo que não dá suporte à divisão:

// 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 você passa a opção /diagnostics:caret do compilador para o Visual Studio 2022 versão 17.4 versão prévia 4 ou posterior, o erro que o conceito dividable<char*> avaliou como falso apontará diretamente para o requisito (a / b) de expressão que falhou.

Os conceitos de intervalo são definidos no std::ranges namespace e declarados <ranges> no arquivo de cabeçalho. Eles são usados nas declarações de adaptadores de intervalo, exibições e assim por diante.

Existem seis categorias de intervalos. Eles estão relacionados às categorias de iteradores listados em <iterator> conceitos. Em ordem crescente de capacidade, as categorias são:

Conceito de intervalo Descrição
output_range
input_range
Especifica um intervalo no qual você pode gravar.
Especifica um intervalo do qual você pode ler uma vez.
forward_range Especifica um intervalo que você pode ler (e possivelmente gravar) várias vezes.
bidirectional_range Especifica um intervalo que você pode ler e gravar para frente e para trás.
random_access_range Especifica um intervalo que você pode ler e gravar por índice.
contiguous_range Especifica um intervalo cujos elementos são sequenciais na memória, têm o mesmo tamanho e podem ser acessados usando aritmética de ponteiro.

Na tabela anterior, os conceitos são listados em ordem crescente de capacidade. Um intervalo que atende aos requisitos de um conceito geralmente atende aos requisitos dos conceitos nas linhas que o precedem. Por exemplo, a random_access_range tem a capacidade de um bidirectional_range, forward_range, input_rangee output_range. A exceção é input_range, que não pode ser gravado, portanto, não tem os recursos de output_range.

Diagrama da hierarquia do iterador de intervalos. input_range e output_range são os iteradores mais básicos. forward_range é o próximo e refina input_range e output_range. bidirectional_range refina forward_range. random_access_range refina bidirectional_range. Finalmente, contiguous_range refina random_access_range

Outros conceitos de alcance incluem:

Conceito de intervalo Descrição
rangeC++20 Especifica um tipo que fornece um iterador e um sentinela.
borrowed_rangeC++20 Especifica que o tempo de vida dos iteradores do intervalo não está vinculado ao tempo de vida do intervalo.
common_rangeC++20 Especifica que o tipo do iterador do intervalo e o tipo de sentinela do intervalo são os mesmos.
Simple_ViewC++20 Não é um conceito oficial definido como parte da biblioteca padrão, mas usado como um conceito auxiliar em algumas interfaces.
sized_rangeC++20 Especifica um intervalo que pode fornecer seu número de elementos com eficiência.
viewC++20 Especifica um tipo que tem construção, atribuição e destruição de movimentação eficiente (tempo constante).
viewable_rangeC++20 Especifica um tipo que é um modo de exibição ou pode ser convertido em um.

bidirectional_range

A bidirectional_range suporta a leitura e a gravação do intervalo para frente e para trás.

template<class T>
concept bidirectional_range =
    forward_range<T> && bidirectional_iterator<iterator_t<T>>;

Parâmetros

T
O tipo a ser testado para ver se é um bidirectional_rangearquivo .

Comentários

Este tipo de intervalo suporta bidirectional_iterator ou maior. A bidirectional_iterator tem os recursos de um forward_iterator, mas também pode iterar para trás.

Alguns exemplos de um bidirectional_range são std::set, std::vector, e std::list.

borrowed_range

Um tipo modela borrowed_range se a validade dos iteradores que você obtém do objeto pode sobreviver ao tempo de vida do objeto. Ou seja, os iteradores de um intervalo podem ser usados mesmo quando o intervalo não existe mais.

template<class T>
concept borrowed_range =
    range<T> &&
    (is_lvalue_reference_v<T> || enable_borrowed_range<remove_cvref_t<T>>);

Parâmetros

T
O tipo a ser testado para ver se é um borrowed_rangearquivo .

Comentários

O tempo de vida de um intervalo de rvalue pode terminar após uma chamada de função, independentemente de o intervalo ser modelo borrowed_range ou não. Se for um borrowed_range, você poderá continuar a usar os iteradores com um comportamento bem definido, independentemente de quando o tempo de vida do intervalo terminar.

Os casos em que isso não é verdade são, por exemplo, para contêineres como vector ou list porque, quando o tempo de vida do contêiner termina, os iteradores se referem a elementos que foram destruídos.

Você pode continuar a usar os iteradores para um borrowed_range, por exemplo, para um view like iota_view<int>{0, 42} cujos iteradores estão acima do conjunto de valores que não estão sujeitos a serem destruídos porque são gerados sob demanda.

common_range

O tipo do iterador para a common_range é o mesmo que o tipo da sentinela. Ou seja, begin() e end() retorne o mesmo tipo.

template<class T>
concept common_range =
   ranges::range<T> && std::same_as<ranges::iterator_t<T>, ranges::sentinel_t<T>>;

Parâmetros

T
O tipo a ser testado para ver se é um common_rangearquivo .

Comentários

Obter o tipo de e std::ranges::end() é importante para algoritmos que calculam a distância entre dois iteradores e para algoritmos que aceitam intervalos indicados por pares de std::ranges::begin() iteradores.

Os contêineres padrão (por exemplo, vector) atendem aos requisitos do common_range.

contiguous_range

Os elementos de a contiguous_range são armazenados sequencialmente na memória e podem ser acessados usando aritmética de ponteiro. Por exemplo, uma matriz é um 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>>>;};

Parâmetros

T
O tipo a ser testado para ver se é um contiguous_rangearquivo .

Comentários

A contiguous_range pode ser acessado por aritmética de ponteiro porque os elementos são dispostos sequencialmente na memória e têm o mesmo tamanho. Esse tipo de intervalo suporta continguous_iterator, que é o mais flexível de todos os iteradores.

Alguns exemplos de um contiguous_range são std::array, std::vector, e std::string.

Exemplo: contiguous_range

O exemplo a seguir mostra o uso da aritmética do ponteiro para acessar um 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

A forward_range suporta a leitura (e possivelmente a gravação) do intervalo várias vezes.

template<class T>
concept forward_range = input_range<T> && forward_iterator<iterator_t<T>>;

Parâmetros

T
O tipo a ser testado para ver se é um forward_rangearquivo .

Comentários

Este tipo de intervalo suporta forward_iterator ou maior. A forward_iterator pode iterar em um intervalo várias vezes.

input_range

An input_range é um intervalo que pode ser lido uma vez.

template<class T>
concept input_range = range<T> && input_iterator<iterator_t<T>>;

Parâmetros

T
O tipo a ser testado para ver se é um input_rangearquivo .

Comentários

Quando um tipo atende aos requisitos de input_range:

  • A ranges::begin() função retorna um input_iterator. Chamar begin() mais de uma vez em um resulta em comportamento input_range indefinido.
  • Você pode desreferenciar um input_iterator repetidamente, o que produz o mesmo valor todas as vezes. An input_range não é multi-pass. Incrementar um iterador invalida todas as cópias.
  • Pode ser usado com ranges::for_each.
  • Ele suporta input_iterator ou maior.

output_range

An output_range é um intervalo no qual você pode gravar.

template<class R, class T>
concept output_range = range<R> && output_iterator<iterator_t<R>, T>;

Parâmetros

R
O tipo do intervalo.

T
O tipo de dados a serem gravados no intervalo.

Comentários

O significado de output_iterator<iterator_t<R>, T> é que o tipo fornece um iterador que pode gravar valores de tipo T em um intervalo de tipo R. Em outras palavras, ele suporta output_iterator ou maior.

random_access_range

A random_access_range pode ler ou gravar um intervalo por índice.

template<class T>
concept random_access_range =
bidirectional_range<T> && random_access_iterator<iterator_t<T>>;

Parâmetros

T
O tipo a ser testado para ver se é um sized_rangearquivo .

Comentários

Este tipo de intervalo suporta random_access_iterator ou maior. A random_access_range tem as capacidades de um input_range, output_range, forward_rangee bidirectional_range. A random_access_range é classificável.

Alguns exemplos de um random_access_range são std::vector, std::array, e std::deque.

range

Define os requisitos que um tipo deve atender para ser um range. A range fornece um iterador e um sentinela, para que você possa iterar sobre seus elementos.

template<class T>
concept range = requires(T& rg)
{
  ranges::begin(rg);
  ranges::end(rg);
};

Parâmetros

T
O tipo a ser testado para ver se é um rangearquivo .

Comentários

Os requisitos de um range são:

  • Pode ser iterado usando std::ranges::begin() e std::ranges::end()
  • ranges::begin() e ranges::end() executar em tempo constante amortizado e não modificar o range. O tempo constante amortizado não significa O(1), mas que o custo médio em uma série de chamadas, mesmo no pior caso, é O(n) em vez de O(n^2) ou pior.
  • [ranges::begin(), ranges::end()) denota um intervalo válido.

Simple_View

A Simple_View é um conceito somente de exposição usado em algumas ranges interfaces. Ele não está definido na biblioteca. Ele é usado apenas na especificação para ajudar a descrever o comportamento de alguns adaptadores de intervalo.

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

Parâmetros

V
O tipo a ser testado para ver se é um Simple_Viewarquivo .

Comentários

Uma exibição V é uma Simple_View se todos os itens a seguir forem verdadeiros:

  • V é uma visualização
  • const V é um intervalo
  • Ambos v e const V têm os mesmos tipos de iterador e sentinela.

sized_range

A sized_range fornece o número de elementos no intervalo em tempo constante amortizado.

template<class T>
  concept sized_range = range<T> &&
    requires(T& t) { ranges::size(t); };

Parâmetros

T
O tipo a ser testado para ver se é um sized_rangearquivo .

Comentários

Os requisitos de um sized_range são que o chamam ranges::size :

  • Não modifica o intervalo.
  • Retorna o número de elementos em tempo constante amortizado. O tempo constante amortizado não significa O(1), mas que o custo médio em uma série de chamadas, mesmo no pior caso, é O(n) em vez de O(n^2) ou pior.

Alguns exemplos de um sized_range são std::list e std::vector.

Exemplo: sized_range

O exemplo a seguir mostra que a vector de int é um 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

A view tem operações de construção, atribuição e destruição de movimento de tempo constante, independentemente do número de elementos que possui. As exibições não precisam ser construíveis ou atribuíveis à cópia, mas, se forem, essas operações também deverão ser executadas em tempo constante.

Devido ao requisito de tempo constante, você pode compor exibições com eficiência. Por exemplo, dado um vetor chamado int input, uma função que determina se um número é divisível por três e uma função que eleva um número ao quadrado, a instrução auto x = input | std::views::filter(divisible_by_three) | std::views::transform(square); produz eficientemente uma exibição que contém os quadrados dos números na entrada que são divisíveis por três. Conectar exibições com | é chamado de composição das exibições. Se um tipo satisfaz o view conceito, ele pode ser composto de forma eficiente.

template<class T>
concept view = ranges::range<T> && std::movable<T> && ranges::enable_view<T>;

Parâmetros

T
O tipo a ser testado para ver se é uma exibição.

Comentários

O requisito essencial que torna uma visualização combinável é que ela seja barata de mover/copiar. Isso ocorre porque a exibição é movida/copiada quando é composta com outra exibição. Deve ser uma faixa móvel.

ranges::enable_view<T> é uma característica usada para reivindicar conformidade com os requisitos semânticos view do conceito. Um tipo pode aceitar:

  • pública e inequivocamente decorrente de uma especialização de ranges::view_interface
  • publicamente e inequivocamente derivada da classe ranges::view_basevazia , ou
  • especializando-se ranges::enable_view<T> em true

A opção 1 é preferida porque view_interface também fornece implementação padrão que salva algum código clichê que você precisa escrever.

Caso contrário, a opção 2 é um pouco mais simples do que a opção 3.

A vantagem da opção 3 é que ela é possível sem alterar a definição do tipo.

viewable_range

A viewable_range é um tipo que é uma exibição ou pode ser convertido em uma.

template<class T>
  concept viewable_range =
    range<T> && (borrowed_range<T> || view<remove_cvref_t<T>>);

Parâmetros

T
O tipo a ser testado para ver se ele é um modo de exibição ou pode ser convertido em um.

Comentários

Use std::ranges::views::all() para converter um intervalo em uma exibição.

Confira também

<ranges>
Adaptadores de gama
Ver aulas