I have an expensive operation that I want to call conditionally depending on the template type. The naive function looks basically like this.
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...
}
In order to avoid the expensive copy of the first branch, I tried making vecAsFloat
a const reference. However, due to ConvertExpensiveStruct
not compiling for float
types (second branch), I still need an if constexpr
in there.
This is my current attempt at it:
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...
}
I also thought about putting the conversion loop in a lambda to be called in-place inside the ternary operator, but that would mean it would have to return a const reference to its local variable (extends lifetime, but still ugly), or at worse return a copy by value (do lambdas have NRVO?).
But it looks overly complicated. Is there a way to write this simpler, or at least with clearer intent?
You can extract whatever you do at // use vecAsFloat...
into a separate function, and then use if constexpr
to create a temporary std::vector<ExpensiveStruct<float>>
only if needed:
void FooAsFloat(const std::vector<ExpensiveStruct<float>>& vecAsFloat) {
// use vecAsFloat...
}
template <typename T>
void Foo(const std::vector<ExpensiveStruct<T>>& vec)
{
if constexpr (std::is_same_v<T, float>) {
FooAsFloat(vec); // no copy needed
}
else {
std::vector<ExpensiveStruct<float>> vecAsFloat;
vecAsFloat.reserve(vec.size());
for (const auto& s : vec) {
vecAsFloat.push_back(ConvertExpensiveStruct(s));
}
FooAsFloat(vecAsFloat); // call with copy
}
}
As @Jarod42 commented below, you can even avoid the extration into another function:
Do the main processing in the if
part, and use a recursive call for the else
part after creating the needed std::vector<ExpensiveStruct<float>>
:
template <typename T>
void Foo(const std::vector<ExpensiveStruct<T>>& vec)
{
if constexpr (std::is_same_v<T, float>) {
// use vec (it contains `ExpensiveStruct<float>` elements as needed)
// ...
}
else {
std::vector<ExpensiveStruct<float>> vecAsFloat;
vecAsFloat.reserve(vec.size());
for (const auto& s : vec) {
vecAsFloat.push_back(ConvertExpensiveStruct(s));
}
// Recursive call with `vecAsFloat`
// (when executed it will get into the main `if constexpr` part to be used):
Foo(vecAsFloat);
}
}