【问题标题】:When instantiating a template, should members of its incomplete argument types be visible?实例化模板时,其不完整参数类型的成员是否可见?
【发布时间】:2013-07-02 22:49:35
【问题描述】:

在下面的示例中,A 有一个成员 typedef Instantiate,它会导致 B<A> 的实例化。

template<typename T>
struct B
{
    typedef typename T::Before Before; // ok
    typedef typename T::After After; // error: no type named 'After' in 'A<int>'
};

template<typename T>
struct A
{
    typedef int Before;
    typedef typename B<A>::After Instantiate;
    typedef int After;
};

template struct A<int>; // instantiate A<int>

我尝试过的所有编译器都报告说,A::Before 可见,A::After 不可见。这种行为是否符合标准?如果是这样,标准在哪里指定A 中的哪些名称应该在B&lt;A&gt; 的实例化期间可见?

如果从属名称“在模板实例化点查找”,那么在T::After 等模板参数限定名称的场景中,这意味着什么?

编辑:请注意,当 A 不是模板时会发生相同的行为:

template<typename T>
struct B
{
    typedef typename T::Before Before; // ok
    typedef typename T::After After; // error: no type named 'After' in 'A'
};

struct A
{
    typedef int Before;
    typedef B<A>::After Instantiate;
    typedef int After;
};

.. 和 G++ 接受以下内容,但 Clang 不接受:

template<typename T>
struct B
{
    static const int value = 0;
    static const int i = T::value; // clang error: not a constant expression
};

struct A
{
    static const int value = B<A>::value;
};

编辑:阅读 C++03 标准后:

[temp.dep.type] 类型是依赖的,如果它是一个模板参数

因此T 是依赖的。

[temp.res] 在查找模板定义中使用的名称声明时,通常的查找规则用于非依赖名称。依赖于模板参数的名称查找被推迟到知道实际的模板参数。

T::After 的查找因此被推迟,直到知道T 的参数为止。

[temp.inst] 除非已显式实例化类模板特化......当在需要完全定义的对象类型的上下文中引用特化时,类模板特化将被隐式实例化。

因此A&lt;int&gt;::Instantiate 的声明需要B&lt;A&gt; 的实例化(因为它用于嵌套名称说明符中。)

A&lt;int&gt;::After 在声明A&lt;int&gt;::Instantiate 时不可见,因此编译器的行为是有道理的——但我在 C++03 中没有看到任何明确描述此行为的内容。最接近的是这个有点模糊的段落:

[温度.dep.res] 在解析从属名称时,会考虑来自以下来源的名称:

——在模板定义处可见的声明。

【问题讨论】:

  • 我会说这些是从属名称 [temp.dep],它们在模板的实例化点 [temp.dep]/1 -> [temp.point]
  • 我把那段读了一百遍,但直到现在才有意义..
  • @DyP 你应该这样回答 ;)
  • @DyP:我同意 Obvlious 船长的观点。
  • 嗯,不,我认为在不完整类型的名称查找中仍然缺少一些东西;但我可能错了。

标签: c++ templates language-lawyer


【解决方案1】:

规范没有明确说明typename T::Before 是否有效。它是缺陷报告的主题(因为可以非常合理地阅读标准以禁止它):http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#287

typename T::After 是否无效也可以非常合理地被规范解读为正确,实际上它很有道理(并且前面提到的 DR 仍然保持其格式错误)。因为您有一个类A&lt;Foo&gt; 的实例化,它在成员Baz 尚未声明的期间引用另一个类A&lt;Bar&gt;,并且引用回A&lt;Foo&gt;::Bar。这在非模板的情况下也是不正确的(尝试“忘记”你正在处理模板的片刻:B&lt;A&gt;::After 的查找肯定是在A 之后完成的模板已被完全解析,但在它的特定实例化完全创建之后没有。它的实例化实际上会做引用!)。

struct A {
   typedef int Foo;
   typedef A::Foo Bar; // valid
   typedef A::Baz Lulz; // *not* valid
   typedef int Baz; 
};

【讨论】:

  • “一致认为,就成员声明/定义和类完成的顺序而言,模板案例应与非模板类案例相同。”我想这定义了“合规”行为;)
  • 这是 2001 年的缺陷报告,他们还没有解决?还是我太糊涂了?
  • @DyP 有多个 10 岁以上的 DR :)
【解决方案2】:

T::BeforeT::After 由于 [temp.dep.type]/8 和 /5 是从属名称。

“在模板定义的上下文和实例化点的上下文中,在模板实例化点 (14.6.4.1) 查找依赖名称”。 [temp.dep]/1

我将其解释为:实例化模板时会查找它们。他们在哪里抬头?在模板定义的上下文和实例化点的上下文中。

[temp.dep.type]/7 另一方面表示:

如果对于给定的一组模板参数,一个模板的特化被实例化,该模板使用 qualified-id 或类成员访问表达式引用当前实例化的成员,则在qualified-id 或类成员访问表达式在模板实例化上下文中查找。

[temp.point]/7 定义实例化上下文如下:

依赖于模板参数的表达式的实例化上下文是在同一翻译单元中模板特化的实例化点之前声明的具有外部链接的声明集。

因此,我们需要知道实例化的意义是什么。

[温度点]/4

对于一个类模板特化 [...],如果特化是隐式实例化的,因为它是从另一个模板特化中引用的,如果特化被引用的上下文依赖于模板参数,并且特化是在实例化之前未实例化 封闭模板,实例化点就在封闭模板的实例化点之前。

虽然注入的类名A 可以说是一个依赖(作为外行术语)A 的模板参数的上下文,但名称A 本身并不是一个从属名称。Johannes Schaub 更正:它一个从属名称。请参阅 [temp.local]/1 和 [temp.dep.type]/8 => A 是依赖类型。

因此,这个条件 not满足,B&lt;A&gt;应该在A&lt;int&gt;之前实例化。

【讨论】:

  • hmm.. 如果B&lt;A&gt; 更改为B&lt;A, T&gt;,是否会更改B 的实例化点?
  • 我很确定名称查找行为是由于 A 的“完整性”,而不是实例化点。
  • @willj 我发现了一条适用于此处的名称查找特殊规则;但实例化点仍然发挥着重要作用。
  • 好的,只有一件事:“表达式的实例化上下文...”示例中没有“表达式”,使用严格的 C++ 定义.
  • 例子中有id-expression
猜你喜欢
  • 2016-05-20
  • 2011-12-14
  • 2017-11-21
  • 2019-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-22
相关资源
最近更新 更多