For some reason, up-to-date versions of both GCC and clang do not recognize return type covariance in this particular scenario. The error message is misleading:
error: return type of virtual function 'foo' is not covariant with the return type of the function it overrides ('derived *' is not derived from 'base *')
Here is the code:
class base { private: virtual base * foo() = 0; }; template< class T > class foo_default_impl : public virtual base { private: T * foo() override { return nullptr; } }; class derived : public virtual base, private foo_default_impl< derived > { }; int main() { derived d{}; // error: return type of virtual function 'foo' is not covariant with the return type of the function it overrides ('derived *' is not derived from 'base *') return 0; }
Covariant return type refers to return type of an overriding method. It allows to narrow down return type of an overridden method without any need to cast the type or check the return type. Covariant return type works only for non-primitive return types.
above. C++'s classical OOP system supports “covariant return types,” but it does not support “contravariant parameter types.” This concludes our explanation of covariance and contravariance in the classical OOP system.
From Java 5 onwards, we can override a method by changing its return type only by abiding the condition that return type is a subclass of that of overridden method return type.
Here's the thing. While to us it may appear that the compiler knows everything it needs to know about the types in question, the standard says otherwise.
[temp.arg.type/2]
... [ Note: A template type argument may be an incomplete type. — end note ]
[basic.types/5]
A class that has been declared but not defined, an enumeration type in certain contexts ([dcl.enum]), or an array of unknown bound or of incomplete element type, is an incompletely-defined object type.46 Incompletely-defined object types and cv void are incomplete types ([basic.fundamental]). Objects shall not be defined to have an incomplete type.
[class/2]
A class-name is inserted into the scope in which it is declared immediately after the class-name is seen. The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name. For purposes of access checking, the injected-class-name is treated as if it were a public member name. A class-specifier is commonly referred to as a class definition. A class is considered defined after the closing brace of its class-specifier has been seen even though its member functions are in general not yet defined. The optional attribute-specifier-seq appertains to the class; the attributes in the attribute-specifier-seq are thereafter considered attributes of the class whenever it is named.
The text in bold paints the simple picture that the compilers in question treat the type parameter T as an incomplete object type. It's as though you only forward declared it, like so:
class derived;
They cannot deduce that this forward declaration is a class derived from base
. So they cannot accept it as a co-variant return type in the context of foo_default_impl
. Like was pointed out by @marcinj in the comments:
[class.virtual/8]
If the class type in the covariant return type of D::f differs from that of B::f, the class type in the return type of D::f shall be complete at the point of declaration of D::f or shall be the class type D.
Since T
is neither complete, nor is it foo_default_impl<T>
itself, it cannot be a co-variant return type.
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