Sou novo em conceitos e intervalos/visões.
Estou tentando escrever a inicialização de uma classe passando uma sequência de valores definidos por um iterador ou por um intervalo/visualização.
Eu consigo verificar se os argumentos da função são iteradores ou intervalos. Mas não consigo verificar se os valores retornados pelos iteradores são de um tipo específico.
Por exemplo (C++23):
#include <print>
#include <vector>
struct Data {
int i;
std::string l;
};
struct DataContainer {
// iterator version
// QUESTION: how can I check that I handles "Data"?
template<std::input_iterator I, std::sentinel_for<I> S>
void add(I start, S end) {
for (auto data = start; data != end; ++data) {
_data.push_back(*data);
}
}
// range/views version
// QUESTION: how can I check that R handles "Data"?
template<std::ranges::input_range R>
void add(R &&r) {
add(std::begin(r), std::end(r));
}
void dump() const {
for (const auto& d: _data){
std::print("[{},'{}'], ", d.i, d.l);
}
std::println();
}
std::vector<Data> _data;
};
int main()
{
std::vector<Data> init{{1, "one"}, {2, "two"}, {3, "three"}};
{
DataContainer dc;
dc.add(init.begin(), init.end());
dc.dump();
}
{
DataContainer dc;
dc.add(init);
dc.dump();
}
return 0;
}
Como posso verificar se *start
retorna um Data
?
Para este:
O tipo de
*data
éiter_reference_t<I>
. Isso é chamado de tipo de referência do iterador, o que é um pouco enganoso porque, na verdade, não precisa ser nenhum tipo de referência. Normalmente, os algoritmos devem ser restringidos com base no tipo de referência - é com isso que você realmente interage. Então, isso se torna:Você só deve usar o tipo de valor de um iterador se realmente precisar produzir valores.
Para este:
A ideia é a mesma, só que é escrito
std::ranges::range_reference_t<R>
. Isto é definido comostd::iter_reference_t<std::ranges::iterator_t<R>>
(ou seja, o tipo de referência do intervalo é o tipo de referência do tipo iterador do intervalo).Note que isso precisa usar
std::ranges::{begin,end}
, nãostd::{begin,end}
. O último é incorreto para alguns tipos de intervalo - aqueles que definembegin
eend
como funções não-membro que são encontradas pelo ADL, mas não estão emstd
.Em ambos os casos, você pode adicionar uma restrição:
std::convertible_to
:std::iter_value_t
std::ranges::range_value_t
:Uma versão mais relaxada poderia usar
std::constructible_from
em vez destd::convertible_to
.Barry deu uma resposta muito abrangente!
Apenas como um pequeno acréscimo - se você usar algoritmos padrão (neste caso
std::ranges::copy
), o código pode ser mais conciso erequires
não é necessário escrever nada adicional - ele é "costurado" no próprio algoritmo: