Is it possible for an inherited class to implement a virtual function with a different return type (not using a template as return)?
The return type of an overriding virtual function may differ from the return type of the overridden virtual function. This overriding function would then be called a covariant virtual function. Suppose that B::f overrides the virtual function A::f .
No. A function is pure virtual if and only if it is declared with = 0 .
A virtual function is a member function that you expect to be redefined in derived classes. When you refer to a derived class object using a pointer or a reference to the base class, you can call a virtual function for that object and execute the derived class's version of the function.
Pure virtual functions (when we set = 0 ) can also have a function body.
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.
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 functionB::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 ofD::f
or, is an unambiguous direct or indirect base class of the class in the return type ofD::f
and is accessible inD
- 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 ofB::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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With