C++如何从函数指针推导资源类型,生成GetResource调用实参列表?
我正在开发一套系统,允许用户通过定义数据片段(称为resources)和操作这些资源的函数(称为systems)来创建应用。希望用户仅提供特定类型的函数指针,从中推导需要传递给该函数的资源类型。
上下文代码
#include <vector> #include <unordered_map> #include <memory> using TypeID = size_t; class Type { public: template <class T> static TypeID ID() { static TypeID id = s_Counter++; return id; } private: inline static TypeID s_Counter = 0; }; struct Resource { virtual ~Resource() = default; }; template <typename T> struct Res : public Resource { inline static const TypeID ID = Type::ID<T>(); // Type::ID<T>() 为每个类型返回唯一ID T Data; }; class Application { private: std::unordered_map<TypeID, std::unique_ptr<Resource>> m_Resources; std::vector<void(*)()> m_Systems; public: template <typename T> void AddResource() { m_Resources[Res<T>::ID] = std::make_unique<Res<T>>(); } template <typename T> T& GetResource() { return m_Resources[Res<T>::ID]->Data; } template <typename... Resources> void AddSystem(void (*pSystem)(Resources...)) { m_Systems.push_back([pSystem]() { pSystem(/* 需要将参数包Resources展开为GetResource<每个类型>()的调用列表 */); }); } }; struct Foo { int a; float b; }; void system(Foo foo, int num, float val) { /* 使用foo、num、val进行操作 */ } int main() { Application app; app.AddResource<Foo>(); app.AddResource<int>(); app.AddResource<float>(); app.AddSystem(system); }
核心需求:在AddSystem函数中,将模板参数包(例如<Foo, int, float>)展开为GetResource<Foo>(), GetResource<int>(), GetResource<float>(),并将这些调用的返回值作为实参传递给用户定义的pSystem函数,最终生成代码pSystem(GetResource<Foo>(), GetResource<int>(), GetResource<float>());。
可移植的实现方案
1. 直接展开参数包(C++11及以上,最简洁)
函数调用中的参数包展开是C++11引入的标准特性,完全可移植。只需修改AddSystem中的lambda,捕获this(因为GetResource是成员函数)并直接展开参数包:
template <typename... Resources> void AddSystem(void (*pSystem)(Resources...)) { m_Systems.push_back([this, pSystem]() { pSystem(GetResource<Resources>()...); }); }
关键说明:
GetResource<Resources>()...会将参数包Resources中的每个类型R替换为GetResource<R>(),自动生成对应的实参列表。- 必须捕获
this,否则无法调用成员函数GetResource。
2. C++11兼容的冗余展开(备选)
如果需要兼容更早期的编译器(虽然C++11已经普及),可以用初始化列表辅助展开:
template <typename... Resources> void AddSystem(void (*pSystem)(Resources...)) { m_Systems.push_back([this, pSystem]() { // 使用初始化列表强制展开参数包 auto call = [&](){ pSystem(GetResource<Resources>()...); }; call(); }); }
其实本质和第一种方案一致,只是显式封装了调用,确保参数包正确展开。
额外的设计优化建议
传递资源引用而非值:当前系统函数按值传递资源会触发拷贝,建议修改为接受引用,
GetResource返回的引用可直接传递,无需修改AddSystem实现:void system(Foo& foo, int& num, float& val) { /* ... */ }资源存在性检查:原代码中
GetResource使用m_Resources[Res<T>::ID]会在资源不存在时创建空指针,导致未定义行为,建议添加检查:template <typename T> T& GetResource() { auto it = m_Resources.find(Res<T>::ID); if (it == m_Resources.end()) { throw std::runtime_error("Resource not found"); } return static_cast<Res<T>*>(it->second.get())->Data; }支持资源初始化:允许用户添加资源时传入初始值,提升易用性:
template <typename T, typename... Args> void AddResource(Args&&... args) { m_Resources[Res<T>::ID] = std::make_unique<Res<T>>(std::forward<Args>(args)...); }使用示例:
app.AddResource<Foo>({1, 3.14f});添加const版本的GetResource:支持在const上下文中安全获取资源:
template <typename T> const T& GetResource() const { auto it = m_Resources.find(Res<T>::ID); if (it == m_Resources.end()) { throw std::runtime_error("Resource not found"); } return static_cast<const Res<T>*>(it->second.get())->Data; }
内容的提问来源于stack exchange,提问作者Thomas

