如何让编译器自动推导基类模板方法的模板实参?
如何让基类通用方法自动推导派生类的Result类型?
我有多个继承自Base的类,每个类内部都定义了Result结构体,希望在基类中实现一个通用的get_result方法,调用时能自动返回对应派生类的Result结构体。目前代码能运行,但调用get_result时必须手动指定模板参数,非常麻烦。
附加限制
- 当前使用C++17,同时想了解更高版本的解决方案
Base类自身有属性和多态逻辑,无法完全模板化
原代码示例
#include <string> using namespace std; class Base { public: // 这里有一些虚函数,导致整个类无法模板化,所有派生类都依赖这些逻辑 template<typename T> typename T::Result get_result() const { // static_assert(std::is_base_of_v<Base, T>, "Derived not derived from BaseClass"); return T::Deserialize(); } }; class DerivedA : public Base { public: struct Result { int i = 0; }; static Result Deserialize(/*实际代码中有流参数*/) { Result r; r.i = 42; return r; } }; class DerivedB : public Base { public: struct Result { string s; }; static Result Deserialize(/*实际代码中有流参数*/) { Result r; r.s = "Yatta"; return r; } }; int main() { { DerivedA a; const auto res = a.get_result<DerivedA>(); // 必须手动指定类型 } { DerivedB b; const auto res = b.get_result<decltype(b)>(); // 用decltype也不够简洁 } return 0; }
可行解决方案
方案1:C++17可用的自由函数(最优方案)
直接使用非成员模板函数,利用函数参数的类型推导自动获取派生类类型,完全不需要修改原有基类结构:
#include <string> #include <type_traits> using namespace std; // 原Base、DerivedA、DerivedB代码保持不变 template <class T> typename T::Result get_result(const T& instance) { static_assert(is_base_of_v<Base, T>, "T必须继承自Base"); return T::Deserialize(); } int main() { DerivedA a; const auto res_a = get_result(a); // 自动推导T为DerivedA DerivedB b; const auto res_b = get_result(b); // 自动推导T为DerivedB return 0; }
这个方案简单直接,符合C++17的要求,同时保留了Base类的多态特性。
方案2:C++20及以上:CRTP实现成员方法自动推导
如果允许修改派生类的继承结构,可以用CRTP(奇异递归模板模式)实现成员方法的自动推导:
#include <string> #include <type_traits> using namespace std; class Base { public: // 原有的虚函数和属性保持不变 }; // CRTP辅助基类,继承自Base template <typename Derived> class BaseCRTP : public Base { public: decltype(auto) get_result() const { return Derived::Deserialize(); } }; // 派生类改为继承BaseCRTP class DerivedA : public BaseCRTP<DerivedA> { public: struct Result { int i = 0; }; static Result Deserialize() { return {42}; } }; class DerivedB : public BaseCRTP<DerivedB> { public: struct Result { string s; }; static Result Deserialize() { return {"Yatta"}; } }; int main() { DerivedA a; auto res_a = a.get_result(); // 自动返回DerivedA::Result DerivedB b; auto res_b = b.get_result(); // 自动返回DerivedB::Result // 依然支持多态:Base指针可以指向派生类对象 Base* ptr = &a; // 注意:多态场景下无法通过Base*调用get_result,因为它不是Base的虚函数 return 0; }
这个方案让get_result成为成员方法,但需要调整派生类的继承关系,且多态场景下无法通过基类指针调用该方法。
方案3:C++17下的成员方法优化(不推荐)
如果坚持要用成员方法,可以通过宏给每个派生类添加一个包装方法,实现自动推导,但代码优雅性较差:
#include <string> #include <type_traits> using namespace std; class Base { public: template<typename T> typename T::Result get_result() const { static_assert(is_base_of_v<Base, T>, "T必须继承自Base"); return T::Deserialize(); } }; // 定义宏自动生成包装方法 #define ADD_GET_RESULT() \ auto get_result() const { \ return Base::get_result<std::decay_t<decltype(*this)>>(); \ } class DerivedA : public Base { public: struct Result { int i = 0; }; static Result Deserialize() { return {42}; } ADD_GET_RESULT() }; class DerivedB : public Base { public: struct Result { string s; }; static Result Deserialize() { return {"Yatta"}; } ADD_GET_RESULT() }; int main() { DerivedA a; auto res_a = a.get_result(); // 自动推导 DerivedB b; auto res_b = b.get_result(); // 自动推导 return 0; }
这种方式依赖宏,不利于代码维护,仅作为备选方案。
内容的提问来源于stack exchange,提问作者ceph
相关产品推荐
相关产品推荐

