从不完整的类继承的类模板

Class template inheriting from incomplete class

本文关键字:继承      更新时间:2023-10-16

由于继承了不完整的类型,因此此代码无法编译(https://godbolt.org/z/G35wj9):

template<typename>
class Incomplete;
class T : public Incomplete<T> {};
template<typename>
class Incomplete {};
int main()
{
[[maybe_unused]] T x;
}

我相信这个规则也适用于类模板。但是,此代码编译良好(https://godbolt.org/z/cU6GNt):

template<typename>
class Incomplete;
template<int d>
class T : public Incomplete<T<d>> {};
template<typename>
class Incomplete {};
int main()
{
[[maybe_unused]] T<1> x;
}

当涉及到类模板时,基类是否只需要在实例化时完成?

当涉及类模板时,基类是否只需要在实例化时完成?

如果它是一个依赖的基,那么是。因此,编译器在定义模板时不知道Incomplete<T<d>>是什么。毕竟,对于d的某些值,我们可以对Incomplete<T<d>>进行专门化,这与主模板声明完全不同。

template<>
class Incomplete<T<0>> {};

这不是循环依赖关系。简单地将专门化命名为T<0>不会导致它被实例化。这只是一个类型名称。但这确实意味着编译器没有追索权,只能等到它可以检查基类是否有效。

另一方面,如果基类不是依赖类型,那么将其用作基类将是不正确的。

标准在[temp.inst]§1(取自C++17(:中涵盖了这一点

除非类模板专门化已显式实例化(17.7.2(或显式专门化(17.7.3(,否则当在需要完全定义的对象类型的上下文中引用该专门化时,或者当类类型的完整性影响程序的语义时,类模板专化将隐式实例化。[注意:特别是,如果表达式的语义取决于类模板专用化的成员或基类列表,则类模板专用性是隐式生成的。例如,删除指向类类型的指针取决于该类是否声明了析构函数,而指向类类型指针之间的转换取决于继承关系在涉及的两个班之间。——尾注][示例

template<class T> class B { /* ... */ };
template<class T> class D : public B<T> { /* ... */ };
void f(void*);
void f(B<int>*);
void g(D<int>* p, D<char>* pp, D<double>* ppp) {
f(p);            // instantiation of D<int> required: call f(B<int>*)
B<char>* q = pp; // instantiation of D<char> required: convert D<char>* to B<char>*
delete ppp;      // instantiation of D<double> required
}

--结束示例]如果在实例化时(17.6.4.1(已经声明了类模板,但没有定义,则实例化会产生不完整的类类型(6.9(

template<class T> class X;
X<char> ch;          // error: incomplete type X<char>

--结束示例][注意:在模板声明中,本地类(12.4(或枚举以及本地类的成员永远不会被视为可以单独实例化的实体(这包括它们的默认参数、noexcept说明符和非静态数据成员初始值设定项,如果有的话(因此,查找依赖名称,检查语义约束,并将使用的任何模板实例化为实体实例化的一部分,在实体实例化中声明本地类或枚举。——尾注]

cppreference中也有介绍。

请记住,类模板不是类型,也不会为其生成代码。代码是在模板实例化时生成的。当类模板被实例化(隐式或显式(时,就会生成一个实际的类(类型((包括它的代码(。

唯一的区别是,在您的示例中,您在模板内部使用了Incomplete类。

在最初的例子中,它被一个类使用。此时编译器将创建类的类型,因此在定义Incomplete之前。

在您的代码中,Incomplete被用于另一个模板T中(粗略地说:T是一个使用Incomplete生成各种类型的模具(。此时编译器什么也不做,它只存储一个"生成类型的规则"(我称之为模具(
编译器在使用T时检查它的有效性,因此在第行-然后生成实际类型T<1> : public IncompleteT(模具(是"有效的",如果Incomplete定义为

[[maybe_unused]] T<1> x;

在这一点上,模板类Incomplete定义得很好,编译器也很满意。