模板基类中转换与赋值运算符的编译错误排查
问题背景
我需要封装int、double等基本类型,构建AdvancedInt、AdvancedDouble这类类,通过重载转换运算符和赋值运算符,为底层值的访问添加额外逻辑。由于无法修改原有代码的访问方式,只能采用运算符重载方案,但模板基类中定义的赋值运算符导致编译错误。
复现代码
#include <string> class Base { public: Base() = delete; Base(std::string s) : name(s) { //Some extra logic } ~Base() { //More custom logic } virtual std::string get_name() const final { //Final on purpose return name; } private: const std::string name; }; template <typename T, class C> class Serializable :public Base { //Poor man's interface (but not quite) public: Serializable() = delete; Serializable(std::string name) : Base(name) { } operator T() const { //Some extra 'getter' logic return value; } C& operator=(const T& val) { //Some extra 'setter' logic value = val; return (C&)*this; } virtual void serialize() = 0; //This has to be pure virtual private: T value; }; class AdvancedInt final : public Serializable<int, AdvancedInt> { //This is the actual complete class public: AdvancedInt(std::string name) : Serializable(name) { //Nothing here, but needed for the special constructor logic from AbstractBase } void serialize() override { //Some logic here } }; int main() { AdvancedInt adv{"foo"}; int calc = adv; calc += 7; adv = calc; //error C2679 (msvc) | binary '=': no operator found which takes a right-hand operand of type 'int' (or there is no acceptable conversion) return 0; }
错误原因
编译器会为AdvancedInt自动生成拷贝赋值运算符:AdvancedInt& operator=(const AdvancedInt&)。根据C++名字查找规则,派生类中的同名函数(这里operator=是名字)会隐藏基类中的同名函数。因此当执行adv = calc时,编译器只会查找AdvancedInt自身的赋值运算符,而自动生成的版本仅接受const AdvancedInt&类型,无法匹配传入的int,导致编译错误。
解决方案
方案1:引入基类赋值运算符到派生类作用域
在AdvancedInt的public区域添加using声明,将基类的赋值运算符引入派生类的名字空间,这样编译器就能找到接受int的版本:
class AdvancedInt final : public Serializable<int, AdvancedInt> { public: AdvancedInt(std::string name) : Serializable(name) {} using Serializable<int, AdvancedInt>::operator=; // 引入基类赋值运算符 void serialize() override { //Some logic here } };
方案2:派生类显式实现对应赋值运算符
在AdvancedInt中直接实现接受int的赋值运算符,调用基类的实现逻辑:
class AdvancedInt final : public Serializable<int, AdvancedInt> { public: AdvancedInt(std::string name) : Serializable(name) {} AdvancedInt& operator=(const int& val) { Serializable<int, AdvancedInt>::operator=(val); return *this; } void serialize() override { //Some logic here } };
额外问题解答
1. 将AdvancedInt传入自身基类模板的合理性
这种写法是**奇怪递归模板模式(CRTP)**的典型应用,用于让基类知晓派生类类型,从而返回派生类的引用,是完全合理的。由于AdvancedInt是final类,且继承关系为public,基类中return (C&)*this的类型转换是安全的。
2. 声明纯虚赋值运算符的可行性
将基类的C& operator=(const T& val)声明为纯虚函数确实能编译,但这会强制所有派生类必须实现该赋值运算符,导致代码重复。相比之下,用CRTP在基类实现逻辑,再通过using引入派生类的方案更简洁,避免重复代码。
内容的提问来源于stack exchange,提问作者NRUB

