С++/С++ 11 - Инструкция switch для вариативных шаблонов?
Скажем, у меня есть несколько таких структур:
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() {
DoSomething = &InternalFunction;
}
static void InternalFunction() {
cout << "I'm the tricky loser." << endl;
}
};
Как вы можете видеть, для всех трех структур я могу вызвать DoSomething() на объекте этой структуры и заставить его работать (хотя для каждой структуры это выполняется по-разному):
MyStruct1 a;
MyStruct2 b;
MyStruct3 c;
a.DoSomething(); // works, calls Struct1 instance function
b.DoSomething(); // works, calls Struct2 static function, discards return value
c.DoSomething(); // works, calls Struct3 function pointer
Теперь, допустим, я помещаю произвольный выбор этих структур в кортеж:
tuple<MyStruct2, MyStruct3, MyStruct2, MyStruct1> collection;
Предположим также, что я хочу взять один из этих элементов и запустить его функцию DoSomething()
на основе индекса, который определяется во время выполнения. Для этого я мог бы использовать оператор switch:
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;
}
Это отлично работает и денди, но становится очень утомительным, повторяющимся и подверженным ошибкам, когда это нужно делать с несколькими по-разному упорядоченными (и, возможно, намного длиннее 4-элементными) кортежами. Было бы очень удобно, если бы оператор switch автоматически генерировался на основе количества элементов в вариационном шаблоне. Псевдокод:
template <typename... T>
void DoSomethingByIndex(int index, tuple<T...>& collection) {
switch(index) {
STATIC_REPEAT(sizeof...(T), X) {
case X: get<X>(collection).DoSomething(); break;
}
}
}
Есть ли какой-нибудь механизм в С++ 11, который позволил бы мне это достичь? Если нет, я знаю, что могу без сомнения взломать решение со списком указателей на функции в шаблоне, но мне просто интересно, если что-то вроде этого существует, так как это было бы лучше для моих целей. Я уверен, что список подсказок, созданный компилятором оператора switch, будет более эффективным, чем мое домашнее решение для указателей на функцию.
Ответы
Ответ 1
Вы можете использовать массив для объединения времени компиляции и времени выполнения: (ab) использовать вариативные шаблоны для статической инициализации элементов массива, а затем индексировать в массив с параметром runtime. Трудная часть - поиск правильного типа элемента для массива. Кроме того, поскольку нам нужно, чтобы шаблон был переменным по индексам кортежа, а не по элементам кортежа, я буду использовать свой обычный трюк.
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{});
}
Ответ 2
Хммм, у меня возникает соблазн попробовать что-то вроде этого:
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);
}
Это просто идея, непроверенная и все.