如何消除C++运行时类型调用中的样板代码?
问题描述
我有一些简化后的代码:
inline Type f(const int*) { return Type::Int; } inline Type f(const double*) { return Type::Double; } inline void g(int) { } inline void g(double) { }
类型是运行时管理的枚举:
enum class Type { Int, Double };
我想写一个工具函数,把运行时的Type转换为模板参数,避免重复写大量样板代码。目前的实现如下,do_call_f()和do_call_g()里有很多重复的分支判断:
void do_call_f(Type t) { if (t == Type::Int) { const auto result = f(static_cast<const int*>(nullptr)); } if (t == Type::Double) { const auto result = f(static_cast<const double*>(nullptr)); } } void do_call_g(Type t) { if (t == Type::Int) { g(123); } if (t == Type::Double) { g(1.23); } } int main() { do_call_f(Type::Int); do_call_f(Type::Double); do_call_g(Type::Int); do_call_g(Type::Double); }
我尝试过各种模板技巧但没成功,期望的工具函数大致像这样的伪代码:
template<typename TFunc, typename TArg> auto invoke(Type t, TFunc func, TArg&& arg) { if (t == Type::Int) return func<char>(std::forward<TArg>(arg)); if (type == Type::Double) return func<double>(std::forward<TArg>(arg)); // ... }
需要兼容C14,也可以提供C23的实现作为参考。
解决方案
C++14 实现
核心思路是建立Type枚举到具体类型的映射,通过分发器统一处理分支逻辑,避免重复编写判断代码:
#include <utility> #include <stdexcept> enum class Type { Int, Double }; // 类型-枚举映射模板,特化每个类型对应的枚举值 template<typename T> struct TypeMap; template<> struct TypeMap<int> { static constexpr Type value = Type::Int; }; template<> struct TypeMap<double> { static constexpr Type value = Type::Double; }; // 自定义type_identity,C++14标准库未提供 template<typename T> struct type_identity { using type = T; }; template<typename T> using type_identity_t = typename type_identity<T>::type; // 通用分发函数:根据Type枚举值调用对应类型的处理逻辑 template<typename F> auto dispatch(Type t, F&& func) { switch(t) { case Type::Int: return std::forward<F>(func)(type_identity_t<int>{}); case Type::Double: return std::forward<F>(func)(type_identity_t<double>{}); default: throw std::invalid_argument("Unknown Type"); } }
现在可以用这个分发器简化原来的调用逻辑:
inline Type f(const int*) { return Type::Int; } inline Type f(const double*) { return Type::Double; } inline void g(int) { } inline void g(double) { } void do_call_f(Type t) { dispatch(t, [](auto tag) { using T = typename decltype(tag)::type; const auto result = f(static_cast<const T*>(nullptr)); // 这里可以添加result的处理逻辑 }); } void do_call_g(Type t) { dispatch(t, [](auto tag) { using T = typename decltype(tag)::type; if (std::is_same<T, int>::value) { g(123); } else if (std::is_same<T, double>::value) { g(1.23); } }); } // 如果需要返回值,分发器也支持(所有分支返回类型需一致) Type call_f(Type t) { return dispatch(t, [](auto tag) { using T = typename decltype(tag)::type; return f(static_cast<const T*>(nullptr)); }); }
C++23 实现
C++23的模板lambda、std::visit和std::variant能让实现更简洁:
#include <variant> #include <stdexcept> #include <type_traits> enum class Type { Int, Double }; // 将Type枚举转换为对应类型的variant constexpr auto type_to_variant(Type t) { switch(t) { case Type::Int: return std::variant<int, double>(int{}); case Type::Double: return std::variant<int, double>(double{}); default: throw std::invalid_argument("Unknown Type"); } } // 通用invoke函数 template<typename F> auto invoke(Type t, F&& func) { return std::visit([&func]<typename T>(T) { return func.template operator()<T>(); }, type_to_variant(t)); }
使用时直接借助模板lambda指定类型参数:
inline Type f(const int*) { return Type::Int; } inline Type f(const double*) { return Type::Double; } inline void g(int) { } inline void g(double) { } void do_call_f(Type t) { invoke(t, []<typename T>() { const auto result = f(static_cast<const T*>(nullptr)); }); } void do_call_g(Type t) { invoke(t, []<typename T>() { if constexpr(std::same_as<T, int>) { g(123); } else if constexpr(std::same_as<T, double>) { g(1.23); } }); }
内容的提问来源于stack exchange,提问作者Blake Whitmore
相关产品推荐
相关产品推荐

