不同命名空间中别名声明的成员特化
Member specialization of alias declaration in different namespaces
我刚刚遇到了 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};
}
Clang 愉快地编译了这个:
$ clang++ -std=c++11 -c -o alias_specialization.o alias_specialization.cc
但是 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 {
我知道我可以只写原始类型的全名(MyTemplate<int, int>
)而不是第 15 行的 MyClass
。
但我只是想知道这两个编译器中的哪一个是 "right"。
使用的确切编译器是:
$ clang++ --version
clang version 4.0.0
$ g++ --version
g++ (GCC) 6.3.1 20170306
免责声明:GCC Clang 是正确的(正如@suluke 所指出的,使用以下调查结果)。
我在 C++14 标准中发现了以下段落,但我确信这同样适用于 C++11:
第 I 部分:模板的专业化和实例化
inline namespace
(§7.3.1 cl.8):
Members of an inline namespace can be used in most respects as though they were members of the enclosing namespace. Specifically, the inline namespace and its enclosing namespace are both added to the set of associated namespaces used in argument-dependent lookup (3.4.2) whenever one of them is, and a using- directive (7.3.4) that names the inline namespace is implicitly inserted into the enclosing namespace as for an unnamed namespace (7.3.1.1). Furthermore, each member of the inline namespace can subsequently be partially specialized (14.5.5), explicitly instantiated (14.7.2), or explicitly specialized (14.7.3) as though it were a member of the enclosing namespace. Finally, looking up a name in the enclosing namespace via explicit qualification (3.4.3.2) will include members of the inline namespace brought in by the using-directive even if there are declarations of that name in the enclosing namespace.
显式实例化(§ 14.7.2 cl. 3)
If the explicit instantiation is for a class or member class, the elaborated-type-specifier in the declaration shall include a simple-template-id. If the explicit instantiation is for a function or member function, the unqualified- id in the declaration shall be either a template-id or, where all template arguments can be deduced, a template-name or operator-function-id. [Note: The declaration may declare a qualified-id, in which case the unqualified-id of the qualified-id must be a template-id. —end note] If the explicit instantiation is for a member function, a member class or a static data member of a class template specialization, the name of the class template specialization in the qualified-id for the member name shall be a simple-template-id. If the explicit instantiation is for a variable, the unqualified-id in the declaration shall be a template-id. An explicit instantiation shall appear in an enclosing namespace of its template. If the name declared in the explicit instantiation is an unqualified name, the explicit instantiation shall appear in the namespace where its template is declared or, if that namespace is inline (7.3.1), any namespace from its enclosing namespace set.
显式实例化(§ 14.7.2 cl. 6)
An explicit instantiation of a class, function template, or variable template specialization is placed in the namespace in which the template is defined. An explicit instantiation for a member of a class template is placed in the namespace where the enclosing class template is defined. An explicit instantiation for a member template is placed in the namespace where the enclosing class or class template is defined. [ Example:
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
— end example ]
第二部分:Typedef、别名
什么是 typedef 或别名?
§7.1.3 分类。 1 个州:
[...]
A name declared with the typedef specifier becomes a typedef-name. Within the scope of its declaration, a typedef-name is syntactically equivalent to a keyword and names the type associated with the identifier in the way described in Clause 8. A typedef-name is thus a synonym for another type. A typedef-name does not introduce a new type the way a class declaration (9.1) or enum declaration does.
§7.1.3 分类。 2 个州:
A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics as if it were introduced by the typedef specifier. In particular, it does not define a new type and it shall not appear in the 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 指出:
[...] If a using-declaration names a constructor (3.4.3.1), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9); otherwise the name specified in a using-declaration is a synonym for a set of declarations in another namespace or class.
#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 再次愉快地编译它。
我刚刚遇到了 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};
}
Clang 愉快地编译了这个:
$ clang++ -std=c++11 -c -o alias_specialization.o alias_specialization.cc
但是 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 {
我知道我可以只写原始类型的全名(MyTemplate<int, int>
)而不是第 15 行的 MyClass
。
但我只是想知道这两个编译器中的哪一个是 "right"。
使用的确切编译器是:
$ clang++ --version
clang version 4.0.0
$ g++ --version
g++ (GCC) 6.3.1 20170306
免责声明:GCC Clang 是正确的(正如@suluke 所指出的,使用以下调查结果)。
我在 C++14 标准中发现了以下段落,但我确信这同样适用于 C++11:
第 I 部分:模板的专业化和实例化
inline namespace
(§7.3.1 cl.8):
Members of an inline namespace can be used in most respects as though they were members of the enclosing namespace. Specifically, the inline namespace and its enclosing namespace are both added to the set of associated namespaces used in argument-dependent lookup (3.4.2) whenever one of them is, and a using- directive (7.3.4) that names the inline namespace is implicitly inserted into the enclosing namespace as for an unnamed namespace (7.3.1.1). Furthermore, each member of the inline namespace can subsequently be partially specialized (14.5.5), explicitly instantiated (14.7.2), or explicitly specialized (14.7.3) as though it were a member of the enclosing namespace. Finally, looking up a name in the enclosing namespace via explicit qualification (3.4.3.2) will include members of the inline namespace brought in by the using-directive even if there are declarations of that name in the enclosing namespace.
显式实例化(§ 14.7.2 cl. 3)
If the explicit instantiation is for a class or member class, the elaborated-type-specifier in the declaration shall include a simple-template-id. If the explicit instantiation is for a function or member function, the unqualified- id in the declaration shall be either a template-id or, where all template arguments can be deduced, a template-name or operator-function-id. [Note: The declaration may declare a qualified-id, in which case the unqualified-id of the qualified-id must be a template-id. —end note] If the explicit instantiation is for a member function, a member class or a static data member of a class template specialization, the name of the class template specialization in the qualified-id for the member name shall be a simple-template-id. If the explicit instantiation is for a variable, the unqualified-id in the declaration shall be a template-id. An explicit instantiation shall appear in an enclosing namespace of its template. If the name declared in the explicit instantiation is an unqualified name, the explicit instantiation shall appear in the namespace where its template is declared or, if that namespace is inline (7.3.1), any namespace from its enclosing namespace set.
显式实例化(§ 14.7.2 cl. 6)
An explicit instantiation of a class, function template, or variable template specialization is placed in the namespace in which the template is defined. An explicit instantiation for a member of a class template is placed in the namespace where the enclosing class template is defined. An explicit instantiation for a member template is placed in the namespace where the enclosing class or class template is defined. [ Example:
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
— end example ]
第二部分:Typedef、别名
什么是 typedef 或别名?
§7.1.3 分类。 1 个州:
[...] A name declared with the typedef specifier becomes a typedef-name. Within the scope of its declaration, a typedef-name is syntactically equivalent to a keyword and names the type associated with the identifier in the way described in Clause 8. A typedef-name is thus a synonym for another type. A typedef-name does not introduce a new type the way a class declaration (9.1) or enum declaration does.
§7.1.3 分类。 2 个州:
A typedef-name can also be introduced by an alias-declaration. The identifier following the using keyword becomes a typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. It has the same semantics as if it were introduced by the typedef specifier. In particular, it does not define a new type and it shall not appear in the 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 指出:
[...] If a using-declaration names a constructor (3.4.3.1), it implicitly declares a set of constructors in the class in which the using-declaration appears (12.9); otherwise the name specified in a using-declaration is a synonym for a set of declarations in another namespace or class.
#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 再次愉快地编译它。