Tenho uma operação cara que quero chamar condicionalmente dependendo do tipo de template. A função ingênua se parece basicamente com isso.
template <typename T>
void FooNaive( const std::vector<ExpensiveStruct<T>>& vec )
{
std::vector<ExpensiveStruct<float>> vecAsFloat;
if constexpr ( std::is_same_v<T, float> )
vecAsFloat = vec; // expensive copy I want to avoid
else
{
vecAsFloat.reserve( vec.size() );
for ( const auto& s : vec )
vecAsFloat.push_back( ConvertExpensiveStruct( s ) ); // <-- does not compile for floats
}
// ...use vecAsFloat...
}
Para evitar a cópia cara do primeiro branch, tentei fazer vecAsFloat
uma referência const. No entanto, devido a ConvertExpensiveStruct
não compilar para float
tipos (segundo branch), ainda preciso de um if constexpr
in ali.
Esta é minha tentativa atual:
template <typename T>
void FooAttempt( const std::vector<ExpensiveStruct<T>>& vec )
{
std::vector<ExpensiveStruct<float>> dummyVecAsFloat;
if constexpr ( !std::is_same_v<T, float> )
{
dummyVecAsFloat.reserve( vec.size() );
for ( const auto& s : vec )
dummyVecAsFloat.push_back( ConvertExpensiveStruct( s ) );
}
const std::vector<ExpensiveStruct<float>>& vecAsFloat = std::is_same_v<T, float> ? vec : dummyVecAsFloat;
// use vecAsFloat...
}
Também pensei em colocar o loop de conversão em um lambda para ser chamado no local dentro do operador ternário, mas isso significaria que ele teria que retornar uma referência const para sua variável local (estende o tempo de vida, mas ainda é feio) ou, na pior das hipóteses, retornar uma cópia por valor (lambdas têm NRVO?).
Mas parece muito complicado. Existe uma maneira de escrever isso de forma mais simples, ou pelo menos com intenção mais clara?
Você pode extrair tudo o que fizer
// use vecAsFloat...
em uma função separada e usá-laif constexpr
para criar um temporáriostd::vector<ExpensiveStruct<float>>
somente se necessário:Como @Jarod42 comentou abaixo , você pode até evitar a extração para outra função:
faça o processamento principal na
if
parte e use uma chamada recursiva para aelse
parte depois de criar o necessáriostd::vector<ExpensiveStruct<float>>
:Mais simples do que você pensa:
Mas se
vecAsFloat
não precisa ser armazenado como um vetor e só é usado (irreportado) uma vez, é ainda mais eficiente descartar a conversão de vetor e manter apenas uma visualização:Observe o uso de
std::span
no segundo snippet. É mais genérico caso você decida usar astd::array
em vez destd::vector
at call sight. Ele também reflete sua intenção de não inserir ou remover nenhum elemento.