【问题标题】:Incomplete class usage in template模板中的类使用不完整
【发布时间】:2011-11-04 20:01:06
【问题描述】:

我很惊讶在 g++ 的各种采样版本上,以下编译没有错误或警告:

// Adapted from boost::checked_delete()
template <class T> inline void assert_complete()
{
  typedef char type_must_be_complete[ sizeof(T) ? 1 : -1 ];
  (void) sizeof(type_must_be_complete);
}

class X;

void f()
{
  assert_complete<X>();
}

class X {};

int main() {}

如果X 的定义丢失或在不同的翻译单元中,我确实会出错。

但是在上面的程序中,f的定义不是我的模板的单个实例化点吗? X 在那个实例化点的不完整性难道不是语义错误吗?

(C++03 和/或 C++11 草案)标准是否将此程序称为格式正确、格式错误、格式错误但不需要诊断或未定义行为?

编辑:@David Rodriguez - dribeas 报告说 clang++、comeau 和 Visual Studio 2010 也接受类似的代码。

【问题讨论】:

  • 有趣的是,当我在 X 的定义中添加 int 时,sizeof(T) 是 4。过去已知模板具有预认知能力。

标签: c++ templates incomplete-type


【解决方案1】:

(我一直在等待 Alf Steinbach 发布答案,但由于他没有这样做,我将发布他在 Lounge C++ 聊天中提到的参考):

标准表明模板实例化是在翻译单元已经被翻译之后执行的,因此模板实例化及时发生在所有非模板化元素已经完成之后被处理。这在 2.2 翻译阶段中有所描述:

第 1-6 段定义了预处理器的工作和基本的文本操作(字符集的转换、文字的连接......)

7/ 分隔标记的空白字符不再重要。每个预处理令牌都被转换为一个令牌。 (2.7)。生成的标记在句法和语义上进行分析并作为翻译单元进行翻译。

8/ 翻译后的翻译单元和实例化单元组合如下:检查每个翻译后的翻译单元以生成所需实例化的列表。找到所需模板的定义。是否需要包含这些定义的翻译单元的源是由实现定义的。执行所有必需的实例化以生成实例化单元。 [注意:这些类似于翻译的翻译单元,但不包含对未实例化模板的引用和模板定义。 — end note ] 如果任何实例化失败,则程序是非良构的。

为简洁起见,我删除了一些注释。现在,重要的一点似乎是在一步中翻译代码而不触发模板实例化,然后在后面的步骤中实例化模板。这反过来意味着,如果类型在翻译单元中的任何位置是complete,那么在编译器进行实例化时它就已经被处理了。

免责声明:这似乎是我尝试过的所有编译器都表现出完全相同行为的一个很好的理由(gcc、clang、comeau、VS 2010),但这只是说明及时执行实例化时,它没有明确说明在模板实例化时类型可能是不完整的。

【讨论】:

  • spencercw 的回答和评论似乎表明它在标准的其他地方被明确允许。
  • @MatthewWalton:spencercw 的回答与这个问题无关。如果不是函数模板而是非模板,那么代码将无法编译,正如阿什普勒在他的评论中指出 spencercw 的答案,标识符在所有 TU 中引用相同类型的事实并不意味着存在对遇到类型定义之前(即仍然不完整)你可以做什么或不做什么没有限制。
  • 具体问题是为什么int size1() { return sizeof(X); }如果X不完整此时不会编译,而template &lt;typename T&gt; int size2() { return sizeof(T) }即使类型不完整也会编译实例化点,而 spencercw 的答案没有处理这种差异。
  • @DavidRodríguez-dribeas 先生,我仍然对您在其他一些答案中教授的 POI 概念感到困惑(抱歉,我不记得您在哪个问题中指定了 POI),所以事实上现在没有POI概念吗?
  • @AngelusMortis:我不明白这个问题。实例化点是语言的一部分,自从模板进入语言以来就一直存在,并且(可能)永远存在。
【解决方案2】:

这一行完成了类型:

class X {};

只要类型在翻译单元中的某个地方完成,那么任何先前未完成的实例都将完成。

这是标准 [basic.types] 中的相关部分(3.9 第 7 段):

一个类类型(例如“class X”)可能在翻译单元中的某个时间点不完整,然后在稍后完成; “X 类”类型在两个点上都是相同的类型。数组对象的声明类型可能是一个不完整类类型的数组,因此是不完整的;如果类类型稍后在翻译单元中完成,则数组类型变得完整;这两个点的数组类型是相同的类型。数组对象的声明类型可能是一个未知大小的数组,因此在翻译单元中的某一点是不完整的,并在稍后完成;这两个点的数组类型(“T 的未知边界数组”和“N T 的数组”)是不同的类型。指向未知大小数组的指针的类型,或由 typedef 声明定义为未知大小数组的类型,无法完成。

【讨论】:

  • 但是有很多事情你不能用尚未完成的类型来做,即使它稍后在 TU 中完成。尝试将模板从图片中取出:class X; void f() { (void) sizeof(X); } class X{}; 肯定会出错。
  • 啊,是的,我明白你的意思;看起来我可能误解了规范。但是,[temp.arg.type] 声明“模板类型参数可能是不完整的类型”,我想这允许它使用稍后在翻译单元中定义的完整类型。
猜你喜欢
  • 1970-01-01
  • 2021-12-11
  • 2019-03-15
  • 2019-06-21
  • 1970-01-01
  • 1970-01-01
  • 2014-05-11
相关资源
最近更新 更多