3

I am currently writing a program that models various types of numbers and manages them polymorphically in a Set object (already written and tested). My inheritance relationship is this: Multinumber (all virtual functions, virtual class) inherited by Pairs, Complex, Rational The subclasses all have mostly the same functions with different parameters.

The problem that I am running into is in functions like this:

Multinumber& Complex::operator=(const Multinumber &rhs)
{
   imag=rhs.imag;
   real=rhs.real;
   return *this;
}

Because I am treating this polymorphically, my return types and parameters all have to be of type Multinumber, in order to override the base class's parameters. However, I am having a terrible time getting this to compile. I am getting a boatload of errors along the lines of:

error: 'const class Multinumber' has no member named 'imag'

which is true. Multinumber does not have an imag attribute or function. But how can I get the compiler to realize that the Multinumber& rhs will always be Complex, or to treat it as such? Thank you for your help.

This is what my superclass looks like:

class Multinumber
{
public:
virtual Multinumber& operator+(Multinumber&);
virtual Multinumber& operator=(Multinumber&);
virtual bool operator==(Multinumber&);
virtual string object2string();
virtual Multinumber& makeobject(double, double)=0;
};
user229044
  • 232,980
  • 40
  • 330
  • 338
  • What do you mean by "in order to override the base class's parameters"? – Argote Nov 25 '10 at 20:44
  • I usually think that polymorphic objects do not need a copy-assignment operator (ie. it should be made private). At the very most, they should have a protected copy constructor and a public `clone` function, since you usually store them by pointers. I'd like to be shown a case where proper non trivial assignment semantics are necessary. If necessary, there is a great discussion here: http://stackoverflow.com/questions/669818/virtual-assignment-operator-c – Alexandre C. Nov 26 '10 at 09:48

5 Answers5

1

I think you'll have to cast. Try this:

Multinumber& Complex::operator=(const Multinumber &rhs){
    const Complex & _rhs = dynamic_cast<const Complex &>(rhs);
    imag=_rhs.imag;
    real=_rhs.real;
    return *this;
}
Steve Rowe
  • 19,411
  • 9
  • 51
  • 82
  • I'll remove the downvote if you don't use `reinterpret_cast`. Furthermore, casting is only one solution, and surely not the best one. – Alexandre C. Nov 25 '10 at 21:09
  • Good call. I was thinking dynamic cast, not reinterpret. – Steve Rowe Nov 25 '10 at 21:42
  • I'm having trouble with the syntax that you gave me here. I put in const Complex &rhs = dynamic_cast(rhs); and got the error: error: declaration of 'const Complex& rhs' shadows a parameter| –  Nov 25 '10 at 22:14
  • Okay, so you let the assignment operator throw if the cast fails. – Alexandre C. Nov 26 '10 at 09:41
1

A signature such as :

Multinumber& Complex::operator=(const Multinumber &rhs)

means that any kind of Multinumber may be assigned to a Complex. Is that really something you want ? You have two options here :

  • Allow it and verify if the dynamic type of the parameter is indeed Complex (for example through a dynamic_cast). What will you do if it's not ? You will probably end up throwing an exception.
  • Forbid it as a whole by changing the signature to Multinumber& Complex::operator=(const Complex &rhs) : trying to assign a Rational to a Complex will fail to compile.

In the end, you're the only one who can really decide what better fits your needs, but from my point of view compile time errors are preferable to runtime errors.

On a side note, I think you gave the answer by asking "how can I get the compiler to realize that the Multinumber& rhs will always be Complex" : make it a Complex and it will never be anything else.

EDIT Now that we see that operator= is virtual in Multinumber, it seems you are indeed forced to stick with the initial signature and verify the dynamic type of the parameter in Complex::operator= (see Steve answer for this).

icecrime
  • 74,451
  • 13
  • 99
  • 111
  • Is that still considered polymorphic, even if the function definitions are not identical? In my superclass I have a number of virtual functions, so I am concerned that they won't be able to find the appropriate dynamic bind if I use different function headers like that. –  Nov 25 '10 at 22:01
  • @Ross: if `operator=` is virtual in `Multinumber`, this won't do the trick. You'll probably have to cast to verify the type of the parameter in this case. – icecrime Nov 25 '10 at 22:04
  • I added my superclass header file to the question above –  Nov 25 '10 at 22:04
