如何简化执行模板函数的switch语句?

3

考虑以下 程序

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

这里的ABC是类,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++拥有的是函数模板,可以从中生成不同的函数。这就是为什么您拥有的查找表几乎是最好的原因。 - StoryTeller - Unslander Monica
谢谢您简化示例,但您可能已经抽象了代码的目的。是否涉及实际的A/B/C对象?类型标识符是否附加在像标记联合那样的对象上?一些额外的上下文可能会产生更好的解决方案。 - parktomatomi
你不需要这样做。你可以将这些优化留给编译器。 - Basile Starynkevitch
@BasileStarynkevitch 没有任何优化的问题。OP正在寻求源代码简化。 - cigien
2个回答

2

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

使用方法:

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, Args&&... 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, 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


0

看起来你需要一个从一组封闭的类型到枚举元素的映射:

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

但如果需要在运行时确定idstd::variant API可能提供了更好的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接