【问题标题】:The implicit instantiation for a member class template when the specialization is referenced in the enclosing class template在封闭类模板中引用特化时成员类模板的隐式实例化
【发布时间】:2021-03-14 00:20:11
【问题描述】:
#include <iostream>
template<typename T>
struct A{
    template<typename U>
    struct B{};
    B<T> b;   //#2
};
//#3
int main() {
    A<int> a;  // #1
}

考虑上面的代码,模板 ID A&lt;int&gt; 的使用会导致专门化 A&lt;int&gt; 的隐式实例化。根据以下规则:

temp.point#4

对于类模板特化、类成员模板特化或类模板的类成员的特化,如果该特化是隐式实例化的,因为它是从另一个模板特化中引用的,如果上下文来自引用哪个特化取决于模板参数,如果特化没有在封闭模板的实例化之前被实例化,则实例化点就在封闭模板的实例化点之前否则,这种特化的实例化点紧接在命名空间范围声明或引用特化的定义之前。

对于#1 中引用的专业化,该规则的Otherwise 部分将适用于它。这意味着,A&lt;int&gt; 的实例化点应该在#3,这里没有问题。但是,A&lt;int&gt; 的实例化将导致作为其成员的专门化B&lt;int&gt; 的实例化。因此,该规则的 if 部分将适用于 B&lt;int&gt;。我感到困惑的是这里。根据相关规则,B&lt;int&gt;的实例化点应该是紧接在封闭模板的实例化点之前,也就是#3之前的某个地方,我这里看不懂。如果,通过normal class思考,定义其类成员的方式只有两种,一种是在类定义中定义类成员,另一种是在类定义中声明类成员,然后定义类定义之外的类成员在类定义之后的某个点。

使用普通类更改示例:

struct Normal_A_Int{
   struct Normal_B_Int{};
   Normal_B_Int b;
};
int main(){
  Normal_A_Int a;
}

也就是说,成员类Normal_B_Int的定义必须在封闭类的定义中,因为声明Normal_B_Int b需要一个完整的类类型。

那么,如何将成员类B&lt;int&gt; 的定义放在封闭类A&lt;int&gt; 的定义之前?充其量,B&lt;int&gt; 的定义应在A&lt;int&gt; 的定义中。第一个例子的兴趣点如何解释?

【问题讨论】:

  • 是否在任何地方指定了“实例化点”的意思是“在该点放置定义”?
  • @LanguageLawyer 该标准并未明确说明这一点,但是,temp.inst#10 规则和 [temp.point#4] 暗示了这一点。
  • @dfrib 是的,对于函数特化,有两个 POI(一个由规则指定,另一个是 TU 的结尾),但是,对于类特化,只有一个 POI 被指定按相关规定。恕我直言,POI 应该用英文理解,即实例化发明的地方,对吧?
  • 是的,我现在看到[temp.point]/8 在草案中甚至被澄清(现在为 [temp.point]/7),明确指出类模板的专业化最多有一个 POI一个TU。
  • 啊,@DavisHerring 是 P1787 的作者!也许他可以说我是否在我的文字答案墙中准确地反映了一些事情。

标签: c++ templates language-lawyer


【解决方案1】:

您从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&lt;T&gt;::B&lt;T&gt; 的实例化点在A&lt;T&gt; 之前?最终,因为标准是这样说的。但是考虑一下,如果是在之后,你根本就没有内部类模板。如果它在A&lt;T&gt; 的定义中,名称查找将出现错误,因为A 的定义和A&lt;int&gt; 的实例化点之间的名称在A&lt;int&gt;::B&lt;int&gt; 中不可见。所以这实际上是有道理的。

也许直觉来自混淆定义和实例化点:

那么,如何将成员类 B 的定义放在封闭类 A 的定义之前?

不是。实例化点控制名称的可见性。 TU 中截至该点的所有名称都是可见的。 (它不是定义。)从这个角度来看,很明显A&lt;int&gt;::B&lt;int&gt;应该有一个接近A&lt;int&gt;的实例化点(他们应该看到相同的其他名称)。如果有排序,可能内部应该排在第一位(这样A&lt;int&gt; 可以有一个A&lt;int&gt;::B&lt;int&gt; 成员)。如果没有顺序,则必须有关于实例化如何交错或交互的语言。