0

If you're SURE that Multinumber is always complex, you can simply cast your rhs to a Complex &.

However, that sounds like the wrong approach. Instead, why don't you simply write Complex& Complex::operator=(const Complex &rhs)? Your operator is not virtual, so there's no reason that it has to use the same types as your base class.

EboMike
  • 76,846
  • 14
  • 164
  • 167
0

The missing phrase here is covariant return types. In C++ an overridden method is allowed to return a derived (and therefore covariant) reference or pointer type. Hence Complex& Complex::operator=(const Multinumber &rhs) is a valid overriding method of Multinumber& Multinumber::operator=(const Multinumber&)

Unfortunately I think you also want to have covariance on the parameter type. That's not allowed by the C++ standard, IFAIK. At this point you should probably consider whether you really want this polymorphism. It means that you must have a means to convert between types. Do you wish to support conversion between Pair and Complex types? If so, Steve's dynamic_cast approach seems the way to go. Otherwise you should remove methods such as operator= from Multinumber's overrideable methods and allow only the meaningful methods to be declared where they belong in the derived types.

Update In response to further comments: Return type covariance only applies to references and pointers. Return by value doesn't work, since the storage requirements will differ for each type (unlike pointers). So Multinumber& operator+(Multinumber&) is a valid method that can be overridden, but it probably won't work as one would expect, since you can't return a reference to a newly created type, since it would be destroyed when the function completes. One way to get around this would be to replace that method with Multinumber& operator+=(const Multinumber&). For Complex you could then implement it as:

Complex& Complex::operator+=(const Multinumber &rhs){
    const Complex & _rhs = dynamic_cast<const Complex &>(rhs);
    imag+=_rhs.imag;
    real+=_rhs.real;
    return *this;
}

An alternative approach would be to deal completely with pointers, and make operator+ return a copy of a new pointer. But that's just plain awful, and I strongly recommend you keep away from such horrors - that's like C with structs before C++ came along. You could improve things with some form of polymorphic pimpl approach (note I haven't checked the following, it's just to give you an idea, and it certainly could be improved):

class Multinumber
{
public:
  virtual Multinumber* operator+(const Multinumber&);
  virtual Multinumber& operator=(const Multinumber&);
  virtual bool operator==(const Multinumber&) const;
  // etc.
};

class MultinumberOuter
{
  std::unique_ptr<Multinumber> impl_;
public:
  explicit MultinumberOuter(Multinumber* pimpl) : impl_(pimpl) {}

  MultinumberOuter operator+(const MultinumberOuter& src) const {
    return MultinumberOuter(impl_->operator+(*(src.impl_)));
  }

  MultinumberOuter& operator=(const MultinumberOuter& src) {
    impl_->operator=(*(src.impl_));
    return *this;
  }

  bool operator==(const MultinumberOuter& src) const {
    return impl_->operator==(*(src.impl_));
  }
  // etc.
};

But, before going down this road, have a long think about whether such complexity is justified. Perhaps the level of polymorphism you're after is not warranted by the problem you've been set.

beldaz
  • 4,299
  • 3
  • 43
  • 63
  • Polymorphism is the point of the assignment. I guess my professor must have been going for casting. –  Nov 26 '10 at 01:14
  • 1
    @Ross: Thought so. Had to be an assignment, given what you're trying to do. I'd downvote your professor for giving you a poor case study. Don't forget to accept Steve's answer since it looks to be the one you're after. – beldaz Nov 26 '10 at 01:32
  • Question about covariant return types: Clearly I have to return by value, but in my hierarchy the base class is abstract, so its functions return a reference. If the base function is Multinumber& operator+(Multinumber&) and the derived function is Complex operator+(Multinumber&) are those considered the same functino? Because if not I'm in a little bit of trouble –  Nov 26 '10 at 16:00
  • @Ross: No, you can't covariantly return by value, and you also can't override a method that returns by reference with a new method that returns by value. I've expanded on my answer to give you some options. – beldaz Nov 27 '10 at 03:09
0

Give up. It can't be done. @beldaz is right that covariant arguments aren't allowed in C++ but misses the real point: it wouldn't help even if they were typesafe.

See my answer in:

C++ Abstract class can't have a method with a parameter of that class

Community
  • 1
  • 1
Yttrill
  • 4,725
  • 1
  • 20
  • 29