为何在CRTP基类函数调用中添加引用可消除编译错误?
嘿,这个CRTP里的引用坑我之前踩过!咱们先把问题拆明白,再给你捋清楚为啥会出现这种“同类型成员有的报错有的不报错”的诡异情况。
先还原你的问题场景
我猜你大概是这么写的(两种写法对比):
写法1(无报错,返回值非引用)
template <typename Derived> class MatrixBase { public: // 返回T而非T&,编译能过 T get(int i, int j) const { return static_cast<const Derived*>(this)->data[i][j]; } }; template <typename T, int Rows, int Cols> class Matrix : public MatrixBase<Matrix<T, Rows, Cols>> { friend class MatrixBase<Matrix<T, Rows, Cols>>; T data[Rows][Cols]; };
写法2(报错,返回值为引用)
template <typename Derived> class MatrixBase { public: // 返回T&,编译直接报错 T& get(int i, int j) { return static_cast<Derived*>(this)->data[i][j]; } }; // Matrix类和上面一样
为啥会这样?核心原因:CRTP基类里的派生类是「不完全类型」
当编译器处理MatrixBase<Matrix<T,Rows,Cols>>的时候,Matrix<T,Rows,Cols>这个派生类还没被完全定义呢——它的类体还在基类之后才会被解析。这时候,基类里直接用T是有问题的:
- 对于非引用的返回值:编译器会触发「模板延迟实例化」,也就是不会在解析基类模板的时候就去查
T的定义,而是等到你实际调用get()函数的时候,Matrix已经是完全类型了,这时候就能找到T,所以碰巧能编译通过。但这其实是编译器的宽松处理,严格来说不符合C++标准。 - 对于引用类型的返回值:很多编译器会在解析基类模板的时候就尝试解析
T&的类型,而此时Matrix还是不完全类型,编译器不知道T是什么,自然就报错了。
怎么解决?两种靠谱的方案
方案1:让基类通过派生类的类型别名获取T(CRTP惯用手法)
在派生类里定义一个公开的类型别名,基类用typename Derived::value_type来指代T(必须加typename,因为这是依赖于模板参数的类型):
template <typename Derived> class MatrixBase { public: // 用typename声明这是依赖类型 using ValueType = typename Derived::value_type; // 现在返回ValueType&就没问题了 ValueType& get(int i, int j) { return static_cast<Derived*>(this)->data[i][j]; } const ValueType& get(int i, int j) const { return static_cast<const Derived*>(this)->data[i][j]; } }; template <typename T, int Rows, int Cols> class Matrix : public MatrixBase<Matrix<T, Rows, Cols>> { public: // 定义类型别名,给基类用 using value_type = T; friend class MatrixBase<Matrix<T, Rows, Cols>>; private: T data[Rows][Cols]; };
方案2:给基类模板额外传递T参数
如果觉得类型别名太麻烦,直接把T作为基类的第二个模板参数,这样基类不需要依赖派生类的不完全类型:
// 基类多一个T模板参数 template <typename Derived, typename T> class MatrixBase { public: T& get(int i, int j) { return static_cast<Derived*>(this)->data[i][j]; } const T& get(int i, int j) const { return static_cast<const Derived*>(this)->data[i][j]; } }; template <typename T, int Rows, int Cols> class Matrix : public MatrixBase<Matrix<T, Rows, Cols>, T> { friend class MatrixBase<Matrix<T, Rows, Cols>, T>; private: T data[Rows][Cols]; };
最后补一句
那些没把T设为引用的成员没报错,纯粹是编译器的“网开一面”,本质上这种写法是不严谨的。上面两种方案都是符合C++标准的,随便选哪种都能解决你的问题~
内容的提问来源于stack exchange,提问作者Steffan
相关产品推荐
相关产品推荐

