这段代码肯定是不正确的,因为 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 {};
也许两者都是不正确的,只有最后一种情况才会给出诊断?
假设我们只有一个翻译单元, [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]
非静态数据成员的类型不应是不完整的类型[...]
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 ++中完整(包括 C ++ 11:“17.6.4.8其他函数(...)2。在以下情况下效果未定义:( ...)特别是 - 如果在实例化模板组件时将不完整类型(3.9)用作模板参数,除非特别允许该组件“ - 从boost不完整的容器文档中复制”。具体来说, std::vector<T>
曾经要求 T
完成。
通过 C ++ 17 具有 变了 std::vector
:
[vector.overview] / 3
如果分配器满足分配器完整性要求17.6.3.5.1,则在实例化向量时可以使用不完整类型T.在引用向量特化的任何成员之前,T应该是完整的。
现在,甚至在此之前 C ++ 17,大多数的实现 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 你能说状态机转换功能吗?
幸运的是,这是明确定义的。出于同样的原因,这是明确定义的:
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 {};