Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I simplify a switch statement that executes a templated function?

Consider the following program:

#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(); }

Here A, B and C are classes and func is some templated function that is explicitly specialized.

As you can see, the following pattern:

switch(some_enum) {
   case SomeEnum::A: return func<A>();
   case SomeEnum::B: return func<B>();
   case SomeEnum::C: return func<C>();

   default:
      // error handling
}

needs to be repeated several times, but just with different functions. How can I simplify it?

The only thing I can think about is to define a macro, which is not very pretty.

like image 450
User12547645 Avatar asked Sep 15 '25 15:09

User12547645


1 Answers

A Basic Solution For my_enum

A template template is normally the way to go for this kind of thing. Unfortunately, those only exist for types, and not for non-type parameters like function pointers.

But, turn those functions into functors:

template<typename T>
struct func1;

template<>
struct func1<A> {
    void operator ()() {
        std::cout << "func 1 A";
    }
};

/* etc */

And you're good to go:

template <template <typename T> class F, typename... Args>
void dispatch(my_enum me, Args&&... 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)...);
    }
}

usage:

my_enum me = my_enum::A;
dispatch<func1>(me);
std::cout << '\n';
dispatch<func2>(me);

demo: https://godbolt.org/z/c6jPxa

Generalizing A Bit Further

If you're willing to get "clever", you can generalize dispatch too. Define some helper stubs:

template <typename TEnum, TEnum... Es>
struct Tags {};

template <typename... Ts>
struct Types {};

template <typename TTags, typename TTypes>
struct Dispatch;

Tags and Types hold the enums, and types you want to test:

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>;

Then, you can generate that switch block by combining an initializer list, the comma operator, and the ternary operator.

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, Args&&... args)
        {
            (void)std::initializer_list<bool> {
                e == ETags ? (F<TTypes>{}(std::forward<Args>(args)...), false) : false...
            };
        }
    };
};

Usage:

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);
}

Demo: https://godbolt.org/z/Edhbrr

For Functions With Return Types

If you want your functors to return a value, it's a little trickier because you need a default return value if none of the tags match. This solution calls F<void>{}(...) in that case by recursively testing one argument and calling a dispatcher with the remaining arguments.

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, Args&&... 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, Args&&... args) -> decltype(F<void>{}(std::forward<Args>(args)...))
        {
            return F<void>{}(std::forward<Args>(args)...);
        }
    };
};

Demo: https://godbolt.org/z/qz6r4T

like image 178
parktomatomi Avatar answered Sep 17 '25 05:09

parktomatomi