Compartilhar via


Conceitos de <ranges>

Os conceitos são um recurso de linguagem C++20 que restringe os parâmetros do modelo em tempo de compilação. Eles ajudam a evitar instanciação incorreta de modelo, especificam requisitos de argumento de modelo em um formulário legível e fornecem erros de compilador relacionados a modelos 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 oferece 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 preview 4 ou posterior, o erro que o conceito dividable<char*> avaliado como false apontará diretamente para o requisito (a / b) de expressão que falhou.

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

Existem seis categorias de gamas. Eles estão relacionados às categorias de iteradores listadas nos <iterator> conceitos. Em ordem de aumento 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 que você pode ler de 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 tem a random_access_range capacidade de um bidirectional_range, , input_rangeforward_rangee output_range. A exceção é input_range, que não pode ser gravado, portanto, não tem os recursos do output_range.

Diagram of the ranges iterator hierarchy. input_range and output_range are the most basic iterators. forward_range is next and refines both input_range and output_range. bidirectional_range refines forward_range. random_access_range refines bidirectional_range. Finally, contiguous_range refines random_access_range

Outros conceitos de gama 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 do 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 de forma eficiente.
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 leitura e escrita 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 gama 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 a bidirectional_range são std::set, std::vectore std::list.

borrowed_range

Um modelo de tipo borrowed_range se a validade dos iteradores obtidos do objeto puder sobreviver ao tempo de vida do objeto. Ou seja, os iteradores para 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 valor pode terminar após uma chamada de função, sejam os modelos borrowed_range de intervalo ou não. Se for um borrowed_range, você poderá continuar a usar os iteradores com comportamento bem definido, independentemente de quando a vida útil do intervalo terminar.

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

Você pode continuar a usar os iteradores para um , por exemplo, para um borrowed_rangeview 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 um common_range é o mesmo que o tipo do sentinela. Ou seja, begin() e end() retornar 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 é importante para algoritmos que calculam a distância entre dois iteradores e std::ranges::end() 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_rangearquivo .

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 pela aritmética do ponteiro porque os elementos são dispostos sequencialmente na memória e têm o mesmo tamanho. Este tipo de gama suporta continguous_iterator, que é o mais flexível de todos os iteradores.

Alguns exemplos de a contiguous_range são std::array, std::vectore std::string.

Exemplo: contiguous_range

O exemplo a seguir mostra o uso da aritmética de 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 escrita) 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 gama suporta forward_iterator ou maior. Um forward_iterator pode iterar em um intervalo várias vezes.

input_range

An input_range é um intervalo que pode ser lido de 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 satisfaz os requisitos de input_range:

  • A ranges::begin() função retorna um input_iteratorarquivo . Chamar begin() mais de uma vez em um input_range resulta em comportamento indefinido.
  • Você pode desreferenciar um input_iterator repetidamente, o que produz o mesmo valor todas as vezes. Um input_range não é multi-passe. O incremento de um iterador invalida todas as cópias.
  • Pode ser usado com ranges::for_each.
  • 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 é que o tipo fornece um iterador que pode gravar valores de tipo em um intervalo de output_iterator<iterator_t<R>, T> tipo RT . Ou seja, 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 gama 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 a random_access_range são std::vector, std::arraye std::deque.

range

Define os requisitos que um tipo deve atender para ser um rangearquivo . A range fornece um iterador e uma 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:

  • Ele pode ser iterado usando std::ranges::begin() e std::ranges::end()
  • ranges::begin() e executar em tempo constante amortizado e ranges::end() 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 dos casos, é 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 gama.

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

Um modo de exibição V é um Simple_View se todos os itens a seguir forem verdadeiros:

  • V é uma vista
  • const V é um intervalo
  • Ambos v e têm os mesmos tipos iterador e const V 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 chamem 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 dos casos, é 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 um de int é um :vectorsized_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 constantes de construção, atribuição e destruição de movimento de tempo - independentemente do número de elementos que tenha. Os modos de exibição não precisam ser construtíveis ou atribuíveis a cópia, mas, se estiverem, essas operações também devem ser executadas em tempo constante.

Devido ao requisito de tempo constante, você pode compor exibições com eficiência. Por exemplo, dado um vetor de int chamado input, uma função que determina se um número é divisível por três, e uma função que quadra um número, 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 modos de exibição com | é chamado de compor os modos de exibição. Se um tipo satisfaz o view conceito, então 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 é um modo de exibição.

Comentários

O requisito essencial que torna uma visualização componível é que ela seja barata de mover/copiar. Isso ocorre porque o modo de exibição é movido/copiado quando é composto com outro modo de exibição. Deve ser uma faixa móvel.

ranges::enable_view<T> é um traço usado para reivindicar conformidade com os requisitos semânticos view do conceito. Um tipo pode optar por:

  • pública e inequivocamente decorrente de uma especialização de ranges::view_interface
  • derivando pública e inequivocamente 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 é um modo de exibição ou pode ser convertido em um.

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 é um modo de exibição ou pode ser convertido em um.

Comentários

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

Confira também

<ranges>
Adaptadores de gama
Ver classes