#c #templates #enums #switch-statement
#c #шаблоны #перечисления #switch-statement
Вопрос:
Рассмотрим следующую программу:
#include <string_view>
#include <string>
#include <iostream>
class A;
class B;
class C;
template<typename T>
void func1();
template<>
void func1<A>() {
std::cout << "func 1 A";
}
template<>
void func1<B>(){
std::cout << "func 1 B";
}
template<>
void func1<C>() {
std::cout << "func 1 C";
}
template<typename T>
void func2();
template<>
void func2<A>() {
std::cout << "func 2 A";
}
template<>
void func2<B>(){
std::cout << "func 2 B";
}
template<>
void func2<C>() {
std::cout << "func 2 C";
}
enum class my_enum { A, B, C};
void wrapper() {
my_enum me = my_enum::A;
switch (me) {
case my_enum::A: func1<A>(); break;
case my_enum::B: func1<B>(); break;
case my_enum::C: func1<C>();
}
std::cout << 'n';
switch (me) {
case my_enum::A: return func2<A>(); break;
case my_enum::B: return func2<B>(); break;
case my_enum::C: return func2<C>();
}
}
int main() { wrapper(); }
Здесь A
B
и C
являются классами, а func
также некоторой шаблонной функцией, которая явно специализирована.
Как вы можете видеть, следующий шаблон:
switch(some_enum) {
case SomeEnum::A: return func<A>();
case SomeEnum::B: return func<B>();
case SomeEnum::C: return func<C>();
default:
// error handling
}
необходимо повторить несколько раз, но только с разными функциями. Как я могу это упростить?
Единственное, о чем я могу думать, это определить макрос, что не очень красиво.
Комментарии:
1. «шаблонная функция» — это неправильное название. В C нет шаблонных функций. Просто потому, что шаблоны не являются классами или функциями. Это инструкции для их генерации. Что есть в C , так это шаблоны функций. Из которого генерируются разные функции. Именно поэтому таблица поиска, которая у вас есть, настолько хороша, насколько это возможно.
2. Спасибо за упрощение примера, но вы, возможно, абстрагировались от цели кода. Задействован ли фактический объект A / B / C? Привязан ли идентификатор типа к объекту, как в помеченном объединении? Небольшой дополнительный контекст может дать лучшее решение
3. Вы этого не делаете. Вы оставляете эти оптимизации компилятору
4. @BasileStarynkevitch Об оптимизации нигде не может быть и речи. OP запрашивает упрощение исходного кода.
Ответ №1:
Базовое решение для my_enum
Шаблон шаблона обычно подходит для такого рода вещей. К сожалению, они существуют только для типов, а не для нетиповых параметров, таких как указатели на функции.
Но превратите эти функции в функторы:
template<typename T>
struct func1;
template<>
struct func1<A> {
void operator ()() {
std::cout << "func 1 A";
}
};
/* etc */
И все готово:
template <template <typename T> class F, typename... Args>
void dispatch(my_enum me, Argsamp;amp;... args) {
switch (me) {
case my_enum::A: F<A>{}(std::forward<Args>(args)...); break;
case my_enum::B: F<B>{}(std::forward<Args>(args)...); break;
case my_enum::C: F<C>{}(std::forward<Args>(args)...);
}
}
использование:
my_enum me = my_enum::A;
dispatch<func1>(me);
std::cout << 'n';
dispatch<func2>(me);
демонстрация: https://godbolt.org/z/c6jPxa
Обобщая немного дальше
Если вы хотите стать «умным», вы dispatch
тоже можете обобщить. Определите некоторые вспомогательные заглушки:
template <typename TEnum, TEnum... Es>
struct Tags {};
template <typename... Ts>
struct Types {};
template <typename TTags, typename TTypes>
struct Dispatch;
Tags
и Types
удерживайте перечисления и типы, которые вы хотите протестировать:
template <template <typename T> class F>
using dispatch = typename Dispatch<
Tags<my_enum, my_enum::A, my_enum::B, my_enum::C>, // the tags
Types<A, B, C> // their respective types
>::type<F>;
Затем вы можете сгенерировать этот блок switch, объединив список инициализаторов, оператор запятой и троичный оператор.
template <typename TEnum, TEnum... ETags, typename... TTypes>
struct Dispatch<Tags<TEnum, ETags...>, Types<TTypes...>> {
template <template <typename T> class F>
struct type {
template <typename... Args>
void operator()(TEnum e, Argsamp;amp;... args)
{
(void)std::initializer_list<bool> {
e == ETags ? (F<TTypes>{}(std::forward<Args>(args)...), false) : false...
};
}
};
};
Использование:
template <template <typename T> class F>
auto dispatch = typename Dispatch<
Tags<my_enum::A, my_enum::B, my_enum::C>,
Types<A, B, C>
>::template type<F>{};
void wrapper() {
my_enum me = my_enum::A;
dispatch<func1>(me);
std::cout << 'n';
dispatch<func2>(me);
}
Демонстрация: https://godbolt.org/z/Edhbrr
Для функций с возвращаемыми типами
Если вы хотите, чтобы ваши функторы возвращали значение, это немного сложнее, потому что вам нужно возвращаемое значение по умолчанию, если ни один из тегов не совпадает. F<void>{}(...)
В этом случае это решение вызывается путем рекурсивной проверки одного аргумента и вызова диспетчера с остальными аргументами.
template <typename TEnum, TEnum ETag, TEnum... ETags, typename TType, typename... TTypes>
struct Dispatch<Tags<TEnum, ETag, ETags...>, Types<TType, TTypes...>> {
template <template <typename T> class F>
struct type {
template <typename... Args>
auto operator()(TEnum e, Argsamp;amp;... args) -> decltype(F<TType>{}(std::forward<Args>(args)...))
{
return e == ETag
? F<TType>{}(std::forward<Args>(args)...)
: typename Dispatch<Tags<TEnum, ETags...>, Types<TTypes...>>::template type<F>{}(e, std::forward<Args>(args)...);
}
};
};
template <typename TEnum>
struct Dispatch<Tags<TEnum>, Types<>> {
template <template <typename T> class F>
struct type {
template <typename... Args>
auto operator()(TEnum e, Argsamp;amp;... args) -> decltype(F<void>{}(std::forward<Args>(args)...))
{
return F<void>{}(std::forward<Args>(args)...);
}
};
};
Демонстрация: https://godbolt.org/z/qz6r4T
Ответ №2:
Похоже, вам нужно сопоставление из замкнутого набора типов с элементами перечисления:
enum SomeEnum;
SomeEnum constexpr id= ???;
return func<mapped_type<id>>();
это может быть достигнуто с помощью std::tuple
API:
template<SomeEnum id>
using mapped_type=std::tuple_element_t<static_cast<std::size_t>(id), std::tuple<A,B,C>>;
но если id
это должно быть определено во время выполнения, std::variant
API может предоставить гораздо лучшую альтернативу.