成员在不同名称空间中的别名声明专门化

sul*_*uke 12 c++ gcc clang c++11 clang++

我刚刚在clang和gcc之间遇到了一个奇怪的区别,我想编译看起来类似于以下内容的代码:

namespace n1 {
  template <class T1, class T2>
  struct MyTemplate {
    struct Inner {};
  };
}

using namespace n1;
namespace n2 {
  using MyClass = MyTemplate<int, int>;
}

namespace n1 {
  using n2::MyClass;
  template<> struct MyClass::Inner {
    int member;
  };

  MyClass::Inner inner{0};
}
Run Code Online (Sandbox Code Playgroud)

Clang愉快地编写了这个:

$ clang++ -std=c++11 -c -o alias_specialization.o alias_specialization.cc
Run Code Online (Sandbox Code Playgroud)

但是gcc会抛出以下错误:

$ g++ -std=c++11 -c -o alias_specialization.o alias_specialization.cc

alias_specialization:15:30: error: declaration of ‘struct n1::MyTemplate<int, int>::Inner’ in namespace ‘n1’ which does not enclose ‘using MyClass = struct n1::MyTemplate<int, int>’
   template<> struct MyClass::Inner {
Run Code Online (Sandbox Code Playgroud)

我知道我只能写出原始类型(MyTemplate<int, int>)的全名,而不是MyClass第15行.但我只是想知道这两个编译器中的哪一个是"正确的".使用的确切编译器是:

$ clang++ --version
clang version 4.0.0

$ g++ --version
g++ (GCC) 6.3.1 20170306
Run Code Online (Sandbox Code Playgroud)

ova*_*nes 4

免责声明海湾合作委员会Clang 是对的(正如 @suluke 指出的,使用下面的发现)。

\n\n

我在 C++14 标准中找到了以下段落,但我确信这同样适用于 C++11:

\n\n

第一部分:模板的专业化和实例化

\n\n

inline namespace(\xc2\xa77.3.1 cl.8):

\n\n
\n

内联命名空间的成员可以在大多数方面使用,就像它们是封闭命名空间的成员一样。具体来说,内联命名空间及其封闭命名空间都被添加到参数相关查找(3.4.2)中使用的一组关联命名空间中,只要其中一个是,以及命名内联命名空间的 using- 指令(7.3.4)与未命名的命名空间 (7.3.1.1) 一样,隐式插入到封闭的命名空间中。此外,内联命名空间的每个成员随后可以部分专门化(14.5.5)、显式实例化(14.7.2)或显式专门化(14.7.3),就好像它是封闭命名空间的成员一样。最后,通过显式限定 (3.4.3.2) 在封闭命名空间中查找名称将包括 using 指令引入的内联命名空间的成员,即使封闭命名空间中有该名称的声明也是如此。

\n
\n\n

显式实例化 (\xc2\xa7 14.7.2 cl. 3)

\n\n
\n

如果显式实例化是针对类或成员类,则声明中的详细类型说明符应包含简单模板 ID。如果显式实例化是针对函数或成员函数,则声明中的 unqualified-id 应该是 template-id,或者(在所有模板参数都可以推导的情况下)是 template-name 或operator-function-id。[注意:声明可以声明一个qualified-id,在这种情况下qualified-id的unqualified-id必须是一个template-id。\xe2\x80\x94end note] 如果显式实例化是针对类模板特化的成员函数、成员类或静态数据成员,则成员名称的qualified-id中的类模板特化的名称应为一个简单模板 ID。如果显式实例化是针对变量,则声明中的 unqualified-id 应为 template-id。显式实例化应出现在其模板的封闭命名空间中。如果显式实例化中声明的名称是非限定名称,则显式实例化应出现在声明其模板的命名空间中,或者如果该命名空间是内联的(7.3.1),则应出现在其封闭命名空间集中的任何命名空间中。

\n
\n\n

显式实例化 (\xc2\xa7 14.7.2 cl. 6)

\n\n
\n

类、函数模板或变量模板特化的显式实例化放置在定义模板的命名空间中。类模板成员的显式实例化放置在定义封闭类模板的命名空间中。成员模板的显式实例化放置在定义封闭类或类模板的命名空间中。[ 例子:

\n
\n\n
namespace N {\n    template<class T> class Y { void mf() { } };\n  }\n  template class Y<int>;  // error: class template Y not visible \n                          // in the global namespace\n\n  using N::Y;\n  template class Y<int>; // error: explicit instantiation outside of the \n                         // namespace of the template\n\n\n  template class N::Y<char*>;  // OK: explicit instantiation in namespace N\n  template void N::Y<double>::mf();  // OK: explicit instantiation\n                                     // in namespace N\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

\xe2\x80\x94 结束示例]

\n
\n\n

第二部分:Typedef、别名

\n\n

什么是 typedef 或别名?

\n\n

\xc2\xa77.1.3 cl。1 状态:

\n\n
\n

[...]\n 使用 typedef 说明符声明的名称成为 typedef 名称。在其声明范围内,typedef-name 在语法上等同于关键字,并以第 8 条中描述的方式命名与标识符关联的类型。因此,typedef-name 是另一种类型的同义词。typedef-name 不会像类声明 (9.1) 或枚举声明那样引入新类型。

\n
\n\n

\xc2\xa77.1.3 cl。2 个状态:

\n\n
\n

typedef-name 也可以通过alias-declaration引入。using 关键字后面的标识符成为 typedef-name,并且标识符后面的可选 attribute-specifier-seq 属于该 typedef-name。它具有与 typedef 说明符引入的语义相同的语义。特别是,它没有定义新类型,并且不应出现在 type-id 中。

\n
\n\n

第三部分:测试您的案例

\n\n

按照您的逻辑,以下代码应该编译并生成 2 种不同的类型。我用 Clang 测试了它,这是有问题的编译器。Clang 生成与其所需的标准相同的类型。

\n\n
#include <iostream>\n\nnamespace n1 {\n  template <class T1, class T2>\n  struct MyTemplate {\n    struct Inner {};\n  };\n}\n\nusing namespace n1;\nnamespace n2 {\n  using MyClass = MyTemplate<int, int>;\n}\n\nnamespace n3 {\n  using MyClass = MyTemplate<int, int>;\n}\n\nnamespace n1 {\n  using n2::MyClass;\n  template<> struct MyClass::Inner {\n    int member;\n  };\n\n  MyClass::Inner inner{0};\n}\n\nnamespace n4{\n  using n3::MyClass;\n\n  MyClass::Inner inner{0};\n}\n\nint main()\n{\n  using namespace std;\n\n  cout << typeid(n1::inner).name() << endl;\n  cout << typeid(n4::inner).name() << endl;\n\n  return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

汇编

\n\n
c++ -std=c++14 typedef-typeid.cpp -O3 -o compiled.bin\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者

\n\n
c++ -std=c++11 typedef-typeid.cpp -O3 -o compiled.bin\n
Run Code Online (Sandbox Code Playgroud)\n\n

两种情况下的输出

\n\n
N2n110MyTemplateIiiE5InnerE\nN2n110MyTemplateIiiE5InnerE\n
Run Code Online (Sandbox Code Playgroud)\n\n

事实证明,在这种情况typedef下 和using是等价的,我们可以使用typedef代替using

\n\n

\xc2\xa77.3.3 cl。1指出了这一点:

\n\n
\n

[...] 如果 using 声明命名了一个构造函数 (3.4.3.1),它会在该 using 声明出现的类中隐式声明一组构造函数 (12.9);否则, using 声明中指定的名称是另一个名称空间或类中的一组声明的同义词

\n
\n\n
#include <iostream>\n#include <typeinfo>\n\nnamespace n1 {\n  template <class T1, class T2>\n  struct MyTemplate {\n    struct Inner {};\n  };\n}\n\nusing namespace n1;\nnamespace n2 {\n  typedef MyTemplate<int, int> MyClass;\n}\n\n\nnamespace n1 {\n  typedef n2::MyClass MyClass;\n\n  template<> struct MyClass::Inner {\n    int member;\n  };\n\n  MyClass::Inner inner{0};\n}\n\nint main()\n{\n  using namespace std;\n\n  cout << typeid(n1::inner).name() << endl;\n\n  return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

GCC 完美地编译了这个例子,没有任何指责。

\n\n

此外,了解声明的传递性如何与usingGCC 配合使用也会很有趣。

\n\n

这是修改后的示例:

\n\n
#include <iostream>\n#include <typeinfo>\n\nnamespace n1 {\n  template <class T1, class T2>\n  struct MyTemplate {\n    struct Inner {};\n  };\n}\n\n\nusing namespace n1;\nnamespace n2 {\n  using MyClass = MyTemplate<int, int>;\n\n}\n\nnamespace n3\n{\n  using n2::MyClass;\n}\n\n\nnamespace n1 {\n  typedef n3::MyClass MyClass;\n\n  template<> struct MyClass::Inner {\n    int member;\n  };\n\n  MyClass::Inner inner{0};\n}\n\nint main()\n{\n  using namespace std;\n\n  cout << typeid(n1::inner).name() << endl;\n\n  return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

Clang 和 GCC 再次愉快地编译它。

\n

  • 这当然是一个复杂的话题,如果我忽略了什么,请原谅。但我认为使我的示例有点特别的是类型别名“MyClass”,它在某种程度上模糊了定义“MyClass::Inner”的名称空间。所以我的理解是 gcc 将类型别名视为“#define”,而 clang 将其视为新类型。我在您的答案中没有看到任何对类型别名的引用,所以我认为这部分仍然缺失。仍然非常感谢您花时间研究该标准! (2认同)