23

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;
}
perror
  • 6,329
  • 16
  • 56
  • 72
Volodymyr Lashko
  • 548
  • 4
  • 17

1 Answers1

21

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.

StoryTeller - Unslander Monica
  • 148,497
  • 21
  • 320
  • 399
  • I agree. Part of the error message _('derived *' is not derived from 'base *')_ made me think that compiler got confused with this code. – Volodymyr Lashko Jun 14 '17 at 15:44
  • The point made in the comments above -- that `sizeof(complete_type)` must work -- also makes it clear why `derived` cannot be complete at that line. We are defining a base of `derived`, how can we know how big `derived` is? – Yakk - Adam Nevraumont Jun 14 '17 at 19:05