CRTP场景下派生类调用基类函数出现类型转换错误
刚接触CRTP和复杂模板代码,遇到一个编译错误,不确定问题是否可解决。
定义了如下模板基类,用于接收类型列表并生成元组存储组件,可通过x.component<0>()的方式访问组件:
template<typename ...T> class base_type_components { public: template<std::size_t i> std::tuple_element_t<i, std::tuple<T...>> component() { return std::get<i>(_components); } constexpr std::size_t num_components() const { return sizeof...(T); } private: std::tuple<T...> _components; };
派生类也是模板类,模板参数L用于其他无关逻辑:
template<std::size_t L = 199> class type_derived_st : public base_type_components<std::string> { public: std::string value() const { return this->template component<0>(); } ... };
错误情况
实例化type_derived_st<5>并调用value()函数时,出现编译错误:
error: cannot convert
'const type_derived_st<5>*'to'base_type_components<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >>*'
直接调用x.component<0>()完全正常:
type_derived_st<5> x; std::string s = x.component<0>(); // 编译通过
但调用x.value()就报错:
type_derived_st<5> x; std::string s = x.value(); // 编译错误!
最小可复现示例(MRE)
#include <cstddef> #include <string> #include <tuple> template<typename ...T> class base_type_components { public: template<std::size_t i> std::tuple_element_t<i, std::tuple<T...>> component() { return std::get<i>(_components); } constexpr std::size_t num_components() const { return sizeof...(T); } private: std::tuple<T...> _components; }; template<std::size_t L = 199> class type_derived_st : public base_type_components<std::string> { public: type_derived_st() = default; ~type_derived_st() = default; std::string value() const { return this->template component<0>(); } }; int main() { type_derived_st<5> x; std::string y = x.value(); return 0; }
问题原因与解决方案
原因
value()是const成员函数,函数内部的this指针是const type_derived_st<L>*类型,但基类的component()成员函数是非const的。要调用非const成员函数,需要将const指针转换为非const的基类指针,这违反了const正确性,因此编译器报错。
而直接调用x.component<0>()时,x是非const对象,调用的是非const版本的component(),不存在类型转换问题,所以编译通过。
解决方案
给基类的component()添加const重载,保证const正确性:
template<typename ...T> class base_type_components { public: // 非const版本 template<std::size_t i> std::tuple_element_t<i, std::tuple<T...>> component() { return std::get<i>(_components); } // 添加const重载 template<std::size_t i> std::tuple_element_t<i, std::tuple<T...>> component() const { return std::get<i>(_components); } constexpr std::size_t num_components() const { return sizeof...(T); } private: std::tuple<T...> _components; };
如果想避免不必要的拷贝,也可以让const版本返回const引用:
template<std::size_t i> const std::tuple_element_t<i, std::tuple<T...>>& component() const { return std::get<i>(_components); }
修改后,value()作为const成员函数会调用const版本的component(),类型转换合法,编译即可通过。
内容的提问来源于stack exchange,提问作者agregate