这条规则有两个有趣的方面。

一个是模板可以被专门化。所以,要完成这个要求,当编译器去实例化A&lt;int&gt;时,它必须首先选择特化,处理它足以知道它有一个成员类模板并且它需要实例化它,然后停止一切去实例化A&lt;int&gt;::B&lt;int&gt; 首先。这并不难,但很微妙。确实有一个实例化堆栈。

第二个方面更有趣。您可能会从普通的类直觉中期望B&lt;T&gt; 的定义可以使用来自A&lt;T&gt; 的东西(例如typedefs),这将在模板上下文中需要A&lt;T&gt; 的实例化,而A&lt;T&gt;::B&lt;T&gt; 正在执行时还不存在。实例化。喜欢:

template<typename T>
struct A {
    using type = T;

    template<typename U>
    struct B { using type = typename A<U>::type; };

    B<T> b;
};

这样好吗?

如果A&lt;int&gt;::B&lt;int&gt;的实例化点在A&lt;int&gt;之前,我们不能真正形成A&lt;int&gt;::type

这是CWG287P1787的领域。

CWG287 建议实例化点相同(一个不在另一个之前)。此外,它会添加:

如果隐式实例化的类模板特化、类成员特化或类模板的特化引用了包含直接或间接导致实例化的特化引用的类、类模板特化、类成员特化或类模板的特化, 类引用的完整性和顺序的要求应用在专业化引用的上下文中。

在我的示例中,A&lt;int&gt;::B&lt;int&gt; 引用 A&lt;int&gt; 直接通过引用 B&lt;int&gt; 导致其实例化,因此类引用 (typename A&lt;int&gt;::type) 的完整性和顺序要求适用于专业化参考 (B&lt;int&gt; b)。所以没关系。如果 typedef 低于B 的定义,仍然可以。但是如果我们将 typedef 移到成员 b 的下方,它会是不正确的!微妙的!这具有交错实例化的效果。当我们看到该成员时,我们停止我们正在做的事情,去实例化A&lt;int&gt;::B&lt;int&gt;,但是我们可以使用我们在实例化A&lt;int&gt; 时的顺序和完整性要求。实例化点是相同的,因此我们也可以使用 TU 中的相同声明。

CWG287 似乎旨在复制编译器已经做的事情。但是,CWG287 自 2001 年以来一直开放。(另请参阅 thisthis。)

P1787 似乎是针对 C++23 的,旨在重写很多微妙的语言。我认为旨在产生与 CWG287 类似的效果。但要这样做,他们必须彻底重新定义名称查找,我很难知道这一点。 :)

【讨论】:

  • 一个很好的答案。您的意思是 POI 的目标用于名称查找(在 POI 中可以看到哪个名称)。那么,相应专业化的发明定义在哪里呢? AFAIK,标准对此只字未提,你怎么看?恕我直言,我只是认为 POI 是定义专业化假设定义的地方。和你说的不冲突。
  • 这个result可以反映相应实体的发明定义在哪里。为什么标准没有直接说明专门化的发明定义在哪里,毕竟方便理解,每个名称查找规则都是直接引用的(普通实体的方式),包括你引用的有趣示例。
【解决方案2】:

【讨论】:

  • 这并不能真正回答问题。此外,对于语言律师问题,您应该通过引用标准文本来支持您的主张。
  • 好吧,那我得退了,我不想当语言律师:)。
  • 这不是真的。对于您第一次替换,隐式实例化的规则是:类模板特化的隐式实例化导致声明的隐式实例化,但不是定义、默认参数或 noexcept 说明符类成员函数、成员类、作用域成员枚举、静态数据成员、成员模板和朋友;这意味着,A&lt;int&gt; 的实例化没有定义成员类模板 B,而是对其进行声明。
  • @jackX 不用担心。答案本身并没有真正需要反驳的主张。
  • 不用担心,这并不适合所有人:) 如果您不想回答问题,可以继续删除它。否则你可能会收到反对票。
猜你喜欢
  • 2019-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-10
  • 1970-01-01
相关资源
最近更新 更多