【问题标题】:Trying to understand templates and name lookup试图理解模板和名称查找
【发布时间】:2019-12-16 09:16:52
【问题描述】:

我试图理解下面的代码sn-ps

片段 #1

template <typename T>
struct A
{
    static constexpr int VB = T::VD;
};

struct B : A<B>
{
};

gcc9 和 clang9 都不会在这里抛出错误。

问。为什么这段代码会编译?从B继承时我们不是实例化A&lt;B&gt;吗? B中没有VD,所以编译器不应该在这里抛出错误吗?

片段 #2

template <typename T>
struct A
{
    static constexpr auto AB = T::AD; // <- No member named AD in B
};

struct B : A<B>
{
    static constexpr auto AD = 0xD;
};

在这种情况下,gcc9 可以正常编译,但 clang9 会抛出错误消息“B 中没有名为 AD 的成员”。

问。为什么用gcc9编译/为什么不能用clang9编译?

片段#3

template <typename T>
struct A
{
    using TB = typename T::TD;
};

struct B : A<B>
{
    using TD = int;
};

这里 clang9 和 gcc9 都会抛出一个错误。 gcc9 说“无效使用不完整类型'struct B'”。

问。如果这里的 struct B 不完整,那为什么在 sn-p #2 中不完整呢?

使用的编译器标志:-std=c++17 -O3 -Wall -Werror。提前致谢!!!

【问题讨论】:

  • @xception 不是struct BB 实例化A 吗?
  • clang9 抛出错误,提示“B 中没有名为 AD 的成员”。因为B 不完整......但不确定何时应该实例化成员......
  • @MutableSideEffect 哦,是的,我的错,也将其作为模板阅读:(
  • 我已将此问题标记为“需要更多关注”,并且该问题确实包含多个问题(因此得出我的结论),那么为什么我的标记错误?
  • @Dominique 好像是used as an audit 15 times so far。这些审计中只有一项失败了。根据下面的答案,看起来子问题是紧密耦合的,因为它们是关于非常相似的事情(即探索same section of the specification 的不同方面)。但是,我不是主题专家,所以我会听从 wrt 的人。决定子问题是否紧密耦合,以免问题过于宽泛。

标签: c++ templates language-lawyer


【解决方案1】:

我相信这些基本上可以归结为[temp.inst]/2(强调我的):

类模板特化的隐式实例化导致声明的隐式实例化,而不是定义的隐式实例化、默认参数或 noexcept-specifiers of 类成员函数、成员类、作用域成员枚举、静态数据成员、成员模板和朋友; […]

[temp.inst]/9

除非需要实例化,否则实现不应隐式实例化 [...] 类模板的静态数据成员 [...]。

标准中关于隐式模板实例化的措辞留下了许多可供解释的细节。一般来说,在我看来,除非规范明确说明,否则您根本不能依赖模板的部分被实例化。因此:

片段 #1

问。为什么这段代码会编译?从 B 继承时我们不是实例化 A 吗? B中没有VD,所以编译器不应该在这里抛出错误吗?

您正在实例化A&lt;B&gt;。但是实例化A&lt;B&gt; 只会实例化声明,而不是其静态数据成员的定义。 VB 永远不会以需要定义存在的方式使用。编译器应该接受这个代码。

片段 #2

问。为什么用gcc9编译/为什么不能用clang9编译?

正如 Jarod42 所指出的,AB 的声明包含一个占位符类型。在我看来,标准的措辞并不清楚这里应该发生什么。包含占位符类型的静态数据成员声明的实例化是否会触发占位符类型推导,从而构成需要定义静态数据成员的使用?我在标准中找不到明确表示是或否的措辞。因此,我想说这两种解释在这里同样有效,因此,GCC 和 clang 都是正确的……

片段#3

问。如果这里的 struct B 不完整,那为什么在 sn-p #2 中不完整呢?

类类型仅在您到达 class-specifier [class.mem]/6 的结束 } 时才完成。因此,B 在所有 sn-ps 中隐式实例化 A&lt;B&gt; 期间是不完整的。只是这与 Snippet #1 无关。在 Snippet #2 中,clang 确实给了你一个错误 No member named AD in B 结果。与 Snippet #2 的情况类似,我找不到关于何时确切地实例化成员别名声明的措辞。但是,与静态数据成员的定义不同,在类模板的隐式实例化期间,没有明确的措辞来明确阻止成员别名声明的实例化。因此,我会说 GCC 和 clang 的行为在这种情况下是对标准的有效解释……

【讨论】:

  • 谢谢。在这种情况下,我们在正文中初始化静态数据成员。初始化是声明的一部分,还是静态数据成员定义的一部分?我的印象是,如果初始化在主体内,那么这是静态数据成员声明的一部分。如果它是声明的一部分,那么您的第一个引用需要立即实例化,作为类模板周围隐式实例化的一部分。
  • 我查看了规范,发现这里的 C++14 和 C++17 之间存在差异。在 C++14 中,constexpr 静态数据成员只是一个声明。 C++17 获得了inline 变量和constexpr 隐含inline,这使得体内静态数据成员声明成为一个定义。
  • eel.is/c++draft/dcl.spec.auto#4.sentence-2 说“使用占位符类型声明的变量的类型是从它的初始化程序推导出来的。在变量的初始化声明 ([dcl.init]) 中允许使用这种用法。”。因此,我认为静态数据成员的定义是必需的,因为它包含初始化程序。
  • @JohannesSchaub-litb 感谢您对此进行调查!我也一直想知道这些问题,但找不到任何可以确定的措辞。关于初始化与定义的事情,考虑类模板定义中成员函数的定义。这样的定义也是一个声明,没有其他声明。然而,类模板的隐式实例化只会实例化一个声明,而不是成员函数的定义。为什么静态数据成员不一样?
  • 如果没有什么需要定义,则不会实例化定义。但是在auto 的情况下,规则说声明必须是初始化声明。只有在知道声明是一个定义的情况下才会出现这种情况(据我所知..我已经离开律师界有一段时间了)。过去也有类似的情况,添加了eel.is/c++draft/temp.inst#2.sentence-3,其中定义的声明被实例化为“被称为定义”,而没有实际实例化定义。
猜你喜欢
  • 2013-04-26
  • 2023-03-17
  • 1970-01-01
  • 2019-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-27
  • 1970-01-01
相关资源
最近更新 更多