免责声明:GCC Clang 是正确的(正如@suluke 指出的那样,使用下面的调查结果)。
我在 C++14 标准中找到了以下段落,但我确信这同样适用于 C++11:
第一部分:模板的专业化和实例化
inline namespace(§7.3.1 cl. 8):
内联命名空间的成员在大多数情况下都可以使用,就好像它们是封闭命名空间的成员一样。具体来说,内联命名空间及其封闭的命名空间都被添加到参数相关查找 (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 指令引入的内联命名空间的成员,即使在封闭命名空间中有该名称的声明。
显式实例化(§ 14.7.2 cl. 3)
如果显式实例化用于类或成员类,则声明中的详细类型说明符应包含一个简单模板标识。如果显式实例化是针对函数或成员函数,则声明中的 unqualified-id 应为模板 ID,或者在可以推导出所有模板参数的情况下,为模板名称或操作符函数 ID。 [注意:声明可以声明一个qualified-id,在这种情况下qualified-id的unqualified-id必须是一个template-id。 —尾注] 如果显式实例化是针对类模板特化的成员函数、成员类或静态数据成员,则成员名称的限定 ID 中的类模板特化的名称应为 simple-template -ID。如果显式实例化是针对变量的,则声明中的 unqualified-id 应为 template-id。 显式实例化应出现在其模板的封闭命名空间中。如果显式实例化中声明的名称是非限定名称,则显式实例化应出现在声明其模板的名称空间中,或者,如果该名称空间是内联的(7.3.1),则应出现在其封闭名称空间集中的任何名称空间中。
显式实例化(§ 14.7.2 cl. 6)
类、函数模板或变量模板特化的显式实例化放置在定义模板的命名空间中。类模板成员的显式实例化放置在定义封闭类模板的命名空间中。成员模板的显式实例化放置在定义封闭类或类模板的命名空间中。 [ 例子:
namespace N {
template<class T> class Y { void mf() { } };
}
template class Y<int>; // error: class template Y not visible
// in the global namespace
using N::Y;
template class Y<int>; // error: explicit instantiation outside of the
// namespace of the template
template class N::Y<char*>; // OK: explicit instantiation in namespace N
template void N::Y<double>::mf(); // OK: explicit instantiation
// in namespace N
—结束示例]
第二部分:Typedef,别名
什么是 typedef 或别名?
§7.1.3 分类。 1 个状态:
[...]
使用 typedef 说明符声明的名称成为 typedef-name。在其声明的范围内,typedef-name 在语法上等同于关键字,并以第 8 节中描述的方式命名与标识符相关联的类型。因此 typedef-name 是另一种类型的同义词。 typedef-name 不会像类声明 (9.1) 或枚举声明那样引入新类型。
§7.1.3 分类。 2 个状态:
typedef-name 也可以由 alias-declaration 引入。 using 关键字之后的标识符成为 typedef-name,标识符之后的可选属性说明符序列属于该 typedef-name。 它与由 typedef 说明符引入的语义相同。特别是,它没有定义新的类型,也不会出现在 type-id 中。
第三部分:测试您的案例
按照您的逻辑,以下代码应编译并生成 2 种不同的类型。我用 Clang 测试了它,这是有问题的编译器。 Clang 生成的类型与其所需的标准类型相同。
#include <iostream>
namespace n1 {
template <class T1, class T2>
struct MyTemplate {
struct Inner {};
};
}
using namespace n1;
namespace n2 {
using MyClass = MyTemplate<int, int>;
}
namespace n3 {
using MyClass = MyTemplate<int, int>;
}
namespace n1 {
using n2::MyClass;
template<> struct MyClass::Inner {
int member;
};
MyClass::Inner inner{0};
}
namespace n4{
using n3::MyClass;
MyClass::Inner inner{0};
}
int main()
{
using namespace std;
cout << typeid(n1::inner).name() << endl;
cout << typeid(n4::inner).name() << endl;
return 0;
}
编译
c++ -std=c++14 typedef-typeid.cpp -O3 -o compiled.bin
或
c++ -std=c++11 typedef-typeid.cpp -O3 -o compiled.bin
两种情况下的输出
N2n110MyTemplateIiiE5InnerE
N2n110MyTemplateIiiE5InnerE
事实证明,在这种情况下typedef 和using 是等价的,我们可以使用typedef 代替using。
§7.3.3 分类。 1 指出了这一点:
[...] 如果 using 声明命名了一个构造函数(3.4.3.1),它会在出现 using 声明的类中隐式声明一组构造函数(12.9); 否则在 using 声明中指定的名称是另一个命名空间或类中的一组声明的同义词。
#include <iostream>
#include <typeinfo>
namespace n1 {
template <class T1, class T2>
struct MyTemplate {
struct Inner {};
};
}
using namespace n1;
namespace n2 {
typedef MyTemplate<int, int> MyClass;
}
namespace n1 {
typedef n2::MyClass MyClass;
template<> struct MyClass::Inner {
int member;
};
MyClass::Inner inner{0};
}
int main()
{
using namespace std;
cout << typeid(n1::inner).name() << endl;
return 0;
}
而且 GCC 完美地编译了这个例子,没有任何责备。
此外,了解带有 using 声明的 传递性 如何与 GCC 一起工作会很有趣。
这是修改后的例子:
#include <iostream>
#include <typeinfo>
namespace n1 {
template <class T1, class T2>
struct MyTemplate {
struct Inner {};
};
}
using namespace n1;
namespace n2 {
using MyClass = MyTemplate<int, int>;
}
namespace n3
{
using n2::MyClass;
}
namespace n1 {
typedef n3::MyClass MyClass;
template<> struct MyClass::Inner {
int member;
};
MyClass::Inner inner{0};
}
int main()
{
using namespace std;
cout << typeid(n1::inner).name() << endl;
return 0;
}
Clang 和 GCC 再次愉快地编译它。