如果类型模板是在以后定义的,则实例化具有不完整类型的类模板吗?

时间:2018-09-05 12:44:43

标签: c++ language-lawyer c++17

此代码肯定是格式错误的,因为Foo在实例化点之后是专用的:

template <typename T>
struct Foo {
    int a;
};

Foo<int> x = { 42 };

template <>
struct Foo<int> {
    const char *a;
};

Foo<int> x = { "bar" };

由于我强调的standard部分,它的格式不正确:

  

功能模板,成员函数模板或类模板的成员函数或静态数据成员的专业化可以在翻译单元内具有多个实例化点,并且除了上述实例化点之外,对于翻译单元中具有实例化点的任何此类专业化,翻译单元的末尾也被视为实例化点。类模板的专门化在翻译单元中最多具有一个实例化点。任何模板的专业化可能在多个翻译单元中具有实例化点。 如果两个不同的实例化点根据一个定义规则赋予模板专业化不同的含义,则该程序格式错误,无需诊断。

现在,代码格式错误吗?

struct A;

template <typename> class Foo { };

Foo<A> foo; // note A is incomplete here

struct A {};

如果Foo这样声明,那么格式错误会改变吗?

struct A;

template <typename T>
struct Foo {
    Foo() {
        new T;
    }
};

Foo<A> foo; // note A is incomplete here

struct A {};

由于这个question下的讨论,我问了这个问题。

注意,这不是重复项。这个问题是关于为什么要编译代码的问题,这个问题是关于代码是否格式错误的问题。它们不同,因为格式错误的程序不一定是非编译程序。


请注意,对于clang和gcc,我的示例new T会编译,而此示例(T是成员)不会编译

struct A;

template <typename T>
struct Foo {
    T t;
};

Foo<A> foo; // note A is incomplete here

struct A {};

也许两者都是病态,仅针对最后一种情况进行诊断?

3 个答案:

答案 0 :(得分:7)

struct A;
template <typename> class Foo { };
Foo<A> foo; // note A is incomplete here
struct A {};

Foo<A>仅取决于A的名称而不是其完整类型。

所以这是格式正确的;但是,这种情况仍然可以破坏(格式错误),但可以在您测试的每个编译器中进行编译。

首先,我们偷了is_complete。然后我们这样做:

struct A;
template <class T> class Foo {
  enum{ value = is_complete<T>::value };
};
Foo<A> foo; // note A is incomplete here
struct A {};

尽管如此,我们还可以:

  

[...]对于在翻译单元内具有实例化点的任何此类专业化,翻译单元的末尾也被视为实例化点。 [...]

因为该子句不适用于模板类。在这里,模板类的唯一实例是好的。

现在,如果您在另一个文件中,则:

struct A {};
Foo<A> foo2;

您的程序格式错误。

但是,在一个文件的情况下:

struct A;
template <class T> class Foo {
  enum{ value = is_complete<T>::value };
};
Foo<A> foo; // note A is incomplete here
struct A {};
Foo<A> foo2; // ill-formed

您的代码很好。在给定的编译单元中,Foo<A>有一个实例化点;第二个是对第一个实例化点的引用。

一个和两个文件versoins几乎可以肯定在C ++编译器中编译,没有错误或警告。

一些编译器甚至从一个编译单元到另一个花药都记住模板实例化;即使创建了Foo<A>(带有完整的::value),false的{​​{1}}也将是foo2。其他人在每个编译单元中将有两个不同的A;它的方法将被标记为内联(且不同),类的大小可能会不同,并且您会得到一系列格式错误的程序问题。


最后,请注意,Foo<A>中的许多类型都要求其模板参数在较旧的C ++版本中是完整的(包括:“ 17.6.4.8其他函数(...)2.效果在以下情况下是未定义的:(...)特别是-如果在实例化模板组件时使用不完整类型(3.9)作为模板参数,除非该组件特别允许”(从boost不完整容器文档中复制) 。具体来说,std曾经要求std::vector<T>完整。

由具有changed for std::vector

[vector.overview] / 3

  

如果分配器满足分配器完整性要求17.6.3.5.1,则在实例化矢量时可以使用不完整类型T。在引用所得的向量专业化的任何成员之前,T必须完整。

现在,即使在之前,T的大多数实现也可以使用不完整的std::vector<T>来解决,直到您尝试使用方法(包括其许多构造函数或析构函数)为止,但是标准规定T 必须是完整的。

这实际上妨碍了一些无用的代码,例如具有返回其自身类型为 1 的向量的函数类型。 Boost有一个图书馆来解决这个问题。


T

template <typename T> struct Foo { Foo() { new T; } }; 的正文仅在“调用时”实例化。因此,Foo<T>::Foo()的缺乏完成直到调用T才有影响。

Foo::Foo()

^^将无法使用不完整的Foo<A> foo; 进行编译。

A

^^将会编译,不会造成任何问题。

using foo_t = Foo<A>;

也没问题。当我们尝试构建using foo_t = Foo<A>; struct A {}; foo_t foo; 时,foo_t::foo_t的主体被实例化,并且所有定义都匹配。


1 可以说状态机转换功能吗?

答案 1 :(得分:4)

假设我们只有一个翻译单位,[temp.point]会排除您的报价是造成格式错误的原因

  

类模板的专门化在翻译单元中最多具有一个实例化点。

相反,第一个代码段的问题是[temp.expl.spec]

  

如果一个模板,一个成员模板或一个类模板的成员是显式专门化的,则应在首次使用该专门化之前声明该专门化,这将导致在每个此类翻译单元中进行隐式实例化发生使用;无需诊断。

第二个代码段格式正确,不需要模板参数必须具有完整的类型。

第三个代码段格式错误,new T要求T是完整类型。这里的一个小问题是,构造函数的定义在Foo<A> foo;处隐式实例化。如果是,则代码段更改为

struct A;

template <typename T>
struct Foo {
    Foo() {
        new T;
    }
};

using FooA = Foo<A>;

struct A {};

然后,不会实例化构造函数的定义,因此其格式正确。 [temp.inst]

  

类模板专业化的隐式实例化导致

     
      
  • 未删除的类成员函数,成员类,作用域成员枚举,静态数据成员,成员模板和好友的声明的隐式实例化,而不是定义的隐式实例化;和[...]
  •   

第四个代码段格式错误,因为成员需要具有完整的类型。 [class.mem]

  

非静态数据成员的类型不得为不完整的类型[...]

答案 2 :(得分:1)

幸运的是,这是明确定义的。出于完全相同的原因,这是明确定义的:

struct A;

class Foo { A* value; };

Foo foo; // note A is incomplete here

struct A {};

这是错误的格式:

struct A;

template <class T> class Foo { T value; }; // error: 'Foo<T>::value' has incomplete type

Foo<A> foo; // note A is incomplete here

struct A {};