Как я могу упростить оператор switch, который выполняет шаблонную функцию?

#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 может предоставить гораздо лучшую альтернативу.