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