哪里需要完整的类型(不需要)

Where are complete types (not) required?

本文关键字:类型 不需要      更新时间:2023-10-16

我最近很惊讶地发现,这段代码编译(至少在gcc和MSVC++上):

template<typename T>
class A {
public:
    T getT() { return T(); }
};
class B : public A<B> { };

如果没有:

class A;
class B : public A { };
class A {
public:
    B getB() { return B(); }
};

在我看来,一个模板类可以将一个不完整的类型作为模板参数,并通过调用其构造函数返回一个函数,但仍然可以编译,这似乎很奇怪。那么,哪里需要完整的类型(或者如果列表更短,哪里不需要)?

以下是不需要完整类型的场景:

  • 将成员声明为指向不完整类型的指针或引用
  • 声明接受/返回不完整类型的函数
  • 定义接受/返回指向不完整类型的指针/引用的函数
  • 作为模板类型参数

基本上,在编译器不需要知道type的内存布局的任何地方,使用Incomplete类型都可以。

至于允许模板类型参数为不完整类型,标准在14.3.1模板类型参数

中明确规定

这就是CRTP在两阶段模板解析中的工作方式。模板成员函数在实例化之前不会被解析。

编辑:也许,措辞不是很精确。当编译器看到class B : public A< B > {...};,它经过A< B >,注意到有一个函数B get() {...},但不评估其定义,直到函数的实际实例化,此时B必须是一个完整的类型。

编辑:我相信,标准第14.6节中涵盖了确切的规则(正如Als在回答中指出的那样)。根据两阶段的模板名称查找,处理了dependentnon-dependent的名称及其在编译过程中不同时间的解析。然而,不幸的是,在不同的编译器上,两阶段名称查找实现可能与Standard不同。相同的代码可能在GCC上编译,也可能不在MSVC++上编译,反之亦然。更重要的是,看似相同的代码可能会被相同的编译器拒绝。在MSVC++上,当基类使用指向派生类函数的指针作为其函数的默认参数时,我遇到了一个问题。它没有在MSVC++下编译,而是在GCC下编译(正确)。但是,即使在MSVC++上,也要使用派生类构造函数作为两个编译器编译的默认参数。转到图。

模板并不是真正的代码;这是一个模板,它描述了一旦您填充了缺失的部分(类型参数),如何构建代码。正因为如此,编译器在模板定义中比在实际代码定义中有更多的余地。当您实际使用模板并识别类型时,编译器需要生成实际代码,并应用所有常用规则。

如果编译器不需要知道对象成员的大小或偏移量,则不需要完整的定义。例如,定义指向类的指针或引用不需要这两者。当您尝试使用指针或引用时,您将需要一个完整的定义。