Covariant Return Types in C++ Virtual Functions
In C++, an overriding virtual member function is generally required to have the exact same return type as the base class function it overrides. The covariant return type rule provides a specific exception to this requirement, allowing an overriding function in a derived class to return a pointer or reference to a class derived from the return type declared in the base class. This feature enables cleaner object-oriented design, particularly in factory methods and polymorphic cloning patterns, by eliminating the need for explicit type casting by callers who already know the concrete type of the object.
The Standard Rule vs. Covariance
Under the standard function overriding rules, the signature and return type of a virtual function in a derived class must precisely match the declaration in the base class:
class Base {
public:
virtual Base* clone() const;
};
class Derived : public Base {
public:
// Covariant return type: returns Derived* instead of Base*
Derived* clone() const override;
};Without covariant return types, Derived::clone() would
be forced to return Base*. Callers invoking
clone() on a pointer or reference explicitly typed as
Derived would then have to perform an explicit
static_cast or dynamic_cast to access
derived-specific members. With covariance, the compiler allows the
narrower type while preserving dynamic dispatch.
Requirements for Covariant Return Types
For a return type to be validly covariant, several conditions must be met:
- Pointers or References Only: The return types must
be pointers (
T*) or references (T&) to classes. Covariance does not apply to values (e.g., returningDerivedby value when the base returnsBase). - Accessible Inheritance: The class returned by the
derived function must be an unambiguously derived class of the type
returned by the base function. This typically requires
publicinheritance between the returned types. - Complete Types: The class being returned must be fully defined at the point of the overriding function's declaration so the compiler can verify the inheritance relationship.
- Const and Volatile Qualifiers: The cv-qualifiers of
the target type in the derived function cannot be less restrictive than
those in the base function. For example, if the base returns
const Base*, the derived function cannot returnDerived*unless it is also markedconst.
Code Example
The most common implementation is the polymorphic clone
idiom:
class Animal {
public:
virtual ~Animal() = default;
virtual Animal* clone() const = 0;
};
class Dog : public Animal {
public:
// Overrides Animal::clone() with a covariant return type
Dog* clone() const override {
return new Dog(*this);
}
};
void example() {
Dog originalDog;
// Direct call uses the covariant return type directly:
Dog* dogCopy = originalDog.clone(); // No cast needed
// Polymorphic call still works as expected:
Animal* animalPtr = &originalDog;
Animal* animalCopy = animalPtr->clone(); // Dynamic dispatch returns Dog*, bound to Animal*
delete dogCopy;
delete animalCopy;
}Smart Pointer Limitation
The covariant return type rule is built into the core language
specifically for native raw pointers and references. It does not
natively apply to smart pointer class templates such as
std::unique_ptr<Base> and
std::unique_ptr<Derived>. Because
std::unique_ptr<Base> and
std::unique_ptr<Derived> are distinct template
instantiations rather than pointers with an inheritance relationship,
the compiler considers them incompatible return types for virtual
overrides.