Consider a bunch of fundamental types, Foo
, all with unique implementations of a common method, Bar()
. I can combine Foo1
, Foo2
, Foo5
like so:
CombinedFoo<Foo1, Foo2, Foo5> combined_foo;
Which uses recursive inheritance to make CombinedFoo
effectively the same as:
class CombinedFoo <Foo1, Foo2, Foo5>
{
Foo1 foo1;
Foo2 foo2;
Foo5 foo5;
public:
void Bar ()
{
foo1.Bar();
foo2.Bar();
foo5.Bar();
}
};
This is handy, but I run into a problem when I want to choose at run-time which Foo
types to combine (into a single object) to send to function, say:
template <typename Foo> void Do (Foo && foo);
An example solution with if
s and switch
s to solve the 3 option version:
int result = 0;
if (criteria_for_foo1)
result += 100;
if (criteria_for_foo2)
result += 10;
if (criteria_for_foo3)
result += 1;
switch (result)
{
case 001 : Do(Foo3());
break;
case 010 : Do(Foo2());
break;
case 011 : Do(CombinedFoo<Foo2, Foo3>());
break;
case 100 : Do(Foo1());
break;
case 101 : Do(CombinedFoo<Foo1, Foo3>());
break;
case 110 : Do(CombinedFoo<Foo1, Foo2>());
break;
case 111 : Do(CombinedFoo<Foo1, Foo2, Foo3>());
break;
default : break;
}
The if
statements are fine, they grow linearly, but the switch
statement grows exponentially as we have more choices. My real-world problem has 4 options and so I need to handle 16 cases that I'd rather not have to maintain.
I believe that there's no way to avoid the executable from growing exponentially, but is there a way to avoid this in the c++ code (without introducing significant inefficiencies in the Bar
method)? Or is there a known work-around / alternative for this generic problem?
EDIT:
For clarity: Do(Foo1); Do(Foo2)
is not the same as Do(CombinedFoo<Foo1, Foo2>())
, and it's crucial that the Foo
s are combined for a single call to Do
.
For those who wanting to know the real-world motivation: it's for an optimisation problem, where my Foo
s are really Generator
s of fundamental Move
s that can edit my solution, this is then sent into various heuristics. If I was to send in just one Generator
at a time then my solvers would be repeating the same type of move thousands of times, and so invariably being unproductive / stuck at local minima (considering the same type of move repeatedly is well known to have this effect).
The reason I select some of these template parameters at run-time is because some Move
s aren't appropriate for certain problem instances (which my program doesn't become aware of till run-time).
Here's the interface:
template <typename ... Types, typename Functor>
void ChooseTypes (const bool chosen[], Functor && f)
Where chosen[0] = true
signals choosing the first type from the list.
Internally the types are forwarded to the functor like so: f()<ChosenTypes...>()
with ordering maintained.
struct MyFunctor
{
// data/references can be stored/passed here
template <typename ... Ts>
void operator () ()
{
// Ts = the chosen types
// Here's where we get to use them...
}
};
// later in func/method:
bool chosen[5];
// choose which types you want
MyFunctor f;
// pass data in the functor
// (const/& by constructor)
// Then this will call the functor with the chosen types:
ChooseTypes<T1, T2, T3, T4, T5>(chosen, f);
namespace ChooseTypesRecursive // internal use only
{
// CTR = ChooseTypesRecursive
template <int N, typename Functor>
struct CTR
{
using Next = CTR<N-1, Functor>;
template <typename CandidateType, typename ... Args>
static void Ctr (const bool * chosen, Functor & f)
{
if (*chosen)
{
Next::template Ctr<Args..., CandidateType>
(++chosen, f);
}
else
{
Next::template Ctr<Args...>
(++chosen, f);
}
}
};
template <typename Functor>
struct CTR <0, Functor>
{
template <typename ... ChosenTypes>
static void Ctr (const bool *, Functor & f)
{
f.template operator()<ChosenTypes...>();
}
};
} // namespace ChooseTypesRecursive
template <typename ... Types, typename Functor>
void ChooseTypes (const bool chosen[], Functor && f)
{
constexpr int N = sizeof...(Types);
using namespace ChooseTypesRecursive;
CTR<N, Functor>::template Ctr<Types...>(chosen, f);
}