user520621
user520621

Reputation:

How to make a call to a subclass's member from a base class reference in polymorphic hierarchy

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;
};

Upvotes: 3

Views: 683

Answers (5)

beldaz
beldaz

Reputation: 4471

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.

Upvotes: 0

Yttrill
Yttrill

Reputation: 4921

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

Upvotes: 0

icecrime
icecrime

Reputation: 76755

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).

Upvotes: 1

Steve Rowe
Steve Rowe

Reputation: 19413

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;
}

Upvotes: 1

EboMike
EboMike

Reputation: 77752

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.

Upvotes: 0

Related Questions