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


这段代码肯定是不正确的,因为 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 {};

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


6336
2017-09-05 12:44


起源

如果这是形成不良的NDR, vector<Incomplete> 也是出于同样的原因。但我们可以拥有 vectors类型不完全就好了。 - Barry
明确的专业化不是任何东西的兴趣点。你的第一个片段是不正确的 [temp.expl.spec] / 6。 - T.C.
@ T.C。:我的意思是 Foo<int> x = { "bar" }; 作为POI。 Foo<int>这里的意义不同于 Foo<int> x = { 42 };。这不正确吗? - geza
@geza eel.is/c++draft/temp.point#8.sentence-2 - Barry
@Barry(和T.C.):谢谢! - geza


答案:


假设我们只有一个翻译单元, [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]

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


4
2017-09-07 14:13





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

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

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

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

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 即使 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促进 有一个库来解决这个问题。


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 你能说状态机转换功能吗?


7
2017-09-05 12:48



评论不适用于扩展讨论;这次谈话已经开始了 转移到聊天。 - Samuel Liew♦


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

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

1
2017-09-05 12:47



感谢你的回答!注意(和抱歉),我已经扩展了一点,它对原始问题更有用。 - geza
这很奇怪,那 T 因为成员不编译,但是 new T 在构造函数中。 - geza
@geza new T 只需要 T 在运行时完成;) - YSC
不完整的类型不能 newd - geza
不要忘记两阶段(这适用吗?(meh)) - Lightness Races in Orbit