【问题标题】:Class template inheriting from incomplete class继承自不完整类的类模板
【发布时间】:2019-11-20 08:27:07
【问题描述】:
由于继承自不完整类型 (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;
}
当涉及到类模板时,是否只要求基类在实例化时完整?
【问题讨论】:
标签:
c++
templates
inheritance
incomplete-type
【解决方案1】:
当涉及到类模板时,是否只要求基类在实例化时完整?
如果它是一个依赖基础,那么是的。因此,编译器在模板定义时不知道Incomplete<T<d>> 是什么。毕竟,对于d 的某些值,我们可以拥有一个与主模板声明完全不同的Incomplete<T<d>> 特化。
template<>
class Incomplete<T<0>> {};
这不是循环依赖。简单地命名专业化T<0> 不会导致它被实例化。它只是一个类型名称。但这确实意味着编译器没有追索权,只能等到它可以检查基类是否有效。
另一方面,如果基类不是依赖类型,那么将其用作基类将是错误的。
【解决方案2】:
标准在 [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-specifiers,以及非静态数据成员初始化器,如果有的话)。结果,查找依赖名称,检查语义约束,并且将使用的任何模板实例化为声明本地类或枚举的实体的实例化的一部分。 — 尾注 ]
cppreference 也有报道。
请记住,类模板不是类型,并且不会为它生成任何代码。实例化模板时会生成代码。当类模板被实例化(隐式或显式)时,会生成一个实际的类(类型)(包括它的代码)。
【解决方案3】:
唯一的区别是,在您的示例中,您在模板中使用了 Incomplete 类。
在原始示例中,它被一个类使用。此时编译器会创建类的类型,因此在定义Incomplete之前。
在你的代码中,Incomplete在另一个模板T中使用,(粗略地说:T是使用Incomplete生成各种类型的模具)。这时候编译器什么也不做,它只是存储了一个“生成类型的规则”(我称之为模具)。
编译器在使用 T 时检查其有效性,因此在该行中 - 然后生成实际类型 T<1> : public Incomplete。
T(模具)是“有效的”,如果 Incomplete 已定义
[[maybe_unused]] T<1> x;
至此模板类Incomplete已经定义好了,编译器很开心。