C++/C++11 - Switch statement for variadic templates?
Let's say I have a few structs like this:
struct MyStruct1 {
inline void DoSomething() {
cout << "I'm number one!" << endl;
}
};
struct MyStruct2 {
static int DoSomething() {
cout << "I'm the runner up." << endl;
return 1;
}
};
struct MyStruct3 {
void (*DoSomething)();
MyStruct3() {
开发者_Go百科 DoSomething = &InternalFunction;
}
static void InternalFunction() {
cout << "I'm the tricky loser." << endl;
}
};
As you can see, for all three structs, I can call DoSomething() on an object of that struct and have it work (though this is achieved differently for each struct):
MyStruct1 a;
MyStruct2 b;
MyStruct3 c;
a.DoSomething(); // works, calls Struct1's instance function
b.DoSomething(); // works, calls Struct2's static function, discards return value
c.DoSomething(); // works, calls Struct3's function pointer
Now, let's say I put an arbitrary selection of these structs into a tuple:
tuple<MyStruct2, MyStruct3, MyStruct2, MyStruct1> collection;
Let's also say that I want to take one of those elements and run its DoSomething()
function based on an index that is determined at runtime. To achieve this, I could use a switch statement:
switch(index) {
case 0: get<0>(collection).DoSomething(); break;
case 1: get<1>(collection).DoSomething(); break;
case 2: get<2>(collection).DoSomething(); break;
case 3: get<3>(collection).DoSomething(); break;
}
This works fine and dandy, but gets very tedious, repetitive and error-prone when it needs to be done with multiple differently arranged (and potentially much longer than 4-element) tuples. It would be very handy if a switch statement could be automatically generated based on the number of elements in a variadic template. Pseudocode:
template <typename... T>
void DoSomethingByIndex(int index, tuple<T...>& collection) {
switch(index) {
STATIC_REPEAT(sizeof...(T), X) {
case X: get<X>(collection).DoSomething(); break;
}
}
}
Is there any mechanism in C++11 that would allow me to achieve this? If not, I know I can undoubtedly hack together a solution with a list of function pointers within a template, but I'm just curious if something like this exists, since it would be better suited for my purposes. I'm sure a switch statement's compiler-generated jump list would be more efficient than my homemade function pointer solution as well.
You can use an array to bridge compile-time and runtime: (ab)use variadic templates to statically initialize the array elements, and then index into the array with the runtime parameter. The tricky part is finding the right element type for the array. In addition since we need the template to be variadic on the tuple indices rather than on the tuple elements I'll be using my usual trick.
template<int... Indices>
struct indices {
typedef indices<Indices..., sizeof...(Indices)> next;
};
template<int N>
struct build_indices {
typedef typename build_indices<N - 1>::type::next type;
};
template<>
struct build_indices<0> {
typedef indices<> type;
};
// No need to be variadic on the tuple elements as we don't care about them
// So I'm using perfect forwarding for the tuple
template<typename Tuple, int... Indices>
void
do_something_by_index(Tuple&& tuple, int index, indices<Indices...>)
{
using std::get;
typedef void (*element_type)(Tuple&&);
static constexpr element_type table[] = {
[](Tuple&& tuple)
{ get<Indices>(std::forward<Tuple>(tuple)).DoSomething(); }
...
};
table[index](std::forward<Tuple>(tuple));
}
// Proverbial layer of indirection to get the indices
template<typename Tuple>
void
do_something_by_index(Tuple&& tuple, int index)
{
typedef typename std::decay<Tuple>::type decay_type;
constexpr auto tuple_size = std::tuple_size<decay_type>::value;
typedef typename build_indices<tuple_size>::type indices_type;
do_something_by_index(std::forward<Tuple>(tuple), index, indices_type{});
}
Hmmm, I'm tempted to try something like this:
template<int N, typename ...Args>
struct call_N_helper
{
static void call(const std::tuple<Args...> & t, int i)
{
if (i == N) std::get<N>(t).call();
else call_N_helper<N-1, Args...>(t, i);
}
};
template<typename ...Args>
struct call_N_helper<0, Args...>
{
static void call(const std::tuple<Args...> & t, int i)
{
if (i == 0) std::get<0>(t).call();
}
};
template<typename ...Args>
void call_N(const std::tuple<Args...> & t, int i)
{
call_N_helper<sizeof...(Args), Args...>::call(t, i);
}
This is just an idea, untested and all.
精彩评论