Eu tenho o seguinte código ( https://godbolt.org/z/K7sPbjjKE ):
#include <string>
#include <ranges>
#include <vector>
std::vector<std::string> fields;
void insert_many(std::size_t pos, std::span<std::string> keys)
{
auto view = std::views::iota(0uz, keys.size())
| std::views::transform([&](std::size_t i) {
return std::move(keys[i]);
});
static_assert(std::ranges::input_range<decltype(view)>);
fields.insert(fields.cbegin() + pos, view.begin(), view.end());
}
Nota: Este exemplo é simplificado demais em https://godbolt.org/z/hYTjsohTf . Eu sei, você não precisa usar std::views::iota
nada aqui. A questão é por que o código em sua forma atual não funciona.
Tanto no libc++ quanto no libstdc++, os static_assert
passes. No entanto, apenas libc++ permite a chamada para insert
. estou tentando ligar
template< class InputIt > constexpr iterator insert( const_iterator pos, InputIt first, InputIt last );
Pela aparência da mensagem de erro (com libstdc++), std::ranges::transform_view::iterator
não satisfaz InputIterator , portanto a sobrecarga não pode ser chamada:
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/15.0.0/../../../../include/c++/15.0.0/bits/stl_vector.h:1484:2: note: candidate template ignored: requirement 'is_convertible<std::output_iterator_tag, std::input_iterator_tag>::value' was not satisfied [with > _InputIterator = _Iterator<false>] 1484 | insert(const_iterator __position, _InputIterator __first, | ^
Esse é o comportamento pretendido? Achei que o novo std::views
material também satisfazia os requisitos do iterador legado.
Tanto libstdc++ quanto libc++ estão corretos. O código em questão depende simplesmente de um comportamento não especificado e, portanto, não é portátil.
std::views::iota(0uz, keys.size())
deve serIOTA-DIFF-T(size_t)
, que é um tipo de número inteiro assinado não especificado ( [range.iota.view]/(1.3) ).signed_integral
tipo ou um tipo de classe de número inteiro assinado ( [iterator.concept.winc]/4 ).Ou seja, o padrão não exige
std::views::iota(0uz, keys.size())
ser um tipo inteiro.Como o tipo de diferença de a
transform_view
é o mesmo de sua visão subjacente, também não é necessário que seja um tipo inteiro. Assim, libstdc++ está correto ao usar o específico da implementação__int128
como o tipo de diferença.Por outro lado, como o padrão não proíbe o uso do tipo inteiro, libc++ também está correto.
libc++ está correto, mas acho que isso deve ser considerado um bug do libstdc++, embora atenda tecnicamente aos requisitos de P1522 .
std::vector::insert
requer queInputIt
seja um LegacyInputIterator :falha ao compilar com o seguinte erro em libstdc++:
Olhando para
std::is_integral
, permite:Neste ponto, podemos concluir que para permitir o uso
__int128
dedifference_type
um LegacyInputIterator , libstdc++ deve__int128
ser considerado um número inteiro assinado.Por curiosidade, adicionei as duas especializações de modelo de valor a seguir (estou ciente de que isso é considerado UB):
e isso permite que o código original seja compilado.
Mudar
std::views::iota(0uz, keys.size())
parastd::views::iota(0u, static_cast<unsigned>(keys.size()))
também permite que o código seja compilado e não é UB, mas também não é uma solução alternativa muito satisfatória para esse problema.O problema aqui é que o tipo de iterador produzido por std::views::transform não é exatamente o que a função de inserção espera. Ele precisa de um iterador de entrada, mas o que está sendo gerado é mais parecido com um iterador direto.
Para contornar isso, precisamos converter explicitamente os iteradores da visualização em iteradores de entrada usando std::ranges::begin e std::ranges::end. Dessa forma, temos certeza de que a função insert aceitará o intervalo corretamente.