如果类型是事后定义的,则实例化具有不完整类型的类模板是否格式错误?

2024-04-01

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

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

Foo<int> x = { 42 };

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

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

它的格式不正确,因为standard http://eel.is/c++draft/temp.point#8我强调了:

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

Now, is this代码格式错误?

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 https://stackoverflow.com/questions/52180744/pimpl-why-can-make-unique-be-called-on-an-incomplete-type.

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


注意,对于 clang 和 gcc,我的示例是new T编译,而这个例子(T作为会员)不:

struct A;

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

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

struct A {};

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


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

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

所以这是格式良好的;然而,这种事情仍然可能会破坏(变得格式错误),但在您测试的每个编译器中都可以编译。

首先,我们偷做完了 https://stackoverflow.com/a/21121104/1774667。然后我们这样做:

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>将有一个::value那是false即使foo2已创建(具有完整的A)。其他人会有两种不同的Foo<A>每个编译单元中的 s;它的方法将被标记为内联(并且是不同的),类的大小可能不一致,并且您将遇到一连串不良的程序问题。


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

By c++17 /questions/tagged/c%2b%2b17具有改变为std::vector https://stackoverflow.com/a/44675477/1774667:

[向量.概述]/3

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

现在,甚至在此之前c++17 /questions/tagged/c%2b%2b17,大多数实现std::vector<T>不完整也可以T直到您尝试使用一个方法(包括它的许多构造函数或析构函数),但标准规定T must是完整的。

This actually gets in the way of some unuseful code, like having a function type that returns vectors of its own type1. Boost https://www.boost.org/doc/libs/1_48_0/doc/html/container/containers_of_incomplete_types.html has a library to solve this problem.


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


1 Can you say state machine transition function?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如果类型是事后定义的,则实例化具有不完整类型的类模板是否格式错误? 的相关文章

随机推荐