如何实现从编译期值到类型的反向关联?
问题背景
我已经实现了一个基础的类型标记结构TypeTag,可以将类型关联到唯一数值,代码及运行结果如下:
struct TypeTag { inline static size_t counter = 0; template<typename T> static size_t get() { static size_t value = counter++; return value; } }; class c1 {}; class c2 {}; class c3 {}; class c4 {}; int main() { std::cout << "c1: " << TypeTag::get<c1>() << '\n'; std::cout << "c2: " << TypeTag::get<c2>() << '\n'; std::cout << "c3: " << TypeTag::get<c3>() << '\n'; std::cout << "c4: " << TypeTag::get<c4>() << '\n'; std::cout << "\n================================\n\n"; std::cout << "c1: " << TypeTag::get<c1>() << '\n'; std::cout << "c2: " << TypeTag::get<c2>() << '\n'; std::cout << "c3: " << TypeTag::get<c3>() << '\n'; std::cout << "c4: " << TypeTag::get<c4>() << '\n'; return 0; }
运行输出:
c1: 0 c2: 1 c3: 2 c4: 3 ================================ c1: 0 c2: 1 c3: 2 c4: 3
这个结构可以实现类型到数值的关联。现在希望实现反向关联:已知编译期size_t值,将数值映射到对应的类型,是否可行?如何实现如下模板?
template <size_t value> struct TypeFrom { using type = ... // 此处实现逻辑 };
实现方案
基于当前的TypeTag结构无法实现编译期的反向映射,因为原结构的数值是运行期首次调用时动态分配的,编译期无法预知每个类型对应的数值。要实现编译期的数值到类型反向映射,需要调整设计,让类型对应的数值在编译期就确定下来,同时维护类型与数值的双向映射关系。
方法1:手动注册类型与数值映射
这种方式直接明了,手动为每个类型指定固定的编译期数值,同时通过元组维护类型列表用于反向查找:
#include <tuple> #include <type_traits> // 正向映射:类型到编译期数值 template<typename T> struct TypeTag; template<> struct TypeTag<c1> { static constexpr size_t value = 0; }; template<> struct TypeTag<c2> { static constexpr size_t value = 1; }; template<> struct TypeTag<c3> { static constexpr size_t value = 2; }; template<> struct TypeTag<c4> { static constexpr size_t value = 3; }; // 辅助模板:从数值提取对应类型 template<size_t Value, typename Tuple> struct TypeFromHelper; template<size_t Value, typename... Ts> struct TypeFromHelper<Value, std::tuple<Ts...>> { using type = std::tuple_element_t<Value, std::tuple<Ts...>>; }; // 注册所有需要映射的类型到元组 using AllRegisteredTypes = std::tuple<c1, c2, c3, c4>; // 实现反向映射模板 template<size_t value> struct TypeFrom { static_assert(value < std::tuple_size_v<AllRegisteredTypes>, "Invalid type value: out of range"); using type = typename TypeFromHelper<value, AllRegisteredTypes>::type; }; // 验证代码 int main() { // 正向映射验证 static_assert(TypeTag<c1>::value == 0); static_assert(TypeTag<c2>::value == 1); // 反向映射验证 static_assert(std::is_same_v<TypeFrom<0>::type, c1>); static_assert(std::is_same_v<TypeFrom<2>::type, c3>); }
方法2:自动分配编译期数值并构建映射
如果不想手动指定数值,可以利用C++11及以上的变参模板和编译期特性,自动为类型分配连续ID,同时维护类型列表:
#include <tuple> #include <type_traits> // 类型注册器,统一管理类型与ID的双向映射 template<typename... Ts> struct TypeRegistry { // 正向映射:获取类型对应的编译期ID template<typename T> static constexpr size_t get_id() { static_assert((std::is_same_v<T, Ts> || ...), "Type not registered"); return std::tuple_size_v<std::tuple<typename std::enable_if<std::is_same_v<T, Ts>, void>::type...>> - 1; } // 反向映射:通过ID获取对应类型 template<size_t Value> using get_type = std::tuple_element_t<Value, std::tuple<Ts...>>; }; // 注册所有需要映射的类型 using MyTypeRegistry = TypeRegistry<c1, c2, c3, c4>; // 适配原有的TypeTag接口 struct TypeTag { template<typename T> static constexpr size_t get() { return MyTypeRegistry::get_id<T>(); } }; // 实现反向映射模板 template<size_t value> struct TypeFrom { static_assert(value < sizeof...(Ts), "Invalid type value: out of range"); using type = typename MyTypeRegistry::get_type<value>; }; // 验证代码 int main() { static_assert(TypeTag::get<c1>() == 0); static_assert(TypeTag::get<c4>() == 3); static_assert(std::is_same_v<TypeFrom<1>::type, c2>); static_assert(std::is_same_v<TypeFrom<3>::type, c4>); }
关键说明
- 原
TypeTag的核心问题在于counter是运行期变量,get<T>()首次调用时才会为类型分配数值,编译期无法获取该值,因此无法反向推导类型。 - 上述两种方案均将类型与数值的绑定放在编译期,通过元组或类型列表维护所有待映射类型,以此实现编译期的双向查找。
- 如果需要支持运行期动态注册类型,则无法实现编译期的反向映射,只能在运行期通过
std::unordered_map<size_t, std::type_index>等结构做运行期查找,但无法满足TypeFrom模板的编译期类型要求。
内容的提问来源于stack exchange,提问作者bibanac
相关产品推荐
相关产品推荐

