【问题标题】:odd C++ namespace resolution quirk and g++ vs clang++ [duplicate]奇怪的 C++ 命名空间解析怪癖和 g++ vs clang++ [重复]
【发布时间】:2018-01-06 01:09:14
【问题描述】:

这始于一次观察。我更改了一些看起来有点像这样的代码(编辑:我在这里取出了指定的初始化程序,它们也不在原始代码中):

struct S {
    enum E { E1, E2 } member;
}

// file1.cc
S v1 = { S::E1 };

// file2.cc
S v2 = { S::S::E2 };

请注意,file2.cc 过度限定了E2。然而,这在 g++ 和 clang++ 中都有效。 (编辑 2:这个特定 VM 上的 g++ 是 g++-5.4.1,但原始代码经过了早期和后来的 g++ 版本,以及多个 clang 版本。)事实上,我们可以这样写:

S v3 = { S::S::S::S::S::S::S::E1 };

(无论我们喜欢多少S::s),无论我们喜欢什么。我改变了一些东西,使S 不再是一个普通的struct,而是一个模板化的,之后它就停止工作了。没什么大不了的,但它让我很好奇,所以我做了实验。

如果我们将其更改为非 POD 类型:

struct S {
    S() { std::cout << "made an S" << std::endl; }
    enum E { E1, E2 } member;
}

(使用适当的#include)不再允许。 Clang 和 g++ 产生不同的诊断。这是 clang 的抱怨:

namespace.cc:8:3: error: no matching constructor for initialization of 'S'
S x = { .member = S::S::E1 };
namespace.cc:3:8: note: candidate constructor (the implicit copy constructor)
      not viable: cannot convert argument of incomplete type 'void' to
      'const S &' for 1st argument
struct S {
       ^
namespace.cc:3:8: note: candidate constructor (the implicit move constructor)
      not viable: cannot convert argument of incomplete type 'void' to 'S &&'
      for 1st argument
struct S {
       ^
namespace.cc:4:3: note: candidate constructor not viable: requires 0 arguments,
      but 1 was provided
  S() { std::cout << "made an S\n"; }
  ^
1 error generated.

和 g++ 的:

namespace.cc:8:28: error: could not convert ‘{E1}’ from ‘<brace-enclosed initializer list>’ to ‘S’
 S x = { .member = S::S::E1 };

这些似乎遵循不同的规则。这是怎么回事?

接下来,让我们尝试另一种滥用。这是整个程序:

#include <iostream>

struct S {
  S() { std::cout << "made an S\n"; }
  enum E { E1, E2 } member;
};

int main() {
  std::cout << S::S::S::S::S::E1 << std::endl;
#ifdef DECL
  S::S::S var;
#endif
  return 0;
}

这段代码在两个编译器中都可以编译(没有-DDECL):

$ clang++-3.9 -std=c++11 -Wall -O namespace.cc
$ ./a.out
0
$ g++ -Wall -std=c++11 -O namespace.cc
$ ./a.out
0

(尽管早期代码中的变量 member 初始化程序发出了抱怨,但这里没有构造 S。)但是,启用 main 中的变量会导致 g++ 失败,但不会导致 clang :

$ clang++-3.9 -std=c++11 -DDECL -Wall -O namespace.cc
$ ./a.out 
0
made an S
$ g++ -std=c++11 -DDECL -Wall -O namespace.cc
namespace.cc: In function ‘int main()’:
namespace.cc:11:3: error: ‘S::S’ names the constructor, not the type
   S::S::S var;
   ^
namespace.cc:11:11: error: expected ‘;’ before ‘var’
   S::S::S var;
           ^
namespace.cc:11:14: error: statement cannot resolve address of overloaded function
   S::S::S var;
              ^

哪个编译器是正确的,为什么?究竟这个“过度限定”名称的规则是什么?

【问题讨论】:

  • 它们最终也是 C++20 的一个特性,但它只是 2018 年,所以它们不是标准的。
  • 请注意,上面提到的 clang 错误据说已在 clang 5 中修复,实际上 clang 5 并没有重现该差异。 wandbox.org/permlink/cwYV0XhkzouaHBxf
  • 顺便说一句,"如果我们将其更改为非 POD 类型:" 您在此处禁用了聚合初始化,因此您实际上需要一个匹配的构造函数。
  • “什么规则,如果有的话,允许这种过度资格,什么时候?” - 第 9 条,[class],第 2 段,“class-name 也被插入到类本身的范围内;这被称为 injected-class-name. 出于访问检查的目的,注入的类名被视为公共成员名。” - C++03

标签: c++ c++11 language-lawyer


【解决方案1】:

Yakk 已经解决了您问题的指定初始化程序部分。我将解决你问题的最后一部分。 S::S::S var(在此上下文中)有效吗?不,根据class.qual#2

在不忽略函数名称的查找中34 和 嵌套名称说明符指定一个类 C:

  • 如果在嵌套名称说明符之后指定的名称,当在 C 中查找时,是 C 的注入类名称(子句 [类]),或者

  • 在作为成员声明的 using-declaration 的 using-declarator 中,如果在 嵌套名称说明符与标识符或 simple-template-id 的模板名称在最后一个组件中 嵌套名称说明符,

该名称被认为是命名类 C 的构造函数。

为了使其有效,您需要明确说出struct S::S::S var。所以 clang 3.9 是错误的。

此外,Rob 的评论与此处无关。 S::S 只是在类定义中查找时注入的类名。

【讨论】:

    【解决方案2】:

    指定初始值设定项是 中 C++ 的一项 C 功能。它们是 C++ 模式中的常见扩展。显然,此时它们仅限于 pod(类 C)类型。他们打破并不奇怪。

    一般来说,不同的编译器会对不正确的代码产生不同的错误,尤其是在使用 C++ 扩展时。 C++ 标准从不强制要求诊断的内容(可能有一个例外,但我不记得是什么)。

    gcc 有一个扩展,可以让您命名对象构造函数。这与您对结构命名空间的病态使用相冲突。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-26
      • 1970-01-01
      • 2021-08-29
      • 2017-05-10
      • 1970-01-01
      • 2020-01-14
      • 1970-01-01
      相关资源
      最近更新 更多