【问题标题】:Program with chaining of using-declarations compiles on MSVS and clang but not on GCC带有 using-declarations 链接的程序可以在 MSVS 和 clang 上编译,但不能在 GCC 上编译
【发布时间】:2015-09-22 08:42:06
【问题描述】:

根据 c++ 标准,以下程序是良构的还是良构的?

namespace X { int i; }

namespace Y { using X::i; }

int main() { using X::i; using Y::i; }

我使用不同的编译器得到不同的结果:

我不想修复这个程序以使其在 GCC 上编译。我只想知道 c++ 标准对此有何评论,以及为什么这三个编译器的行为不同。如果这是任何这些编译器中的错误的结果,我也想这样做。

【问题讨论】:

标签: c++ declaration language-lawyer using-declaration


【解决方案1】:

程序不应编译,因为它在同一块范围内声明了两次X::i

C++14 §7.3.3/10:

using-declaration 是一个 declaration,因此可以重复使用 where(并且仅 where)多个 允许声明。 [例子:

namespace A {
    int i;
}

namespace A1 {
    using A::i;
    using A::i;           // OK: double declaration
}

void f() {
    using A::i;
    using A::i;           // error: double declaration
}

编辑:上面引用的非规范评论,我认为它回答了这个问题,最初是在 C++98 中,并且在技术勘误 1 中幸存下来(C++03)、C++11 和 C++14。但显然这是错误。 Richard Smith 在他的回答中引用了 core issue 36 关于它,首先由 Andrew Koenig 于 1998 年 8 月 2nd 提出(在 ANSI 批准第一个标准后不到一个月),这显然意味着一个已知的不正确注释可以在标准的三个修订版中保留。

引用核心问题本身:

C++ 标准核心语言活动问题,第 36 期:

04/00 会议记录:
核心语言工作组无法就 using-declaration 应仿效哪种声明达成共识。在一次草率投票中,7 名成员赞成在任何可能出现非定义声明的地方允许 using-declarations,而 4 名成员则倾向于只在命名空间范围内允许多个 using-eclarations(理由是多个 using-declarations 的权限主要是为了支持它在多个头文件中的使用,这些头文件很少包含在命名空间范围以外的任何地方)。 John Spicer 指出 friend 声明可以在类范围内出现多次,并询问 using-declarations 在“类似声明”决议下是否具有相同的属性。

由于没有达成一致,问题被退回到“开放”状态。

在 C++98 和 C++14 的第 3.3.1/4 节中对同名的多个声明进行了一般性讨论。据我所见,C++14 文本与原始 C++98 文本一字不差。并且它本身允许在许多情况下在同一个声明区域中多次声明同一个名称,其中一种是所有声明都引用同一个实体:

C++14 §3.3.1/4:

给定单个声明区域中的一组声明,每个声明都指定相同的非限定名称,

  • 它们都应该指代同一个实体,或者都指代函数和函数模板;或

  • 只有一个声明应该声明一个不是 typedef 名称的类名或枚举名 并且其他声明都应该引用相同的变量或枚举器,或者都引用函数 和功能模板;在这种情况下,类名或枚举名是隐藏的(3.3.10)。 [注意: A 命名空间名称或类模板名称在其声明区域中必须是唯一的(7.3.2,第 14 条)。 ——尾注]

但是,这里的措辞只说明了不直接无效的内容。一个声明可以被其他规则禁止,即使它没有被这条规则禁止。比如类成员声明就有这样一个限制:

C++14 §9.2/1:

[...] 一个成员不得在 member- 规范,除了可以声明嵌套类或成员类模板,然后再定义, 并且除了可以使用 opaque-enum-declaration 引入枚举并随后重新声明之外 带有 枚举说明符

我找不到支持上面开头引用的 C++14 §7.3.3/10 中明显不正确的注释的这种限制,即我找不到块作用域或命名空间作用域的任何特殊处理,所以一个初步的结论(请记住,尽管在 1998 年就已经存在争议,但该评论仍然存在)是有争议的评论实际上是错误的,并且该问题的代码(同一声明区域中的两个声明引用同一实体)是有效的,并且应该被所有编译器接受。

【讨论】:

  • 需要诊断吗? MSVS 和 Clang 中的错误?
  • @Supremum:该段落没有说“不需要诊断”,因此根据 §1.4/1 和根据 §1.4/2,“如果程序包含违规”是可诊断的规则任何可诊断规则或本标准中描述为“有条件支持”的构造的出现,当实现不支持该构造时,符合要求的实现应发出至少一个诊断消息。”
  • 那么我们在 MSVS 和 Clang 中有一个 bug。
  • @Supremum:这是一个link to the relevant parts of Microsoft Connect 用于报告此类错误。这是我在那里报告的最新错误。如您所见,最近发生了一些变化,例如描述重现的步骤,但我只使用了 cmets。
  • 谢谢,我会向 MSVS 和 Clang 报告错误。
【解决方案2】:

Clang 和 MSVC 是正确的;此代码有效。正如 Alf 所说,[namespace.udecl] (7.3.3)/10

using-declarationdeclaration,因此可以在允许(且仅在)允许多个声明的地方重复使用。

但是,在块范围内对同一实体的多个声明没有限制,因此原始示例是有效的。一个不涉及using-declaration的对应案例是:

int n;
void f() {
  extern int n;
  extern int n;
}

这是有效的(并且被 GCC、EDG、Clang 和 MSVC 接受),因此(根据上面引用的规则)原始示例也是有效的。

值得注意的是,[namespace.udecl] (7.3.3)/10 中的示例包含错误。它说:

namespace A {
  int i;
}

void f() {
  using A::i;
  using A::i; // error: double declaration
}

...但是评论不正确;第二个声明没有错误。请参阅core issue 36 中的讨论。我在标准中使用了removed the example,以免让更多人感到困惑。

【讨论】:

  • 是的,看起来标准中的示例不正确。为什么他们很久以前不删除它?
  • @Supremum 我同意,所以我removed the example
  • 太棒了!如果我正确理解核心问题 36,它仍然是开放的,因为尚未解决的歧义(未达成协议)。我猜这种模棱两可意味着GCC在这里也没有错误,它只是选择了不同于clang的其他解释?这应该可以解释行为上的差异。
  • 您能否也看看OP的其他两个相关问题? (This one, that one)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-09
  • 1970-01-01
  • 2020-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多