完美转发变参模板至成员函数:观察者模式编译问题求助
解决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
相关产品推荐
相关产品推荐

