You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++编译时类型注册表实现:类型与递增ID双向映射的问题

C++编译时类型注册表实现:类型与递增ID双向映射的问题

我现在需要实现一个编译时的"注册表",能把注册的类型映射到递增的整数ID,同时也能通过ID反向查找到对应的类型。理想中的使用方式大概是这样的:

#define component(T) /* ?? */
template <int I> using registry_t = /* ?? */;

component(some_type) {
    // some_type 被映射到ID 0
};

namespace my_namespace {
component(some_type) {
    // my_namespace::some_type 被映射到ID 1
};
}

static_assert(some_type::id() == 0, "First registered component gets ID 0");
static_assert(my_namespace::some_type::id() == 1, "Second should get ID 1");
static_assert(std::is_same_v<registry_t<0>, some_type>, "Should match");
static_assert(std::is_same_v<registry_t<1>, my_namespace::some_type>, "Should match");

我计划用falemagn的constexpr计数器库来实现这个任意类型到递增ID的映射,这个库的工作机制是:每次用唯一类型调用计数器的next方法时,就会分配一个递增的数字,示例代码如下:

struct __context;
static constexpr fameta::counter<__context> component_counter;

struct struct1;
static_assert(component_counter.next<struct1>() == 0, "Starts at 0");

struct helloworld;
static_assert(component_counter.next<helloworld>() == 1, "Increments to 1");

struct foobar;
static_assert(component_counter.next<foobar>() == 2, "Increments to 2");

下面是我尝试过的几个实现方案:

尝试方案1:模板元编程

我第一个想到的常规实现方式是用模板特化:

template <int I> struct registry;
template <int I> using registry_t = typename registry<I>::type;

struct some_type {};
template <> struct registry<0> { using type = ::some_type; };

但这个方案有个致命的局限:component宏只能在全局命名空间(或者注册表模板所在的父命名空间)里使用,因为C++不允许在非父命名空间中特化模板,这直接限制了在子命名空间中注册类型的场景,不符合需求。

尝试方案2:重载技巧

为了绕开命名空间的限制,我想到了用重载的思路:先定义一个用int模板参数生成唯一类型的类,然后通过一个永远不会被实际调用的模板函数来充当注册表——这个函数接收unique<I>类型的参数,返回对应的注册类型。注册类型时,通过static_assert隐式特化这个模板函数。具体代码如下:

template <int> class unique final {};

template <class T>
static constexpr T _registry(unique<component_counter.next<T>()>) noexcept;

template <int I>
using registry_t = std::remove_pointer_t<decltype(_registry(unique<I>{}))>;

template <class T>
struct component {
    static_assert(std::is_same_v<decltype(_registry<T>({})), T>);
    static constexpr int id() noexcept {
        return component_counter.next<T>();
    }
};

但这个方案编译失败了,用GCC 15.1编译时的错误信息如下:

<source>: In substitution of 'template<int I> using ecs::registry_t = std::remove_pointer_t<decltype (ecs::_registry(ecs::unique<I>{}))> [with int I = 0]':
<source>:136:47: required from here
136 | static_assert(std::is_same_v<ecs::registry_t<0>, test>, "");
     |                                               ^
<source>:123:64: error: no matching function for call to '_registry(ecs::unique<0>)'
123 | using registry_t = std::remove_pointer_t<decltype(_registry(unique<I>{}))>;
     |                                                        ^~~~~~~~~
<source>:123:64: note: there is 1 candidate:
<source>:121:24: note: candidate: 'template<class T> constexpr T ecs::_registry(unique<component_counter.next<T>()>)'
121 | static constexpr T _registry(unique<component_counter.next<T>()>) noexcept;
     |                        ^~~~~~~~~
<source>:121:24: note:   template argument deduction/substitution failed:
<source>:123:64: note:   couldn't deduce template parameter 'T'
123 | using registry_t = std::remove_pointer_t<decltype(_registry(unique<I>{}))>;
     |                                                        ^~~~~~~~~
<source>:136:54: error: template argument 1 is invalid
136 | static_assert(std::is_same_v<ecs::registry_t<0>, test>, "");
     |                                                      ^
Compiler returned: 1

核心问题是registry_t无法从unique<I>参数推导出模板参数T,编译器没办法逆向从ID找到对应的类型。而且我测试下来这个方案和MSVC也不兼容。

尝试方案3:ADL与友元重载

在2025年7月23日晚上7点左右,我又尝试了用参数依赖查找(ADL)结合友元重载的方式来实现,但目前还在摸索这个方向的具体实现细节。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 12:53:08