C++模板函数中实现编译时类型注册与检索是否可行?
编译时类型注册与实例获取方案实现
需求概述
需要实现一个Widgets类,支持:
- 通过模板函数
addWidget<T>在编译时注册类型,生成类型列表 - 通过
getWidget<T>获取对应类型的实例,若T未注册则触发编译错误 - 通过
init()从编译时类型列表生成运行时的类型-实例映射
用户提供的示例代码框架:
class Widgets { template<typename T> void addWidget() { // register T in a type list at compile time here } template<typename T> T& getWidget() { // compile time check to see if T is registered // if registered then return a reference from a runtime map } void init() { // create a runtime type->instance map from registered types } } // 使用示例:未注册SizeWidget时触发编译错误 SizeWidget& w = widgets.getWidget<SizeWidget>();
可行实现方案
以下方案基于C++17特性,无需依赖Boost,可完美满足需求:
1. 编译时类型列表与检查工具
先实现基础的编译时类型列表和类型检查元函数:
#include <type_traits> // 编译时类型列表模板 template<typename... Ts> struct TypeList {}; // 元函数:检查类型是否在列表中 template<typename T, typename List> struct IsTypeInList : std::false_type {}; template<typename T, typename... Ts> struct IsTypeInList<T, TypeList<T, Ts...>> : std::true_type {}; template<typename T, typename... Ts> struct IsTypeInList<T, TypeList<Ts..., T>> : std::true_type {}; // 辅助变量模板,简化调用 template<typename T, typename List> constexpr bool IsTypeInListV = IsTypeInList<T, List>::value;
2. Widgets类实现
结合编译时类型列表与运行时实例映射:
#include <unordered_map> #include <typeindex> #include <memory> #include <stdexcept> class Widgets { // 封装类型列表的辅助结构体 template<typename... Ts> struct RegisteredTypes : TypeList<Ts...> {}; // 用inline静态变量存储编译时注册的类型列表(C++17特性,跨TU生效) inline static auto registered_types = RegisteredTypes<>{}; // 私有辅助:扩展类型列表的模板推导函数 template<typename T, typename List> static auto extend_type_list(List) -> RegisteredTypes<T, typename List::Type...>; public: template<typename T> void addWidget() { // 编译时扩展类型列表:通过模板推导生成新的RegisteredTypes实例 registered_types = decltype(extend_type_list<T>(registered_types)){}; } template<typename T> T& getWidget() { // 编译时断言:未注册类型直接触发编译错误 static_assert(IsTypeInListV<T, decltype(registered_types)>, "Error: Type is not registered in Widgets"); // 从运行时map获取实例 auto it = widget_map.find(std::type_index(typeid(T))); if (it == widget_map.end()) { throw std::runtime_error("Widget instance not initialized (call init() first)"); } return *static_cast<T*>(it->second.get()); } void init() { // 遍历编译时类型列表,创建所有注册类型的实例 init_impl(registered_types); } private: // 运行时实例存储map std::unordered_map<std::type_index, std::unique_ptr<void>> widget_map; // 递归遍历类型列表的初始化实现(C++17折叠表达式) template<typename... Ts> void init_impl(TypeList<Ts...>) { (add_instance<Ts>(), ...); } // 单个类型的实例创建与存储 template<typename T> void add_instance() { widget_map.emplace(std::type_index(typeid(T)), std::make_unique<T>()); } };
3. 关键说明
- 编译时检查:
getWidget<T>中的static_assert会在编译期验证T是否在注册列表中,未注册直接报错,符合需求 - 跨翻译单元注册:C++17的
inline静态变量确保registered_types在整个程序中是唯一实例,不同TU中调用addWidget<T>会累积扩展类型列表 - 初始化顺序:必须先调用所有
addWidget<T>完成注册,再调用init()生成实例,否则getWidget<T>会找不到运行时实例 - C++11/14兼容:若需支持更早标准,可改用静态模板变量触发注册(需显式声明
Registrar<SizeWidget>::registered),但灵活性略逊
关于Boost Fusion与Stack Overflow方案的说明
- Boost Fusion的Map是不可变的,每次插入都会生成新的类型,无法在模板函数中动态扩展注册列表,因此不适用
- 部分Stack Overflow的编译时注册技巧依赖全局静态变量初始化,难以在模板函数内灵活触发,而上述方案通过
inline静态变量结合模板推导完美解决了这个问题
内容的提问来源于stack exchange,提问作者signedbit
相关产品推荐
相关产品推荐

