CRTP 和动态多态性编译错误

2022-01-24 00:00:00 standards templates polymorphism c++ crtp
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 *')

在 clang 3.0、gcc 4.7 和 Visual Studio 2008 上测试.

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

第一个解决方案:

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

在 Visual Studio 下编译并警告 B 已经是 A 的子级,并且不在 clang 下编译并出现初始错误.

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

另一种解决方法:

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

解决了不完整问题,但我不知道我将拥有多少个 A 实例.直觉告诉我A是虚的,所以应该只有一个.

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?

推荐答案

你的虚函数 A::foo() 返回一个 A*,而函数 B<C>::foo(),意在覆盖它,返回一个 C*.

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

这在理论上确实尊重协变原则,因为 C 确实是(派生自)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.

重新考虑您的设计的一种可能方法是使 A 也成为类模板,并让 B 传播 TA:

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; }
};

关于您的解决方法:

标准对这种情况有何规定?这段代码应该编译吗?如果不是,为什么?

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

它不应该编译,因为仅仅使 C 也显式地从 A 派生(注意,你最终会得到 two C 内的 A 类型的不同基础子对象在实例化 B<C> 时不会使 C 成为完整类型..根据 C++11 标准的第 9.2/2 段:

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:

类在类说明符的结束}处被视为完全定义的对象类型(3.9)(或完整类型).强>在类member-specification中,类在函数体内被认为是完整的,默认参数和 brace-or-equal-initializers 用于非静态数据成员(包括嵌套类).否则,它在自己的类成员规范中被认为是不完整的.

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.

相关文章