CRTP and dynamic polymorphism compile error

2019-02-13 14:16发布

问题:

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?

回答1:

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.