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_range
e output_range
. A exceção é input_range
, que não pode ser gravado, portanto, não tem os recursos de output_range
.
Outros conceitos de alcance incluem:
Conceito de intervalo | Descrição |
---|---|
range C++20 |
Especifica um tipo que fornece um iterador e um sentinela. |
borrowed_range C++20 |
Especifica que o tempo de vida dos iteradores do intervalo não está vinculado ao tempo de vida do intervalo. |
common_range C++20 |
Especifica que o tipo do iterador do intervalo e o tipo de sentinela do intervalo são os mesmos. |
Simple_View C++20 |
Não é um conceito oficial definido como parte da biblioteca padrão, mas usado como um conceito auxiliar em algumas interfaces. |
sized_range C++20 |
Especifica um intervalo que pode fornecer seu número de elementos com eficiência. |
view C++20 |
Especifica um tipo que tem construção, atribuição e destruição de movimentação eficiente (tempo constante). |
viewable_range C++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_range
arquivo .
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_range
arquivo .
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_range
arquivo .
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_range
arquivo .
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_range
arquivo .
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_range
arquivo .
Comentários
Quando um tipo atende aos requisitos de input_range
:
- A
ranges::begin()
função retorna uminput_iterator
. Chamarbegin()
mais de uma vez em um resulta em comportamentoinput_range
indefinido. - Você pode desreferenciar um
input_iterator
repetidamente, o que produz o mesmo valor todas as vezes. Aninput_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_range
arquivo .
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_range
e 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 range
arquivo .
Comentários
Os requisitos de um range
são:
- Pode ser iterado usando
std::ranges::begin()
estd::ranges::end()
ranges::begin()
eranges::end()
executar em tempo constante amortizado e não modificar orange
. 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_View
arquivo .
Comentários
Uma exibição V
é uma Simple_View
se todos os itens a seguir forem verdadeiros:
V
é uma visualizaçãoconst V
é um intervalo- Ambos
v
econst 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_range
arquivo .
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_base
vazia , ou - especializando-se
ranges::enable_view<T>
emtrue
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.