Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble with inheritance of operator= in C++

I'm having trouble with the inheritance of operator=. Why doesn't this code work, and what is the best way to fix it?

#include <iostream>

class A
{
public:
    A & operator=(const A & a)
    {
        x = a.x;
        return *this;
    }

    bool operator==(const A & a)
    {
        return x == a.x;
    }

    virtual int get() = 0; // Abstract

protected:
    int x;
};

class B : public A
{
public:
    B(int x)
    {
        this->x = x;
    }

    int get()
    {
        return x;
    }
};

class C : public A
{
public:
    C(int x)
    {
        this->x = x;
    }

    int get()
    {
        return x;
    }
};

int main()
{
    B b(3);
    C c(7);
    printf("B: %d C: %d B==C: %d\n", b.get(), c.get(), b==c);

    b = c; // compile error
    // error: no match for 'operator= in 'b = c'
    // note: candidates are B& B::operator=(const B&)

    printf("B: %d C: %d B==C: %d\n", b.get(), c.get(), b==c);
    return 0;
}
like image 330
André Gräsman Avatar asked Oct 07 '10 13:10

André Gräsman


People also ask

Can operator be inherited?

All overloaded operators except assignment (operator=) are inherited by derived classes. The first argument for member-function overloaded operators is always of the class type of the object for which the operator is invoked (the class in which the operator is declared, or a class derived from that class).

Which operator is not inherited by derived class?

No operator= can be declared as a nonmember function. It is not inherited by derived classes.

What is operator inheritance?

operator= is inherit from based class as I quote from 98 c++ standard "Operator functions are inherited in the same manner as other base class functions." It has just been hidden by implicitly created operator= for Derived class.

Does derived class inherit assignment operator?

In C++, like other functions, assignment operator function is inherited in derived class.


4 Answers

If you do not declare copy-assignment operator in a class, the compiler will declare one for you implicitly. The implicitly declared copy-assignment operator will hide any inherited assignment operators (read about "name hiding" in C++), meaning that any inherited assignment operators will become "invisible" to the unqualified name lookup process (which is what happens when you do b = c), unless you take specific steps to "unhide" them.

In your case, class B has no explicitly declared copy-assignment operator. Which mean that the compiler will declare

B& B::operator =(const B&)

implicitly. It will hide the operator inherited from A. The line

b = c;

does not compile, because, the only candidate here is the above implicitly declared B::operator = (the compiler told you about that already); all other candidates are hidden. And since c is not convertible to B&, the above assignment does not compile.

If you want your code to compile, you can use using-declaration to unhide the inherited A::operator = by adding

using A::operator =;

to the definition of class B. The code will now compile, although it won't be a good style. You have to keep in mind that in this case the b = c assignment will invoke A::operator =, which assigns only the A portions of the objects involved. (But apparently that is your intent.)

Alternatively, in cases like this you can always work around name hiding by using a qualified version of the name

b.A::operator =(c);
like image 83
AnT Avatar answered Oct 26 '22 00:10

AnT


What's happening is that the default operator = that the compiler generates for any class that doesn't have one is hiding the base class' operator =. In this particular case, the compiler is generating const B &B::operator =(const B &) for you behind the scenes. Your assignment matches this operator and completely ignores the one you declared in class A. Since a C& cannot be converted to a B& the compiler generates the error you see.

You want this to happen, even though it seems vexing right now. It prevents code like you've written from working. You don't want code like that to work because it allows unrelated types (B and C have a common ancestor, but the only important relationships in inheritance are parent->child->grandchild relationships, not sibling relationships) to be assigned to one another.

Think about it from an ISA perspective. Should a Car be allowed to be assigned to a Boat just because they're both Vehicles?

In order make something like this work you should use the Envelope/Letter pattern. The envelope (aka handle) is a specialized class who's only job it is is to hold an instance of some class that's derived from a particular base class (the letter). The handle forwards all operations but assignment to the contained object. For assignment it simply replaces the instance of the internal object with a copy-constructed (using a 'clone' method (aka virtual constructor)) copy of the assigned from object.

like image 34
Omnifarious Avatar answered Oct 26 '22 00:10

Omnifarious


You cannot assign across the hierarchy like this - B and C are different subclasses of A. You can assign a B to a B or a C to a C but not a C to a B or vice versa.

You probably want to implement operator= in B and C, delegating the A part of the assignment to A::operator= before you try this though. Otherwise the B- and C-specific parts of those classes will get lost in the assignment.

like image 33
Steve Townsend Avatar answered Oct 26 '22 00:10

Steve Townsend


Normally, operator= is defined in B as

B& operator=(B const &);

Since B is not an unambiguous and accessible base of 'C', the conversion from C to B is not allowed by the compiler.

If you really want to have a 'C' be assigned to 'B', 'B' should support an appropriate assignment operator as

B& operator=(C const &);
like image 37
Chubsdad Avatar answered Oct 25 '22 22:10

Chubsdad