C++简化函数模板消重时的decltype相关编译问题求解
成员函数包装类模板精简方案
问题背景
初始实现的类成员函数包装类存在实例化语法冗余的问题,需要重复书写成员函数指针的类型与对应值,尝试编写别名模板精简时触发编译错误:
'Retriever': is not a valid type for non-type template parameter 'retrieverFunc'
初始冗余实现代码
#include <cstdio> #include <type_traits> #include <utility> using namespace std; class testclass { public: double get() { return d_; } void set(double d) { d_ = d; } double d_ = 0.0; }; template<typename Retriever, Retriever retrieverFunc, typename Updater, Updater updaterFunc, typename OwningClass> struct Wrapper { Wrapper(Retriever retriever, Updater updater, OwningClass* owner) : retriever_(retriever), updater_(updater), containingClass_(owner) {} using GetterReturnType = std::invoke_result_t<decltype(retrieverFunc), OwningClass>; GetterReturnType Get() { return (containingClass_->*retriever_)(); } template<typename...Args> using SetterReturnType = std::invoke_result_t<decltype(updaterFunc), OwningClass, Args...>; template<typename...Args> SetterReturnType<Args...> Set(Args&&... args) { return (containingClass_->*updater_)((forward<Args>(args))...); } Retriever retriever_; Updater updater_; OwningClass* containingClass_; }; int main() { testclass tc; // 模板参数重复书写类型和函数指针,非常繁琐 Wrapper<decltype(&testclass::get), &testclass::get, decltype(&testclass::set), &testclass::set, testclass> pp(&testclass::get, &testclass::set, &tc); pp.Get(); pp.Set(1.0); }
错误的别名模板写法
// 存在语法和逻辑错误 template<typename Retriever, Wrapper Updater, typename OwningClass> using WrapperSimplified = Wrapper<decltype(Retriever), Retriever, decltype(Updater), Updater, OwningClass>;
错误原因
- 用
typename声明的是类型模板形参,但传入的&testclass::get是成员函数指针(非类型值),形参类型和实参不匹配 - 非类型模板参数在C++17前必须显式指定类型,无法直接从传入的值推导
- 别名模板中
Wrapper Updater是语法错误,无意义地给Updater参数加了Wrapper前缀
修复方案
根据使用的C++标准版本,二选一即可:
方案1:C++17及以上版本,用auto非类型模板参数
C++17支持用auto声明非类型模板参数,自动推导参数类型,无需重复书写类型:
- 编写正确的别名模板
template<auto retrieverFunc, auto updaterFunc, typename OwningClass> using WrapperSimplified = Wrapper< decltype(retrieverFunc), retrieverFunc, decltype(updaterFunc), updaterFunc, OwningClass >;
- 可选优化:因为模板参数已经固定了两个成员函数指针,不需要在构造函数重复传入,可以修改Wrapper的构造函数进一步精简代码
template<typename Retriever, Retriever retrieverFunc, typename Updater, Updater updaterFunc, typename OwningClass> struct Wrapper { // 构造函数仅需传入宿主对象指针 Wrapper(OwningClass* owner) : containingClass_(owner) {} using GetterReturnType = std::invoke_result_t<decltype(retrieverFunc), OwningClass>; GetterReturnType Get() { return (containingClass_->*retrieverFunc)(); } template<typename...Args> using SetterReturnType = std::invoke_result_t<decltype(updaterFunc), OwningClass, Args...>; template<typename...Args> SetterReturnType<Args...> Set(Args&&... args) { return (containingClass_->*updaterFunc)(std::forward<Args>(args)...); } OwningClass* containingClass_; };
- 实例化代码非常简洁,无冗余
int main() { testclass tc; WrapperSimplified<&testclass::get, &testclass::set, testclass> pp(&tc); pp.Get(); pp.Set(1.0); return 0; }
方案2:C11/C14版本,用工厂函数自动推导
低版本C++没有auto非类型模板参数,可以通过工厂函数的实参推导自动获取类型,避免手动写冗长的模板参数:
template<typename Retriever, typename Updater, typename OwningClass> auto MakeWrapper(Retriever r, Updater u, OwningClass* owner) { return Wrapper<Retriever, r, Updater, u, OwningClass>(r, u, owner); }
使用时无需手动填写模板参数:
int main() { testclass tc; auto pp = MakeWrapper(&testclass::get, &testclass::set, &tc); pp.Get(); pp.Set(1.0); return 0; }
内容的提问来源于stack exchange,提问作者Wad
相关产品推荐
相关产品推荐

