首页 > 解决方案 > 如果类型是在之后定义的,那么实例化具有不完整类型的类模板是否格式错误?

问题描述

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

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

Foo<int> x = { 42 };

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

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

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

函数模板、成员函数模板或类模板的成员函数或静态数据成员的特化可以在翻译单元内具有多个实例化点,并且除了上述实例化点之外,对于任何此类在翻译单元内有一个实例化点的特化,翻译单元的末端也被认为是一个实例化点。类模板的特化在翻译单元内最多有一个实例化点。任何模板的特化都可能在多个翻译单元中具有实例化点。如果根据单一定义规则,两个不同的实例化点赋予模板特化不同的含义,则程序是非良构的,不需要诊断。

现在,这段代码格式不正确吗?

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 {};

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

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


请注意,使用 clang 和 gcc,我的示例new T编译,而这个示例(T作为成员)没有:

struct A;

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

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

struct A {};

也许两者都是不正确的,并且仅针对最后一种情况给出诊断?

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

解决方案


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>在给定的编译单元中有一个实例化点;第二个是对第一个实例化点的引用。

一个和两个文件版本几乎肯定会在 C++ 编译器中编译而不会出现错误或警告。

一些编译器甚至从一个编译单元到另一个编译单元都会记住模板实例化;即使在创建之后Foo<A>也会有一个(带有一个完整的)。其他的每个编译单元会有两个不同的s;它的方法将被标记为内联(并且是不同的),类的大小可能不一致,并且您会遇到一连串格式不正确的程序问题。::valuefalsefoo2AFoo<A>


最后,请注意,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;
  }
};

的主体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当我们尝试构造 a 时,的主体被实例化foo_t,并且所有定义都匹配。


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


推荐阅读