您提供的报价来自温度点/4 https://timsong-cpp.github.io/cppwp/n4659/temp.point#4情况确实如此:
template<typename T>
struct A {
template<typename U>
struct B { };
B<T> b;
};
// point-of-instantiation for A<int>::B<int>
// point-of-instantiation for A<int>
int main() {
A<int> a;
}
没有太多律师可做。该标准说这些是实例化点。类模板不是类,直觉也不一定会延续下去。它们既有定义又有实例化。
获取外部类的定义。如果是类,则必须定义成员的类型。如果是类模板,则只需声明成员的类型。您可以降低 B 的定义:
template<typename T>
struct A {
template<typename U> struct B;
B<T> b;
};
template<typename T>
template<typename U>
struct A<T>::B { };
您无法使用类来执行此操作(定义中具有“不完整”成员),但可以使用类模板来执行此操作。
那么问题来了,为什么模板的实例化点是A<T>::B<T>
之前的A<T>
?最终,因为标准是这么说的。但请考虑一下,如果是在之后,则根本无法拥有内部类模板。比如说,如果它在定义之内A<T>
,名称查找会出现错误,因为定义之间的名称A
和实例化点A<int>
将不可见于A<int>::B<int>
。所以这实际上是有一定道理的。
也许直觉来自于将定义和实例化点混为一谈:
那么,如何才能将成员类 B 的定义放在封闭类 A 的定义之前呢?
事实并非如此。实例化点控制名称可见性。 TU 中截至该点的所有名称都是可见的。 (这是not的定义。)从这个角度来看,直观上很清楚:A<int>::B<int>
应该有一个靠近实例化点的实例化点A<int>
(他们应该看到相同的其他名称)。如果有顺序,可能内部应该排在第一位(这样A<int>
可以有一个A<int>::B<int>
成员)。如果没有顺序,则必须有关于实例化如何交错或交互的语言。
该规则有两个有趣的方面。
一是模板可以专门化。因此,为了完成这个要求,当编译器去实例化时A<int>
,它必须首先选择专业化,对其进行足够的处理以知道它有一个成员类模板并且需要实例化它,然后停止一切来实例化A<int>::B<int>
第一的。这并不困难,但很微妙。可以说,有一个实例化堆栈。
第二个方面更有趣。您可能会从普通的类直觉中预料到B<T>
可以使用以下内容(例如 typedef)A<T>
在模板上下文中需要实例化A<T>
当它还不存在时A<T>::B<T>
正在被实例化。喜欢:
template<typename T>
struct A {
using type = T;
template<typename U>
struct B { using type = typename A<U>::type; };
B<T> b;
};
这可以吗?
如果实例化点A<int>::B<int>
在之前A<int>
,我们无法真正形成A<int>::type
.
这就是境界CWG287 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#287 and P1787 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1787r6.html.
CWG287 提议实例化点应相同(一个不在另一个之前)。此外,它将补充:
如果隐式实例化的类模板特化、类成员特化或类模板的特化引用了包含直接或间接导致实例化的特化引用的类、类模板特化、类成员特化或类模板的特化,则要求类参考的完整性和排序应用于专业化参考的上下文中。
在我的例子中,A<int>::B<int>
参考A<int>
通过引用直接导致其实例化B<int>
,因此类引用的完整性和排序要求(typename A<int>::type
)应用于专业化参考(B<int> b
)。所以没关系。如果 typedef 低于以下定义也没关系B
. But if我们将 typedef 移到成员下方b
,它将是格式错误的!微妙的!这具有交错实例化的效果。当我们看到该成员时,我们停止正在做的事情,去实例化A<int>::B<int>
,但我们可以使用实例化时的顺序和完整性要求A<int>
。实例化点是相同的,因此我们也可以使用 TU 中的相同声明。
CWG287 似乎旨在复制编译器已经做的事情。然而,CWG287 自 2001 年以来一直开放。(另请参阅this https://stackoverflow.com/questions/48227757/curiously-mutually-recurring-class-definitions and this https://stackoverflow.com/questions/45907160/russells-paradox-in-c-templates/45926490.)
P1787似乎是针对C++23的,旨在重写很多微妙的语言。我think旨在达到与 CWG287 类似的效果。但要做到这一点,他们必须彻底重新定义名称查找,我很难知道这一点。 :)