Many years ago I saw a code like this and was quite pizzled: Derived::bar overrides Base::bar even though the return types are different.

class A {
public:
    virtual void hello() const { std::cout << "Hello from A" << std::endl; }
};

class B: public A {
public:
    void hello() const override { std::cout << "Hello from B" << std::endl; }
};

class Base {
public:
    virtual A* bar() { return &a; }
private:
    A a;
};

class Derived: public Base {
public:
    // return type is different
    B* bar() override { return &b; }
private:
    B b;
};

(see the code on Compiler Explorer)

Well, according to C++ standard it’s absolutely valid and in line with the definition of virtual methods

If some member function vf is declared as virtual in a class Base, and some class Derived, which is derived, directly or indirectly, from Base, has a declaration for member function with the same

  • name
  • parameter type list (but not the return type)
  • cv-qualifiers
  • ref-qualifiers
  • Then this function in the class Derived is also virtual (whether or not the keyword virtual is used in its declaration) and overrides Base::vf (whether or not the specifier override is used in its declaration).

The most interesting part is that the return type doesn’t have to be the same.

Covariant types

This trick is called Covariant return types:

If the function Derived::f overrides a function Base::f, their return types must either be the same or be covariant. Two types are covariant if they satisfy all of the following requirements:

  • both types are pointers or references (lvalue or rvalue) to classes. Multi-level pointers or references are not allowed.
  • the referenced/pointed-to class in the return type of Base::f() must be an unambiguous and accessible direct or indirect base class of the referenced/pointed-to class of the return type of Derived::f().
  • the return type of Derived::f() must be equally or less cv-qualified than the return type of Base::f().

In the example above both A* and B* are pointers, A is a base class of B, and A* and B* have the same cv-qualifiers. So, they’re coveriant types, and Derived::bar overrides virtual Base::bar method.

Even more, Base::bar may change the return type to const A*, and everything will continue to work (because B* is less cv-qualified than const A*).

class Base {
public:
    virtual const A* bar() { return &a; }
private:
    A a;
};

class Derived: public Base {
public:
    // return type is different
    B* bar() override { return &b; }
private:
    B b;
};

Notes

I haven’t seen this trick in production code since then, so its usefulness is questionable.

Potentially, it can be used to get the implementation of the interface from the interface itself in places where the implementation class is accessible.