Задача проста: у нас есть список типов (т.е. using list = std::tuple<A, B, C, ...>;
), и мы хотим отправить его содержимое на основе индекса, известного во время выполнения. Под "отправкой" я имею в виду вызов шаблонного обработчика, параметризованного типом из этого списка.
Легко написать switch
вручную:
template <class... Args>
auto dispatch(size_t i, Args&& ...args)
{
switch (i) {
case 0: return handler<typename std::tuple_element<0, list>::type>(std::forward<Args>(args)...);
...
}
}
Но это утомительно, склонно к ошибкам и скучно. Я ищу способ заставить компилятор сделать это из более компактного и краткого определения.
И я хочу добиться результата, идентичного или очень близкого к ручному переключателю. Таким образом, создание таблицы указателей функций (например, здесь) не является желаемым решением (я не хочу вводить вызов функции для случаев, когда обработчики совершенно проницаемы).
До сих пор я нашел два пути:
-
Инклинированная рекурсия (благодаря Horstling)
template <size_t N> struct dispatch_helper { template <class... Args> static R dispatch(size_t i, Args&& ...args) { if (N == i) return handler<typename std::tuple_element<N, list>::type>(std::forward<Args>(args)...); return dispatch_helper<N+1>::dispatch(i, std::forward<Args>(args)...); } }; template <> struct dispatch_helper<200> { template <class... Args> static R dispatch(size_t type, Args&& ...args) { return {}; } }; template <class... Args> auto dispatch_recursion(size_t i, Args&& ...args) { return dispatch_helper<0>::dispatch(i, std::forward<Args>(args)...); }
-
Использование
std::initializer_list
template <class L> struct Iterator; template <template <class...> class L, class... T> struct Iterator<L<T...>> { template <class... Args> static R dispatch(size_t i, Args&& ...args) { R res; std::initializer_list<int> l { ( i == T::value ? (res = handler<typename T::type>(std::forward<Args>(args)...), 0) : 0 )... }; (void)l; return res; } }; template <class... Args> auto dispatch_type_list_iter(size_t i, Args&& ...args) { return Iterator<index_list<list>>::dispatch(i, std::forward<Args>(args)...); }
Я опускаю некоторые подробности здесь, например, преобразовываю список типов в список индексированных типов или как обращаться с типом возврата, но я надеюсь, что идея понятна.
Я пробовал те на godbolt, а Clang 4.0 генерирует "логарифмический" переключатель (дерево небольших переключателей) для первого подхода и код, идентичный ручному переключателю для второго подхода. Я играл с размером обработчика, встроенным или неуправляемым обработчиком, и результаты выглядели стабильными.
Но GCC и ICC генерируют простую последовательность условностей (для обоих подходов), которая очень грустная.
Итак, существуют ли другие решения? Особенно те, кто работает, по крайней мере, в Clang и GCC.