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

完美转发变参模板至成员函数:观察者模式编译问题求助

解决ObserverList中Fire函数模板参数推导歧义的问题

这个问题的核心是模板参数推导的歧义:你的Fire函数模板让TArgs同时从两个来源推导——成员函数指针的参数列表,以及传入的实参类型。当成员函数的参数是传值类型(比如const char*)时,实参是左值,这时候TArgs的推导结果会出现矛盾,导致MSVC报错。

为什么会出错?

举个例子,当你调用Fire(&WidgetObserver::TextChanged, *this, text)时:

  • 从成员函数指针void(Widget&, const char*)推导,TArgs应该是Widget&, const char*
  • 从实参*this(左值Widget&)和text(左值const char*)推导,TArgs&&...会被折叠为Widget&, const char*&

编译器无法确定TArgs到底是const char*还是const char*&,所以抛出了模板参数不明确的错误。

解决方案1:拆分模板参数(兼容C++11及以上)

把函数指针的参数类型和实参类型拆分成两组独立的模板参数,让编译器分别推导,避免歧义:

template <typename TObserver>
class ObserverList {
    std::vector<TObserver*> observer_list;
public:
    void AddObserver(TObserver& observer) { 
        observer_list.push_back(&observer); 
    }

    // 拆分FuncArgs(从函数指针推导)和CallArgs(从实参推导)
    template <typename... FuncArgs, typename... CallArgs>
    void Fire(void(TObserver::*func)(FuncArgs...), CallArgs&&... args) {
        for (TObserver* ob : observer_list) {
            // 实参会自动转换为FuncArgs的类型(比如左值转传值)
            (ob->*func)(std::forward<CallArgs>(args)...);
        }
    }
};

这样修改后,FuncArgs专门从成员函数指针推导,CallArgs从实参推导,编译器会自动处理类型转换(比如把左值const char*拷贝成传值的const char*),完美解决推导歧义。

解决方案2:使用std::invoke(C++17及以上更优雅)

如果你的项目支持C++17,可以用std::invoke简化代码,它会自动处理成员函数的调用逻辑,完全避免模板参数推导的问题:

#include <functional> // 需要包含这个头文件

template <typename TObserver>
class ObserverList {
    std::vector<TObserver*> observer_list;
public:
    void AddObserver(TObserver& observer) { 
        observer_list.push_back(&observer); 
    }

    template <typename Func, typename... Args>
    void Fire(Func func, Args&&... args) {
        for (TObserver* ob : observer_list) {
            // std::invoke自动处理成员函数的调用,无需手动解引用指针
            std::invoke(func, *ob, std::forward<Args>(args)...);
        }
    }
};

std::invoke的优势在于它不仅支持成员函数指针,还支持函数对象、Lambda等,让你的ObserverList更灵活。

验证修改后的代码

现在你原来的OnTextChanged调用:

virtual void OnTextChanged(const char* text) { 
    observers.Fire(&WidgetObserver::TextChanged, *this, text); 
}

可以正常编译,无论是传值参数(const char*)还是引用参数(Widget&、const Rect&)都能正确处理。

内容的提问来源于stack exchange,提问作者Rikus Honey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:05:02