I need to refactor the following code (simplified for clarity) :
#include <iostream>
// those structs cannot be changed
struct Foo1 {int f;};
struct Foo2 {int g;};
struct Foo3 {int h;};
void processFoo(Foo1 &foo)
{
}
void processFoo(Foo2 &foo)
{
}
void processFoo(Foo3 &foo)
{
}
bool selectFoo2()
{
// known at run time
return true;
}
bool selectFoo3()
{
// known at run time
return false;
}
template<typename ...FooT>
void save(const FooT &...foos)
{
std::cout << "foos = " << sizeof...(foos) << std::endl;
}
void handle_foos()
{
// Foo1 is always selected
Foo1 f1;
processFoo(f1);
if (selectFoo2()) {
Foo2 f2;
processFoo(f2);
if (selectFoo3()) {
Foo3 f3;
processFoo(f3);
save(f1, f2, f3);
} else
save(f1, f2);
} else if (selectFoo3()) {
Foo3 f3;
processFoo(f3);
save(f1, f3);
} else
save(f1);
}
int main(int, char**)
{
handle_foos();
return 0;
}
The handle_foos
function works as expected with only selected types being passed as arguments to the variadic save
function.
However, the code will eventually become less readable with new types (not so many types though) and all those nested if
for all combinations of types in the save
call.
It is not possible to change the structs definition and the save
function.
Basically, i would like to have only one call the the save function with arguments being selected types only. I am looking for a C++14 solution.
I am thinking about storing types that are always selected (known at compile time) in a std::tuple
and then using std::tuple_cat
to dynamically add selected types at runtime.
Then convert the tuple into a parameter pack and pass it to the save
function.
I am not sure it's a good idea, is there something more elegant or efficient ?
Thank you.
One way to use template is:
// Allow to dispatch from the Type directly, to be generic
template <typename T> bool selectFooT();
//template <> bool selectFooT<Foo1>() { return true; }
template <> bool selectFooT<Foo2>() { return selectFoo2(); }
template <> bool selectFooT<Foo3>() { return selectFoo3(); }
template <typename... Ts> struct handle_foos_impl;
// End case: All `Ts` has been processed, now save them.
template <>
struct handle_foos_impl<>
{
template <typename... Foos>
void operator()(const Foos&... foos) const {
save(foos...);
}
};
template <typename Foo, typename... Ts>
struct handle_foos_impl<Foo, Ts...>
{
template <typename... Foos>
void operator()(const Foos&... foos) const {
if (selectFooT<Foo>()) {
Foo foo;
processFoo(foo);
handle_foos_impl<Ts...>{}(foos..., foo);
} else {
handle_foos_impl<Ts...>{}(foos...);
}
}
};
void handle_foos()
{
// Foo1 is always selected
Foo1 f1;
processFoo(f1);
handle_foos_impl<Foo2, Foo3>{}(f1);
}
Demo (note demo is not in C++14, just for display with std::source_location
, but code is C++14 compatible)