如果事后定义了类型,那么实例化一个格式不正确的类模板吗?

gez*_*eza 11 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" };
Run Code Online (Sandbox Code Playgroud)

由于我强调的标准部分,它形成不良:

函数模板,成员函数模板或类模板的成员函数或静态数据成员的特化可以在翻译单元内具有多个实例化点,并且除了上述实例化的点之外,对于任何这样的实例化.在翻译单元内具有实例化点的专门化,翻译单元的末尾也被认为是实例化的点.类模板的专门化在翻译单元中最多只有一个实例化点.任何模板的特化可以在多个翻译单元中具有实例化点.如果两个不同的实例化点根据单定义规则给出模板特化的不同含义,则程序形成错误,不需要诊断.

现在,这个代码是不正确的吗?

struct A;

template <typename> class Foo { };

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

struct A {};
Run Code Online (Sandbox Code Playgroud)

如果这样Foo宣布,那么不良形象会改变吗?

struct A;

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

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

struct A {};
Run Code Online (Sandbox Code Playgroud)

我问了这个问题,因为在这个问题下的讨论.

请注意,这不是重复.这个问题是关于代码编译的原因,这个问题是关于它是否是不正确的.它们不同,因为不正确的程序不一定是非编译程序.


请注意,使用clang和gcc,我的new T编译示例,而此示例(T作为成员)不会:

struct A;

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

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

struct A {};
Run Code Online (Sandbox Code Playgroud)

也许两者都是不正确的,只有最后一种情况才会给出诊断?

Yak*_*ont 7

struct A;
template <typename> class Foo { };
Foo<A> foo; // note A is incomplete here
struct A {};
Run Code Online (Sandbox Code Playgroud)

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 {};
Run Code Online (Sandbox Code Playgroud)

尽管如此,我们还可以:

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

因为该子句不适用于模板类.这里,模板类的唯一实例化很好.

现在,如果在另一个文件中,你有:

struct A {};
Foo<A> foo2;
Run Code Online (Sandbox Code Playgroud)

你的程序生病了.

但是,在单文件案例中:

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
Run Code Online (Sandbox Code Playgroud)

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

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

有些编译器会记住模板实例化,甚至从一个编译单元到另一个编译单元; Foo<A>将有一个::valuefalse即使在foo2创建(具有完整A).其他人Foo<A>在每个编译单元中都有两个不同的s; 它的方法将被标记为内联(并且是不同的),类的大小可能不同意,并且你会得到不良形式的程序问题.


最后,请注意,许多类型std要求他们的模板参数在旧版本的C++中完整(包括:"17.6.4.8其他函数(...)2.在以下情况下效果未定义:(. ..)特别是 - 如果在实例化模板组件时将不完整类型(3.9)用作模板参数,除非特别允许该组件" - 从boost不完整的容器文档中复制".具体而言,std::vector<T>过去需要T完整.

通过已经改变了std::vector:

[vector.overview]/3

如果分配器满足分配器完整性要求17.6.3.5.1,则在实例化向量时可以使用不完整类型T. 在引用向量特化的任何成员之前,T应该是完整的.

现在,甚至在之前,大多数实现std::vector<T>都是不完整的,T直到你尝试使用一个方法(包括它的许多构造函数或析构函数),但标准声明T 必须完整.

这实际上妨碍了一些无用的代码,比如有一个函数类型返回它自己的类型1的向量. Boost有一个库来解决这个问题.


template <typename T>
struct Foo {
  Foo() {
    new T;
  }
};
Run Code Online (Sandbox Code Playgroud)

主体Foo<T>::Foo()仅在"被叫时"被实例化.所以T没有完成就没有影响,直到Foo::Foo()被召唤.

Foo<A> foo;
Run Code Online (Sandbox Code Playgroud)

^^将无法使用非完整编译A.

using foo_t = Foo<A>;
Run Code Online (Sandbox Code Playgroud)

^^将编译,并且不会导致任何问题.

using foo_t = Foo<A>;
struct A {};
foo_t foo;
Run Code Online (Sandbox Code Playgroud)

也没问题.foo_t::foo_t当我们尝试构造一个foo_t和所有定义匹配时,获取实体化的主体.


1你能说状态机转换功能吗?


Pas*_* By 5

假设我们只有一个翻译单元,[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 {};
Run Code Online (Sandbox Code Playgroud)

那么构造函数的定义不会被实例化,因此格式良好。[温度设置]

类模板特化的隐式实例化导致

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

第四个片段的格式不正确,因为成员需要具有完整的类型。[类.mem]

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