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.
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
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With