无需手动映射,如何用变量作为C++模板参数调用对应特化函数?
问题
能否不使用switch或if else语句为每个可能值编写分支,直接将变量作为模板参数使用?
现有代码如下:
enum A {a, b, c, d}; template<A> void f() {/* default */}; template<> void f<A::a>() {/* ... */} template<> void f<A::b>() {/* ... */} template<> void f<A::c>() {/* ... */} void execute(A action) { f<action>(); // 直接调用无法通过编译,因为action是运行时变量 }
上述直接调用无法实现,目前可采用switch语句的方式:
void execute(A action) { switch (action) { case A::a: f<A::a>(); break; case A::b: f<A::b>(); break; case A::c: f<A::c>(); break; } }
或使用std::map存储函数指针的方式:
#include <map> std::map<A, void(*)()> mp = { {A::a, f<A::a>}, {A::b, f<A::b>}, {A::c, f<A::c>} }; void execute(A action) { mp[action](); }
但这两种方案均需手动编写映射关系。请问是否存在无需手动映射即可根据变量调用对应函数的方法?比如借助宏定义或模板元编程实现。
解决方案
可以通过模板元编程或编译期生成分发逻辑来避免手动维护映射关系,以下是几种可行方案:
1. 模板元编程生成编译期分发表
利用编译期枚举序列,自动生成对应函数的调用逻辑,无需逐个编写分支:
#include <utility> enum A {a, b, c, d}; template<A> void f() {/* default */}; template<> void f<A::a>() {/* 实现a的逻辑 */} template<> void f<A::b>() {/* 实现b的逻辑 */} template<> void f<A::c>() {/* 实现c的逻辑 */} namespace detail { // 递归遍历枚举值的辅助模板 template<A... Vs> struct dispatcher { static void call(A action) {} }; template<A V, A... Vs> struct dispatcher<V, Vs...> { static void call(A action) { if (action == V) { f<V>(); } else { dispatcher<Vs...>::call(action); } } }; } // 生成包含所有枚举值的分发器 using enum_dispatcher = detail::dispatcher<A::a, A::b, A::c, A::d>; void execute(A action) { enum_dispatcher::call(action); }
如果枚举值是连续的,还可以简化为基于索引的调用,避免逐个判断:
#include <array> namespace detail { // 生成存储函数指针的编译期数组 template<std::size_t... Is> constexpr auto make_func_array(std::index_sequence<Is...>) { return std::array<void(*)(), sizeof...(Is)>{ f<static_cast<A>(Is)>... // 假设枚举值从0开始连续 }; } } constexpr auto func_array = detail::make_func_array(std::make_index_sequence<4>()); // 4为枚举值总数 void execute(A action) { func_array[static_cast<std::size_t>(action)](); }
2. 宏定义自动生成switch分支
如果不想用复杂的模板元编程,可通过宏自动生成switch的所有分支,减少手动重复代码:
#define CASE_F(ENUM_VAL) case ENUM_VAL: f<ENUM_VAL>(); break; void execute(A action) { switch(action) { CASE_F(A::a) CASE_F(A::b) CASE_F(A::c) CASE_F(A::d) default: f<A::d>(); // 默认调用逻辑 } } #undef CASE_F
3. C++17 std::variant + std::visit 间接分发
将枚举值转换为编译期可辨识的标签类型,通过std::visit自动匹配对应函数:
#include <variant> // 为每个枚举值定义标签类型 struct TagA {}; struct TagB {}; struct TagC {}; struct TagD {}; // 枚举到标签的转换(可通过模板元编程自动生成) constexpr auto to_tag(A action) { switch(action) { case A::a: return std::variant<TagA, TagB, TagC, TagD>{TagA{}}; case A::b: return std::variant<TagA, TagB, TagC, TagD>{TagB{}}; case A::c: return std::variant<TagA, TagB, TagC, TagD>{TagC{}}; case A::d: return std::variant<TagA, TagB, TagC, TagD>{TagD{}}; } } // 重载针对不同标签的调用函数 void call_f(TagA) { f<A::a>(); } void call_f(TagB) { f<A::b>(); } void call_f(TagC) { f<A::c>(); } void call_f(TagD) { f<A::d>(); } void execute(A action) { std::visit([](auto tag){ call_f(tag); }, to_tag(action)); }
内容的提问来源于stack exchange,提问作者Tellegar
相关产品推荐
相关产品推荐

