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

C++能否仅通过模板参数T查找匹配的特化模板实例?

问题描述

假设我定义了如下代码:

template <typename T, typename ...args>
static std::map<T, std::tuple<args...>> MyMaps;

所有类型在编译期都是确定的,每传入一组不同的类型参数,就会生成一个新的map实例。我能否仅通过键以及键的类型(对应模板参数T),在所有生成的map实例中搜索到匹配的实例?

补充说明

我实际的需求是开发一款适用于Unreal的调试辅助工具,用于跟踪对象的数值,可直接引入的头文件debbughelper.h代码如下:

#include <tuple>
#include <vector>
#include <type_traits>

#define WATCHMYOBJECT(object, ...) //TO DO
#define RESCANMYOBJECT(object) //TO DO

template <typename ...T>
void Expand(T...args)
{
    return;
}

template <typename T, typename ...args>
class MyWatcherClass
{
public:
    static void WatchMyObject(T &object, args& ...b);
    static void RescanMyObject(T& MyObject);
    static std::vector<MyWatcherClass*> Instaces;

private:
    std::tuple<args...> MyTuple = std::tuple<args...>();
    std::vector<void*> VoidPointerStorage;
    T* MyObjectPointer;

private:
    MyWatcherClass();
    ~MyWatcherClass();
};

template <typename T, typename ...args>
void MyWatcherClass<T, args...>::WatchMyObject(T &MyObject, args& ...b)
{
    MyWatcherClass<T, args...>* MyClassPointer = new MyWatcherClass;
    InstacedObjects.push_back(MyClassPointer);
    MyObjectPointer = &MyObject;
    int helpint = 0;
    MyClassPointer->MyTuple = std::make_tuple(b...);
    Expand((MyClassPointer->PointerStorage.push_back((void*)&b),1)...);
}

template <typename T, typename ...args>
void MyWatcherClass<T, args...>::RescanMyObject(T &MyObject)
{
    // 待实现,当前无法调用该方法
    // 遍历Instaces[i].MyObjectPointer和&MyObject比较找到匹配实例
    // 使用tuple的类型将std::vector<void*> VoidPointerStorage中的空指针转回对应类型指针
    // 解引用指针获取数值,更新到屏幕、日志等位置
}

用户的使用方式如下:

// #include "debbughelper.h"
class MyNormalClass
{
public:
    MyNormalClass(int _MyInt, float _MyFloat, std::string _MyString);
    int MyInt;
    float MyFloat;
    std::string MyString;
};

MyNormalClass::MyNormalClass(int _MyInt, float _MyFloat, std::string _MyString) : MyInt(_MyInt), MyFloat(_MyFloat), MyString(_MyString)
{
}

int main()
{
   MyNormalClass MyObject = MyNormalClass(1, 5.2f, std::string("hello"));
   WATCHMYOBJECT(MyObject, MyObject.MyInt, MyObject.MyFloat, MyObject.MyString);

// 执行业务逻辑

   RESCANMYOBJECT(MyObject); // 用法简单,不需要重复输入所有成员变量
}

当前存在的问题是:如果不传入成员变量的类型,就无法调用RescanMyObject方法。


解决方案

你遇到的核心问题是:MyWatcherClass<T, Args...>的静态方法RescanMyObject依赖Args参数包,而调用RESCANMYOBJECT时只有对象类型T,无法推导出Args参数包,自然找不到对应的模板实例。
可以通过全局注册+lambda捕获参数类型的方式解决,不需要修改太多原有代码:

步骤1:新增全局注册表

在debbughelper.h中新增全局注册表,存储对象地址和对应的扫描回调:

#include <unordered_map>
#include <functional>
// 全局注册表:key为被监听对象的地址,value为对应的扫描回调
inline std::unordered_map<void*, std::function<void()>> g_watcherReg;

如果是Unreal环境,也可以替换为TMap<void*, TFunction<void()>>避免STL依赖。

步骤2:实现两个宏定义

利用宏调用时可以拿到所有参数类型的特性,在WATCHMYOBJECT调用时就把RescanMyObject的调用逻辑捕获到lambda中,存入注册表:

#define WATCHMYOBJECT(object, ...) \
do { \
    using ObjType = std::remove_reference_t<decltype(object)>; \
    MyWatcherClass<ObjType, decltype(__VA_ARGS__)...>::WatchMyObject(object, __VA_ARGS__); \
    g_watcherReg[&object] = [&object](){ \
        MyWatcherClass<ObjType, decltype(__VA_ARGS__)...>::RescanMyObject(object); \
    }; \
} while(0)

#define RESCANMYOBJECT(object) \
do { \
    auto it = g_watcherReg.find(&object); \
    if (it != g_watcherReg.end()) { \
        it->second(); \
    } \
} while(0)

步骤3:修正原有代码的笔误和实现RescanMyObject

你原有代码存在变量名不一致的问题,先修正,再补全RescanMyObject的逻辑:

// 先声明静态成员变量
template <typename T, typename ...args>
std::vector<MyWatcherClass<T, args...>*> MyWatcherClass<T, args...>::Instaces;

template <typename T, typename ...args>
void MyWatcherClass<T, args...>::WatchMyObject(T &MyObject, args& ...b)
{
    MyWatcherClass<T, args...>* MyClassPointer = new MyWatcherClass;
    // 修正变量名InstacedObjects为Instaces
    Instaces.push_back(MyClassPointer);
    MyClassPointer->MyObjectPointer = &MyObject;
    MyClassPointer->MyTuple = std::make_tuple(b...);
    // 修正变量名PointerStorage为VoidPointerStorage
    Expand((MyClassPointer->VoidPointerStorage.push_back((void*)&b),1)...);
}

template <typename T, typename ...args>
void MyWatcherClass<T, args...>::RescanMyObject(T &MyObject)
{
    // 遍历当前模板实例的所有监听对象
    for (auto* instance : Instaces) {
        if (instance->MyObjectPointer == &MyObject) {
            // 折叠表达式遍历tuple的类型,把void*转回原类型输出
            std::size_t idx = 0;
            std::apply([&](auto&&... arg) {
                (
                    [&]() {
                        using ArgType = std::remove_reference_t<decltype(arg)>;
                        ArgType* ptr = static_cast<ArgType*>(instance->VoidPointerStorage[idx]);
                        // 这里替换为你需要的输出逻辑,比如Unreal的UE_LOG、屏幕打印
                        UE_LOG(LogTemp, Log, TEXT("值: %s"), *FString(std::to_string(*ptr)));
                        idx++;
                    }(),
                    ...
                );
            }, instance->MyTuple);
            break;
        }
    }
}

注意事项

  • 调试工具可以忽略小的内存泄漏问题,如果需要完善可以加UNWATCHMYOBJECT宏,从g_watcherReg和Instaces中移除对应的实例,避免野指针。
  • 同一对象不要多次调用WATCHMYOBJECT,否则后面的注册会覆盖前面的回调。

内容的提问来源于stack exchange,提问作者João Pedro Bezerra Socas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 01:27:03