views:

298

answers:

8

I have code like this:

class Base
{
public:
  void operator = (const Base& base_)
  {
  }
};

class Child : public Base
{
public:

};

void func()
{
  const Base base;
  Child child;
  child = base;
}

My question is: since Child derives from Base (hence it should inherit Base's operator= ), how come when the statement

child = base;

is executed, I get a compiler error like this:

>.\main.cpp(78) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const Base' (or there is no acceptable conversion)
1>        .\main.cpp(69): could be 'Child &Child::operator =(const Child &)'
1>        while trying to match the argument list '(Child, const Base)'

The behavior that I want is for the Child class to recognize that it's being assigned a Base class, and just "automatically" call the its parent's operator=.

Once I added this code to the Child class

void operator = (const Base& base_)
{
  Base::operator=(base_);
}

then everything compiled fine. Though I dont think this would be good because if I have like 5 different classes that inherit from Base, then I have to repeat the same code in every single derived class.

NOTE: My intention for copying the Base to Child is to simply copying the members that are common to both Base and Child (which would be all the members of Base). Even after reading all of the answers below, I really don't see why C++ doesn't allow one to do this, especially if there's an explicit operator= defined in the Base class.

A: 

The problem is that you can't assign a constant value to a non-constant variable. If you could, you could potentially modify the constant value by modifying the non-constant variable.

Is it necessary that base is const? I ask because you're really running into two problems: const correctness, and inheritence/polymorphism.

EDIT: I'm second guessing myself now regarding my first statement. Any clarification?

Shakedown
I thought that at first, but assignment is to child which is not const, not base (which is const).
Michael Anderson
Right...isn't that the problem? What will happen when a non-const method is called on child then?
Shakedown
Michael Anderson
+2  A: 

Because a base isn't a child. Consider:

class Child : public Base 
{ 
public: 
   SomeFunc();
};

void func() 
{ 
  const Base base; 
  Child child; 
  child = base; 
  child.SomeFunc();     // whatcha gonna call?
} 

UPDATE: TO answer the question in the comment, child.SomeFunc(); is perfectly legal -- it's only a problem if th value of child isn't really a Child object. The compiler cannot allow child = base; because it would create a situation where the legal call fails.

James Curran
This is a good point, but I think he is asking why he is getting the compiler error.
J.W.
+1  A: 

When you don't declare copy assignment explicitly, the C++ compiler will create copy assignment for you . In your case, it would be

class Child : public Base
{
public:
Child& operator=(const Child& rhs) { ... }

};

And that's why you get this compiler error. Also, it makes more sense to return a reference to itself when you provide a copy assignment because it will allow you to chain operator together.

It's not a good idea to declare a copy assignment like this.

void operator = (const Base& base_)
{
  Base::operator=(base_);
}

I don't see why you only want to copy the base class portion when you do a copy assignment.

J.W.
-1 This has nothing to do with the error.
Peter Alexander
well, the compile error clearly says it. I don't get your comment. He is asking why he is getting the compile error.
J.W.
Overloading the assignment operator, while being the reason the compiler reports, is totally orthogonal to the real issue here, which is that the poster hasn't understood the idea of polymorphism. Your answer is like telling people to use `const_cast` to get around const-correctness. It might solve the error, but it's not the real issue.
Peter Alexander
I read the question again , here is what he has asked :My question is: since Child derives from Base (hence it should inherit Base's operator= ), how come when the statementchild = base;is executed, I get a compiler error like this: bla, bla...Maybe, I completed missed his question..
J.W.
+5  A: 

You can't assign Base to Child, because Child might not be base. To use the typical animal example, what you have here is:

const Animal animal;
Dog dog;
dog = animal;

But animal might be a cat, and you certainly can't assign a cat to a dog.

That being said, you couldn't do it the other way round either:

const Dog dog;
Animal animal;
animal = dog;

A dog certainly is an animal, but you have a size issue here. animal takes up sizeof(Animal) space on the stack, but dog takes up sizeof(Dog) space, and those two amounts may be different.

When working with polymorphism in C++, you need to be working with either references or pointers. For example, the following is fine:

Dog* const dog;
Animal* animal;
animal = dog;

Here, the size of a pointer is the same regardless of what it points to, so the assignment is possible. Note that you still need to maintain the correct hierarchy conversions. Even when using a pointer, you can't assign a Base to a Child, because of the reason I explained earlier: A Base may be something entirely different from the Child.

Peter Alexander
@Poita_ : thank you for a great answer. However, I have a question. When we do dog = animal, how come it cant just copy only the Animal part to the dog? Do you see what I mean? e.g. say Animal class has a private member called int numLegs; Then, when we do dog = animal, C++ should just copy Animal::numLegs and assign it to Dog::numLegs.
ShaChris23
That is, it's copying the common parts between the 2 classes.
ShaChris23
I see what you mean, but what would happen to the parts of the dog that aren't specified by the Animal? You could possibly design a language to work that way, but the fundamental problem here is that it just does not make any sense.
Peter Alexander
@Poita_ thank you. You are right. =)
ShaChris23
+8  A: 

The standard provides the reason for your specific question in 12.8/10 "Copying class objects" (emphasis added):

Because a copy assignment operator is implicitly declared for a class if not declared by the user, a base class copy assignment operator is always hidden by the copy assignment operator of a derived class (13.5.3).

So since there's an implicitly declared operator=(const Child&) when the compiler is performing the name lookup/overload resolution for a Child::operator=(), the Base class's function signature is never even considered (it's hidden).

Michael Burr
Yeah and as I've noted in my post you can use "using" to pull that operator= back into the Childs scope. (Which I find scary)
Michael Anderson
I feel like hidden is poor wording for the standard.void Base::operator = (const Base andvoid Chile::operator = (const Base are intrinsically different as one assigns a Base to a Base and the other to a Child. Seems silly to say its hidden when they are different functions. It should be hidden because its not logical for that function to exist in the Child class.
0xC0DEFACE
The name hiding for `operator=()` is handled just like for any other function name that might be hidden with one major difference: if you don't declare `operator=()` for a class you get an implicit declaration so the name hiding is there whether you want it or not and if you would have liked the name to not be hidden there's not much you can do about it except to define an `operator=()` in the derived class that forwards to the base class implementation. That's why the standard calls out the name hiding behavior specifically for `operator=()`.
Michael Burr
0xC0DEFACE
@Michael Anderson - I forgot about using `using` to pull the name in from the base class.
Michael Burr
@0xC0DEFACE: the rule for function name hiding in C++ is if the name is declared in the derived class, all overloads in base classes for that name are hidden unless explicitly pulled in. There's a good discussion here: http://stackoverflow.com/questions/1628768/why-does-an-overridden-function-in-the-derived-class-hide-other-overloads-of-the as well as in Herb Sutter's "Exceptional C++" Item 34 - "Name Hiding and the Interface Principal - Part 4"
Michael Burr
Great link! I didn't realise that name hiding actually hid all functions of that name regardless of overloads!
0xC0DEFACE
Not many people do realise, that's always a source of confusion...
Matthieu M.
+1  A: 

The answer is scarier than I thought. You can do this. The standard evens has a little note about it in section 7.3.3 The main problem is that the operator= that gets defined by default in Child hides the operator= from Base so you need to import it back into the class scope using "using".

This compiles OK for me. However I find this idea very scary, and potentially fraught with undefined behaviour if stuff in Child needs to get initialised.

class Base
{
public:
  void operator = (const Base& base_)
  {
  }
};

class Child : public Base
{
public:
using Base::operator=;

};

int main()
{
  const Base base = Base();
  Child child;
  child = base;
}

EDIT: Made the base const again and avoid "uninitialised const" error.

Michael Anderson
A: 

The code below is the behavior I wanted since the beginning, and it compiles,

class Base
{
public:
  void operator = ( const Base& base_)
  {
  }
};

class Child : public Base
{
};

void func()
{
  const Base base;

  Child child;

  child.Base::operator=(base);
}

I never knew that you can explicitly call something like:

  child.Base::operator=(base);

Anyway, I learned a lot. Thank you for all the people that posted answers here.

ShaChris23
Note: I learned the syntax above from reading Sutter's http://www.gotw.ca/publications/mill08.htm (motivated by @Michael Burr's comment)
ShaChris23
+2  A: 

This isn't actually an answer, but the how-to question has been answered. This is the why-not.

Classes should mean something. There should be useful things you can say about any well-formed object in the class ("class invariants"), because that way you can reason about programs on the basis of how a class works. If a class is just a collection of data members, you can't do that, and have to reason about the program on the basis of each individual data element.

The proposed assignment operator will change every data member in the base class, but won't touch the ones defined in the child class. This means one of two things.

If a Child object is well-formed after another Base object is dumped into its Base component, it means that the additional data members have no real connection with the the Base data members. In that case, Child isn't really a subtype of Base, and public inheritance is the wrong relation. Child isn't in a "is-a" relation to Base, but rather a "has-a" relation. This is usually expressed by composition (Child has a Base data member), and can also be expressed by private inheritance.

If a Child object is not well-formed after another Base object is dumped into its Base component, the proposed assignment operator is a really fast and convenient method to mess up a variable.

For an example, let's have a Mammal base class and two child classes, Cetacean and Bat. Animal has things like size and weight, and the child classes have fields related to what they eat and how they move. We assign a Cetacean to a Mammal, which is reasonable (all Cetacean-specific fields are just dropped), and now we assign that Mammal to a Bat. Suddenly, we've got an twenty-ton bat.

With a well-designed set of classes, the only way I can conceive of this assignment operator even being thought of is if the child classes have no additional data members, only additional behavior, and even then it's suspect.

Therefore, my only advice is to redo your class design. Many people, particularly those familiar with Java and other languages that tend to deep inheritance hierarchies, use inheritance far too much in C++. C++ generally works best with a large number of base classes and a relatively shallow hierarchy.

David Thornley