Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CRTP and dynamic polymorphism compile error

class A {
    virtual A* foo() = 0;
};

template<class T>
class B : public A {
    virtual T* foo() { return nullptr; }
};

class C : public B<C> {

};

This is a simplified implementation for Possibility to mix composite pattern and curiously recurring template pattern. I get the following error:

Return type of virtual function 'foo' is not covariant with the return type of the function it overrides ('C *' is not derived from 'A *')

Tested on clang 3.0, gcc 4.7 and visual studio 2008.

First solution:

class C : public A, public B<C> {}

compiles under visual studio with a warning that B is already a child of A and does not compile under clang with initial error.

Another workaround:

class D : public A {}
class C : public B<D> {}

solves the incompleteness issue, but I can't figure out how many A instances will I have. Intuition tells me that A is virtual, thus there should be only one.

Also this workaround creates unreadable code.

What does the standard states about this situation? Should this code compile? If not, why?

like image 862
Vytis Valentinavičius Avatar asked Mar 22 '13 12:03

Vytis Valentinavičius


1 Answers

Your virtual function A::foo() returns an A*, while function B<C>::foo(), which is meant to override it, returns a C*.

This in theory does respect the principle of covariance, since C is indeed a specialization of (derives from) A, but at the point of instantiation, this is not known, because C is an incomplete type.

One possible way to re-think your design is to make A a class template as well, and let B propagate the template argument for T up to A:

template<typename T>
class A {
    virtual T* foo() = 0;
};

template<class T>
class B : public A<T> {
    virtual T* foo() { return nullptr; }
};

Concerning your workaround:

What does the standard states about this situation? Should this code compile? If not, why?

It shouldn't compile, because the mere fact of making C also derive from A explicitly (notice, that you would end up with two distinct base sub-objects of type A inside C) does not make C a complete type when instantiating B<C>. Per Paragraph 9.2/2 of the C++11 Standard:

A class is considered a completely-defined object type (3.9) (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, and brace-or-equal-initializers for non-static data members (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.

like image 65
Andy Prowl Avatar answered Sep 23 '22 01:09

Andy Prowl