C++ virtual function return type
Asked Answered
M

3

93

Is it possible for an inherited class to implement a virtual function with a different return type (not using a template as return)?

Morava answered 12/1, 2011 at 3:35 Comment(0)
C
99

In some cases, yes, it is legal for a derived class to override a virtual function using a different return type as long as the return type is covariant with the original return type. For example, consider the following:

class Base {
public:
    virtual ~Base() {}

    virtual Base* clone() const = 0;
};

class Derived: public Base {
public:
    virtual Derived* clone() const {
        return new Derived(*this);
    }
};

Here, Base defines a pure virtual function called clone that returns a Base *. In the derived implementation, this virtual function is overridden using a return type of Derived *. Although the return type is not the same as in the base, this is perfectly safe because any time you would write

Base* ptr = /* ... */
Base* clone = ptr->clone();

The call to clone() will always return a pointer to a Base object, since even if it returns a Derived*, this pointer is implicitly convertible to a Base* and the operation is well-defined.

More generally, a function's return type is never considered part of its signature. You can override a member function with any return type as long as the return type is covariant.

Coincide answered 12/1, 2011 at 3:45 Comment(6)
This "You can override a member function with any return type" is not correct. You can override as long as the return type is identical or covariant (which you explained), period. There is no more general case here.Ramsay
@bronekk- The rest of the sentence you quoted then states that the new return type has to be usable anywhere the original type would be; that is, the new type is covariant with the original.Coincide
that rest of the sentence is not correct; imagine replacing Base* with long and Derived* with int (or the other way around, doesn't matter). It won't work.Ramsay
That the type is usable anywhere the original type would be and covariance are two different contexts. Covariant means that the relationship between the types that implement the function and the relationship between the returned types varies in the same way. Contravariant (although not useful in C++) is the opposite context. Some languages allow for contravariant arguments in dynamic dispatch (if the base takes an object of a type T, the derived type can take a T' where T' is a base of T --as you go down one hierarchy you move up the other).Para
Someone pointed out an error in your answer (5 years ago now!) can you edit it in instead of leaving the correction in the comments? :)Pound
@Yakk Done! That was from Way Back When when I didn't realize that was the etiquette. :-)Coincide
S
57

Yes. The return types are allowed to be different as long as they are covariant. The C++ standard describes it like this (§10.3/5):

The return type of an overriding function shall be either identical to the return type of the overridden function or covariant with the classes of the functions. If a function D::f overrides a function B::f, the return type of the functions are covariant if the satisfy the following criteria:

  • both are pointers to classes or references to classes98)
  • the class in the return type of B::f is the same class as the class in the return type of D::f or, is an unambiguous direct or indirect base class of the class in the return type of D::f and is accessible in D
  • both pointers or references have the same cv-qualification and the class type in the return type of D::f has the same cv-qualification as or less cv-qualification than the class type in the return type of B::f.

Footnote 98 points out that "multi-level pointers to classes or references to multi-level pointers to classes are not allowed."

In short, if D is a subtype of B, then the return type of the function in D needs to be a subtype of the return type of the function in B. The most common example is when the return types are themselves based on D and B, but they don't have to be. Consider this, where we two separate type hierarchies:

struct Base { /* ... */ };
struct Derived: public Base { /* ... */ };

struct B {
  virtual Base* func() { return new Base; }
  virtual ~B() { }
};
struct D: public B {
  Derived* func() { return new Derived; }
};

int main() {
  B* b = new D;
  Base* base = b->func();
  delete base;
  delete b;
}

The reason this works is because any caller of func is expecting a Base pointer. Any Base pointer will do. So, if D::func promises to always return a Derived pointer, then it will always satisfy the contract laid out by the ancestor class because any Derived pointer can be implicitly converted to a Base pointer. Thus, callers will always get what they expect.


In addition to allowing the return type to vary, some languages allow the parameter types of the overriding function to vary, too. When they do that, they usually need to be contravariant. That is, if B::f accepts a Derived*, then D::f would be allowed to accept a Base*. Descendants are allowed to be looser in what they'll accept, and stricter in what they return. C++ does not allow parameter-type contravariance. If you change the parameter types, C++ considers it a brand new function, so you start getting into overloading and hiding. For more on this topic, see Covariance and contravariance (computer science) in Wikipedia.

Squishy answered 12/1, 2011 at 3:44 Comment(2)
Is this an actual feature or a side effect from the return type not being used in resolution?Brute
@Martin, definitely a feature. I'm pretty sure overload resolution had nothing to do with it. The return type is used if you're overriding a function.Squishy
F
3

A derived class implementation of the virtual function can have a Covariant Return Type.

Forecourt answered 12/1, 2011 at 3:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.