ISO/IEC 14882:2011之条款3.4——名字查找
3.4 名字查找
1、名字查找规则统一应用于所有的名字(包括typedf-name(7.1.3),namespace-name(7.3),以及class-name(9.1))只要是语法在被一个特定的规则所讨论的上下文中允许这些名字的地方。名字查找将一个名字的使用与那个名字的一个声明(3.1)相关联。名字查找应该为那个名字找到一个非歧义的声明(见10.2)。名字查找可以将多个声明与一个名字相关联,如果它找到的这个名字是一个函数名;这些声明被称为形成一组被重载的函数(13.1)。在名字查找成功之后,重载决议(13.3)发生。访问规则(条款11)只在查找和函数重载决议(如果可被应用)已经成功后被考虑一次。只有在名字查找、函数重载决议(如果可被应用)以及访问检查都已经成功后,属性才由在表达式处理(条款5)中进一步被使用的名字声明所引入。
2、一个名字“在一个表达式的上下文中查找被查找”作为表达式被找到的作用域中的一个限定名被查找。
3、一个类的注入类名(条款9[译者注:第二段])也被认为是那个类的一个成员,出于名字隐藏和查找的目的。
4、[注:3.5讨论了连接发布。作用域、声明点和名字隐藏的概念在3.3中被讨论。 ——注结束]
3.4.1 非限定名查找
1、在3.4.1中所列出的所有情况中,作用域以每一个各自的类别中所列出的次序,为一个声明而被搜索;一旦一个声明以那个名字而被发现,名字查找即便结束。如果没有找到声明,那么程序是不良形式的。
2、来自由一个using-directive所指定的名字空间的声明在一个包围此using-directive的名字空间中变得可见;见7.3.4。出于对非限定名查找规则(在3.4.1中描述)的目的,来自由using-directive所指定的名字空间的声明被认为是那个封闭的名字空间的成员。
3、对用作为一个函数调用的postfix-expression的一个非限定名的查找在3.4.2中被描述。[注:出于(在解析期间)判定一个表达式对于一个函数调用是否为一个postfix-expression的目的,会应用通常的名字查找规则。在3.4.2中的规则对一个表达式的语法解释没有效果。比如:
typedef int f; namespace N { struct A { friend void f(A &); operator int(); void g(A a) { int i = f(a); // f为typedef,而不是友元函数:等价于int(a) } }; }
因为第7行的表达式并不是一个函数调用,所以并不会应用依赖实参的名字查找(3.4.2) ,因而友元函数f不被发现。 ——注结束]
4、在全局作用域,任一函数、类或用户声明的名字空间的外部所使用的一个名字应该在其在全局作用域中使用之前被声明。
5、在任一函数或类定义的外部的一个用户声明的名字空间中所使用的一个名字应该在其在那个名字空间中使用之前,或在其在包围它的名字空间的一个名字空间中使用之前被声明。
6、一个函数的定义中所使用的一个名字,该名字跟在这个函数的declarator-id[注:这个指的是非限定名,比如,发生在一个类型中或parameter-declaration-clause中的默认实参中,或用在函数体中]之后,而这个函数的declarator-id是名字空间N的一个成员(在这个名字空间中,仅仅出于呈现目的[译者注:即,仅仅对该函数进行声明],N可以代表全局作用域),那么这个名字应该在其在语句块中使用之前被声明,而在那个语句块中该名字被使用,或在该语句块中所封闭的所有语句块的其中之一中被使用,或者,该名字应该在其在名字空间N中使用之前被声明,或者,如果N是一个被嵌套的名字空间,那么该名字应该在其在N的封闭的名字空间中的其中之一中使用之前被声明。[例:
namespace A { namespace N { void f(); } } void A::N::f() { i = 5; // 以下作用域为i的一个声明被搜索; // 1)A::N::f的最外面的语句块,在i的使用之前 // 2)名字空间N的作用域 // 3)名字空间A的作用域 // 4)全局作用域,在A::N::f的定义之前 }
—— 例结束]
7、在一个成员函数体或嵌套类定义[注:这里指的是跟在类名之后的非限定名;]外部的一个类X的定义中所使用的一个名字应该用以下其中一个方法来声明:
——在其在类X使用之前,或是一个X的基类的一个成员(10.2),或者
——如果X是类Y的一个嵌套类(9.7),那么在Y中X的定义之前,或者应该是Y的一个基类的一个成员(这个查找依次应用于Y的封闭的类,从封闭的类的最里面[注:这个查找,不管X的定义]),或者
——如果X是名字空间N的一个成员,或是作为N的一个成员的一个类的一个嵌套类,或是在作为N的一个成员的一个函数的一个局部类内的一个局部类或一个嵌套类,在名字空间N中或在其中一个N的封闭名字空间中类X的定义之前。
[例:
namespace M { class B { }; } namespace N { class Y : public M::B { class X { int a[i]; }; }; } // 以下作用域为i的一个定义而被搜索: // 1)类N::Y::X的作用域,在i的使用之前 // 2)类N::Y的作用域,在N::Y::X的定义之前 // 3)N::Y的基类M::B的作用域 // 4)名字空间N的作用域,在N::Y的定义之前 // 5)全局作用域,在N的定义之前
—— 例结束][注:当查找由一个friend声明所引入的一个类或函数的一个先前的声明时,最里面的封闭的名字空间作用域的外部的作用域不被考虑;见7.3.1.2。—— 注结束][注:3.3.7进一步描述了一个类定义中对名字使用的限制。9.7进一步描述了在嵌套的类定义中对名字使用的限制。9.8进一步描述了在局部类定义中对名字使用的限制。 ——注结束]
8、用在跟在函数的declarator-id后面的类X的一个成员函数(9.3)的定义中的一个名字[注:即,比如,在一个类型中或在parameter-declaration-clause中的默认实参中或函数体中发生的一个非限定名]或在类X的一个非静态数据成员的brace-or-equal-initializer中使用的一个名字应该用以下其中一个方法来声明:
——在其在语句块中使用之前,在那个语句块中或在一个封闭的语句块中,该名字被使用(6.3),或
——应该是类X的一个成员,或是X的一个基类的一个成员(10.2),或
——如果X是类Y的一个嵌套类(9.7),那么X应该是Y的一个成员,或者应该是Y的一个基类的一个成员(这个查找依次应用于Y的封闭的类,从封闭类的最里面开始)[注:不管成员函数被定义在类X的定义内还是被定义在包围X的定义的一个名字空间中,这个查找都会应用。],或
——如果X是一个局部类(9.8),或是一个局部类的一个嵌套类,那么在包围类X的定义的一个语句块中的类X的定义之前,或
——如果X是名字空间N的一个成员,或是作为N的一个成员的一个类的一个嵌套类,或是一个在作为N的一个成员的一个函数的一个局部类中的一个局部类或一个嵌套类,那么在该名字使用之前,在名字空间N中,或在其中一个N的封闭的名字空间中。
[例:
class B { }; namespace M { namespace N { class X : public B { void f(); }; } } void M::N::X::f() { i = 16; } // 以下作用域为i的一个声明而被搜索: // 1)M::N::X::f的最外部语句块作用域,在i的使用之前 // 2)类M::N::X的作用域 // 3)M::N::X的基类B的作用域 // 4)名字空间M::N的作用域 // 5)名字空间M的作用域 // 6)全局作用域,在M::N::X::f的定义之前
—— 例结束][注:9.3和9.4进一步描述了成员函数定义中的名字使用上的限制。9.7进一步描述了嵌套类的作用域中的名字使用上的限制。9.8进一步描述了局部类定义中名字使用上的限制。 ——注结束]
9、 对一个用在一个友元函数(11.3)定义中的名字查找——该友元函数被定义在准许其友元关系的类中——应该作为在成员函数定义中的所描述的查找那样进行。如果友元函数在准许其友元关系的类中没有被定义,那么在友元函数定义中的名字查找应该像在名字空间成员函数定义中所描述的查找那样进行。
10、在命名一个成员函数的一个友元声明中,用在函数声明符中的一个名字,并且不作为declarator-id中的一个template-argument的一部分,在成员函数所在的类的作用域中先被查找(10.2) 。如果这个名字没被找到,或者,如果这个名字是declarator-id中的一个template-argument的一部分,那么该查找跟在准许友元关系的类定义中的非限定名字查找描述一样。[例:
struct A { typedef int AT; void f1(AT); void f2(float); template <class T> void f3(); }; struct B { typedef char AT; typedef float BT; friend void A::f1(AT); // 形参类型是A::AT friend void A::f2(BT); // 形参类型是B::BT friend void A::f3<AT>(); // 模板实参是B::AT };
—— 例结束]
11、在对被用作为一个默认实参(8.3.6)的一个名字的查找期间,该默认实参在一个函数的parameter-declaration-clause中或该名字被用在一个构造器的一个mem-initializer(12.6.2)的表达式中,函数形参名是可见的,并隐藏声明在包含那个函数声明的语句块、类或名字空间作用域中的实体的名字。[注:8.3.6进一步描述了对默认实参名字使用上的限制。12.6.2进一步描述了在一个ctor-initializer中所使用的名字的限制。 ——注结束]
12、在用在一个enumerator-definition的constant-expression中的一个名字的查找期间,该枚举的先前声明的枚举符是可见的,并且隐藏声明在包含此enum-specifier的语句块、类或名字空间作用域中的实体的名字。
13、用在类X的一个静态数据成员定义中的一个名字(9.4.2)(在静态成员的qualified-id之后)被查找,就好像名字被用在类X的一个成员函数中似的。[注:9.4.2进一步描述了一个静态数据成员定义中名字使用的限制。 ——注结束]
14、如果一个名字空间的一个变量成员被定义在其名字空间的作用域外,那么出现在该成员定义中的任一名字(在declarator-id之后)被查找,就好像该成员的定义发生在其名字空间中似的。[例:
namespace N { int i = 4; extern int j; } int i = 2; int N::j = i; // N::j == 4
—— 例结束]
15、被用在一个function-try-block(条款15)的例程处理中的一个名字被查找,就好像该名字被用在函数定义的最外部的语句块中似的。特别地,函数形参名不应该在exception-declaration中被重新声明,也不应该在function-try-block的一个例程处理的最外部语句块中被重新声明。在函数定义的最外部语句块中所声明的名字,在function-try-block的一个例程处理的作用域中被查找时不被发现。[注:但是形参名字被发现。 ——注结束]
16、[注:模板定义中的名字查找的规则在14.6中描述。 ——注结束]
3.4.2 依赖实参的名字查找
1、当在一个函数调用(5.2.2)中的postfix-expression是一个unqualified-id时,其它在通常的非限定查找(3.4.1)期间不被考虑的名字空间可以被搜索,并且在那些名字空间中,名字空间作用域的友元函数声明(11.3)本来不可见的,也可以被找到。这些对搜索的修改依赖于实参的类型(以及对模板模板实参,模板实参的名字空间)。[例:
namespace N { struct S { }; void f(S); } void g() { N::S s; f(s); // OK:调用N::f (f)(s); // 错误:N::f不被考虑;括号阻止了依赖实参的查找 }
—— 例结束]
[译者注:
#include <iostream> #include <functional> using namespace std; namespace N { class A { friend void func(A &a); public: }; void func(A &a); } extern "C" void gfunc(void); void gfunc(void) { N::A a; func(a); // 友元函数声明N::func被找到 } extern void func(N::A &a); void func(N::A &a) { cout << "Hello" << endl; } void N::func(A &a) { cout << "Hi"<< endl; } extern "C" void HelloTest(void); void HelloTest(void) { gfunc(); // 输出Hi }
—— 注结束]
2、对于在函数调用中的每个实参类型T,有一组零个或多个相关的名字空间和一组零个或多个相关的类要被考虑。这几组名字空间和类由函数实参的完全由函数实参(以及任一模板模板实参的名字空间)的类型确定。用于指定类型的typedef名字和using-declaration并不对这个集合起什么作用。名字空间和类集用以下方法确定:
——如果T是一个基本类型,那么其相关联的类和名字空间集都是空的。
——如果如果T是一个类类型(包括联合),那么其相关联的类是:该类本身;作为一个成员的类,如果存在的话;以及其直接和间接的基类。其相关联的名字空间是,该名字空间相关联的类是该名字空间的成员。此外,如果T是一个类模板特化,那么其相关联的名字空间和类也包括:与提供给模板类型形参(排除模板模板形参[译者注:即模板形参也是一个模板])的模板实参的类型相关联的名字空间和类;任一模板模板实参是其成员的名字空间;以及任一被用作为模板模板实参的成员模板是其成员的类。[注:非类型模板实参对相关的名字空间集不起什么作用。 ——注结束]
——如果T是一个枚举类型,那么其相关联的名字空间是该枚举类型所定义在的名字空间。如果它是类成员,那么其相关联的类是成员的类;否则,它没有相关联的类。
——如果T是一个指向U或U的一个数组的一个指针,那么其相关联的名字空间和类是与U相关联的那么些名字空间和类。
——如果T是一个函数类型,那么其相关联的名字空间和类是那些与函数形参类型相关联的以及那些与函数返回类型相关联的名字空间和类。
——如果T是一个指向一个类X的一个成员函数的指针,那么其相关联的名字空间和类是那些与函数形参类型和返回类型相关联的以及与X相关联的名字空间和类。
——如果T是一个指向类X的一个数据成员的指针,那么其相关联的名字空间和类是那些与成员类型相关联的,以及与X相关联的名字空间和类。
如果一个相关联的名字空间是一个内联名字空间(7.3.1),那么其封闭的名字空间也包含在该集合内。如果一个相关联的名字空间直接包含内联名字空间,那么那些内联名字空间也被包含在这个集合中。此外,如果实参是一组被重载的函数以及/或函数模板的名字或地址,那么其相关联的类和名字空间是那些与该集合的每个成员相关联的并集,即与其形参类型和返回类型相关联的类和名字空间。此外,如果前面提到的重载函数的集合是用一个template-id来命名的,那么其相关联的类和名字空间也包含其类型template-argument和其模板template-argument的名字。
3、让X作为由非限定查找(3.4.1)产生的查找集,并且让Y作为由依赖实参的查找产生的查找集(定义如下)。如果X含有
——一个类成员的一个声明,或
——并非一个using-declaration的一个语句块作用域的函数声明
——既不是一个函数也不是一个函数模板的一个声明
那么Y是空的。否则,Y是与实参类型相关联的名字空间中找到的声明集,如下描述。由名字查找所找到的声明集是X和Y的并集。[注:与实参类型相关联的名字空间和类可以包含已经由普通的非限定查找所考虑的名字空间和类。 ——注结束][例:
namespace NS { class T { }; void f(T); void g(T, int); } NS::T parm; void g(NS::T, float); int main(void) { f(parm); // OK:调用NS::f extern void g(NS::T, float); g(parm, 1); // OK:调用g(NS::T, float) }
]
4、当考虑一个相关联的名字空间时,查找与当相关联的名字空间被用作为一个限定符(3.4.3.2)时被执行的查找一样,除了:
——在相关联的名字空间中的任意using-directive被忽略。
——任意在相关联的类中所声明的名字空间作用域的友元函数或友元函数模板在其各自的名字空间内都可见,即使它们在普通查找期间不可见(11.3)。
——所有名字,除了那些(可能被重载的)函数和函数模板被忽略。
3.4.3 限定名字查找
1、一个类或名字空间成员或枚举符的名字可以在::作用域决议操作符之后被引用(5.1),作用域决议操作符应用于指示其类、名字空间或枚举的一个nested-name-specifier。如果在一个nested-name-specifier中的一个::作用域决议操作符前没有一个decltype-specifier,那么在::前面的名字的查找仅考虑名字空间、类型以及其特化是类型的模板。如果所找到的名字并不指明一个名字空间或一个类、枚举,或依赖类型,那么程序是不良形式的。[例:
class A { public: static int n; }; int main() { int A; A::n = 42; // OK A b; // 不良形式的:A并不命名一个类型[译者注:即这里的A是第一句所声明的一个变量] }
—— 例结束]
2、[注:多个限定名,诸如N1::N2::N3::n,可以被用来引用嵌套类的成员(9.7)或嵌套名字空间的成员。 ——注结束]
3、在一个声明中,而在此声明中的declarator-id是一个qualified-id,在正被声明的qualified-id之前所使用的名字在定义名字空间作用域作用域中被查找;跟在qualified-id之后的名字在成员类或名字空间的作用域中被查找。[例:
class X { }; class C { class X { }; static const int number = 50; static X arr[number]; }; X C::arr[number]; // 不良形式的。 // 等价于 ::X C::arr[C::number];而不是C::X C::arr[C::number];
—— 例结束]
4、带有单目作用域操作符::作为前缀(5.1)的一个名字在全局作用域中被查找,在它被使用的翻译单元中。该名字应该被声明在全局名字空间作用域中,或应该是这么一个名字,其声明因为一个using-directive(3.4.3.2)而在全局作用域中可见。::的使用允许一个全局名字被引用,即使其标示符已经被隐藏(3.3.10)。
5、被一个nested-name-specifier前缀修饰的一个名字——该指定符指定了一个枚举类型——表示那个枚举的一个枚举符。
6、如果一个pseudo-destructor-name(5.2.4)包含一个nested-name-specifier,那么type-name作为在由nested-name-specifier所指明的作用域中的类型被查找。类似地,在以下形式的一个qualified-id中:
nested-name-specifieropt class-name :: ~ class-name
第二个class-name在与第一个相同的作用域中被查找。[例:
struct C { typedef int I; }; typedef int I1, I2; extern int* p; extern int* q; p->C::I::~I(); // I在C的作用域中被查找 q->I1::~I2(); // I2在后缀表达式的作用域中被查找 struct A { ~A(); }; typedef A AB; int main() { AB *p; p->AB::~AB(); // 显式地调用了A的析构器 }
—— 例结束][注:3.4.5描述了名字查找如何在 . 和 -> 操作符之后继续进行。 ——注结束]
3.4.3.1 类成员
1、如果一个qualified-id的nested-name-specifier指定了一个类,那么在nested-name-specifier后所指定的名字在该类的作用域中被查找(10.2),除了以下列出的情况之外。这个名字应该表示那个类或其其中一个基类的一个或多个成员(条款10)。[注:一个类成员可以在其潜在作用域的任一点使用一个qualified-id来被引用(3.3.7)。 ——注结束]对上述所描述的名字查找规则的例外如下:
——一个析构器的名字以3.4.3中所指定的那样被查找;
——一个conversion-function-id的一个conversion-type-id以在一个类成员访问中的一个conversion-type-id的相同的方式被查找(见3.4.5);
——在一个template-id的一个template-argument中的名字在整个postfix-expression所发生的上下文中被查找。
——在一个using-declaration中所指定的一个名字的查找(7.3.3)在同一个作用域内也找到隐藏的类或枚举名(3.3.10)。
2、在一个查找中——构造器在此查找中是一个可接受的查找结果并且nested-name-specifier指定了一个类C:
——如果当在C中查找时,在nested-name-specifier后所指定的名字是C的注入类名(条款9[译者注:第二段]),或
——在作为一个member-declaration的一个using-declaration中,如果在nested-name-specifier后所指定的名字在nested-name-specifier的最后那个分量中与identifier或simple-template-id的template-name相同,
那么该名字被认为是类C的构造器的名字。[注:比如,在一个elaborated-type-specifier中,构造器并不是一个可接受的查找结果,以至于构造器将不会代替该注入类名而被使用。 ——注结束]这样一个构造器的名字将仅被用在命名一个构造器的一个声明的一个declarator-id中,或一个using-declaration中。[例:
struct A { A(); }; struct B : public A { B(); }; A::A() { } B::B() { } B::A ba; // 类型A的对象 A::A a; // 错误,A::A并不是一个类型名 struct A::A a2; // 类型A的对象
—— 例结束]
3、被在一个嵌套声明区域中的一个名字或被一个派生类成员的名字所隐藏的一个类成员的名字仍然可以被找到,如果被其类的名字所限定,::操作符跟在那个类的后面。[译者注:
class A { public: int a, b; }; class B : public A { private: int a; // A::a被B::a所隐藏 public: B(void) { a = A::a + 10; // 等号左边的a为B::a,右边的为A::a int b = A::b + a; // A::b被局部变量b所隐藏,所以等号左边的b为局部变量,右边的a为B::a a += b; } };
]
3.4.3.2 名字空间成员
1、如果一个qualified-id的nested-name-specifier指定了一个名字空间,那么在nested-name-specifier之后被指定的名字在该名字空间的作用域中被查找。如果一个qualified-id以::打头,那么在::之后的名字在全局名字空间中被查找。在上述任一情况下,在一个template-id的一个template-argument中的名字在整个postfix-expression所发生的上下文中被查找。
2、对于一个名字空间X和名字m,名字空间所限定的查找集S(X, m)被如下定义:设S'(X, m)为X中以及X的内联名字空间集中m的所有声明的集合(7.3.1)。如果S'(X, m)不空,那么S(X, m)就是S'(X, m);否则,S(X, m)是由X中以及其内联名字空间集中的using-directive所指明的所有名字空间Ni的查找集S(Ni, m)的并集。[译者注:
namespace Y { int u; namespace { int v; } } namespace X { int m; namespace { int n; } using namespace Y; } extern "C" void HelloTest(void); void HelloTest(void) { X::m = 0; // OK:查找到名字空间X的m X::n = 0; // OK:查找到名字空间中X的匿名名字空间的n X::u = 0; // OK:S'(X, u)为空集,在名字空间Y中查找到 X::v = 0; // OK:S'(X, v)为空集,在名字空间Y中查找到 }
]
3、给定X::m(这里,X是一个用户声明的名字空间),或给定::m(这里,X是全局名字空间),如果S(X, m)是空集,那么程序是不良形式的。否则,如果S(X, m)恰好有一个成员,或如果该引用的上下文是一个using-declaration(7.3.3),那么S(X, m)是所需要的m的声明的集合。否则,如果m的使用并不允许一个唯一的声明从S(X, m)中被选择,那么程序是不良形式的。[例:
int x; namespace Y { void f(float); void h(int); } namespace Z { void h(double); } namespace A { using namespace Y; void f(int); void g(int); int i; } namespace B { using namespace Z; void f(char); int i; } namespace AB { using namespace A; using namespace B; void g(); } void h() { AB::g(); // g直接在AB中被声明,从而S是{ AB::g() }并且AB::g()被选择 AB::f(1); // f并不直接在AB中被声明,因此规则被递归地应用于A和B; // 名字空间Y不被搜索并且Y::f(float)不被考虑; // S是{ A::f(int), B::f(char) }并且重载决议选择了A::f(int) AB::f('c'); // 与上述描述一样,重载决议选择了B::f(char) AB::x++; // x并不直接在AB中被声明,并且被在A或B中声明,因此规则被递归应用于Y和Z, // S为{ },所以程序是不良形式的 AB::i++; // i并不直接在AB中声明,从而规则被递归地应用于A和B, // S是{ A::i, B::i },因此该使用是有歧义的,从而程序是不良形式的 AB::h(16.8); // h并不直接声明在AB中,也不直接声明在A或B中,因此规则被递归应用于Y和Z, // S为{ Y::h(int), Z::h(double) },并且重载决议选择了Z::h(double) }
—— 例结束]
4、相同的声明被多次发现并不是一个歧义(因为它仍然是一个唯一的声明)。比如:
namespace A { int a; } namespace B { using namespace A; } namespace C { using namespace A; } namespace BC { using namespace B; using namespace C; } void f() { BC::a++; // OK:S是{ A::a, A::a } } namespace D { using A::a; } namespace BD { using namespace B; using namespace D; } void g() { BD::a++; // OK:S是{ A::a, A::a } }
5、因为每个被引用的名字空间最多被搜索一次,因此下面的例子是良好定义的:
namespace B { int b; } namespace A { using namespace B; int a; } namespace B { using namespace A; } void f() { A::a++; // OK:a直接在A中声明,S是{ A::a } B::a++; // OK:A和B被搜索(一次),S是{ A::a } A::b++; // OK:A和B被搜索(一次),S是{ B::b } B::b++; // OK:b直接在B中声明,S是{ B::b } }
—— 例结束]
6、在一个被限定的名字空间成员名字的查找期间,如果该查找发现了该成员的多个声明,并且如果有一个引入了一个类名或枚举名,而其它的声明引入了相同的变量,或相同的枚举符,或一组函数,那么非类型名隐藏了该类或枚举名,当且仅当声明来自同一个名字空间;否则,(这些声明来自不同的名字空间)程序是不良形式的。[例:
namespace A { struct x { }; int x; int y; } namespace B { struct y { }; } namespace C { using namespace A; using namespace B; int i = C::x; // OK:A::x(类型为int) int j = C::y; // 有歧义的,A::y或B::y }
—— 例结束]
7、在对一个名字空间成员的一个声明中——在此名字空间成员中,declarator-id是一个qualified-id——,给定这个声明,此名字空间成员的qualified-id具有以下形式:
nested-name-specifier unqualified-id
unqualified-id应该命名一个由nested-name-specifier所指派的名字空间的一个成员,或命名那个名字空间的内联名字空间集(7.3.1)的一个元素的一个成员。[例:
namespace A { namespace B { void f1(int); } using namespace B; } void A::f1(int) { } // 不良形式的,f1不是A的一个成员
—— 例结束]然而,在这样的名字空间成员声明中,nested-name-specifier可以依靠using-directive来隐式地提供nested-name-specifier的初始部分。[例:
namespace A { namespace B { void f1(int); } } namespace C { namespace D { void f1(int); } } using namespace A; // 译者注:这里使用了using指示符,以便在定义B::f1时隐式地提供A:: using namespace C::D; void B::f1(int) { } // OK:定义了A::B::f1(int)
—— 例结束]
3.4.4 详细说明的类型指示符
1、一个elaborated-type-specifier(7.1.6.3)可以被用来引用一个先前所声明的class-name或enum-name,即使该名字已被一个非类型声明所隐藏(3.3.10)。
2、如果elaborated-type-specifier不具有nested-name-specifier,并且如果elaborated-type-specifier不以以下形式出现在一个声明中:
class-key attribute-specifier-seqopt identifier;
那么identifier根据3.4.1进行查找,但忽略任一已被声明的非类型名字。如果elaborated-type-specifier由enum关键字所引入并且此查找没有找到一个先前声明的type-name,那么elaborated-type-specifier是不良形式的。如果elaborated-type-specifier由class-key所引入并且此查找没有发现之前所声明的type-name,或如果elaborated-type-specifier出现在具有以下形式的一个声明中:
class-key attribute-specifier-seqopt identifier;
那么elaborated-type-specifier是引入class-name的一个声明,如3.3.2中所描述的那样。
3、如果elaborated-type-specifier具有一个nested-name-specifier,那么限定的名字查找被执行,如3.4.3中所描述的那样,但忽略已经被声明的任一非类型名字。如果名字查找并没有找到一个先前所声明的type-name,那么该elaborated-type-specifier是不良形式的[例:
struct Node { struct Node* Next; // OK:引用在全局作用域的Node struct Data* Data; // OK:声明了在全局作用域的类型Data以及成员Data }; struct Data { struct Node* Node; // OK:引用在全局作用域的Node friend struct ::Glob; // 错误:Glob不被声明,不能引入一个限定的类型(7.1.6.3) friend struct Glob; // OK:引用在全局作用域的(仍然)未被声明的Glob }; struct Base { struct Data; // OK:声明了嵌套的Data struct ::Data* thatData; // OK:引用::Data struct Base::Data* thisData; // OK:引用嵌套的Data friend class ::Data; // OK:全局Data是一个友元 friend class Data; // OK:嵌套的Data是一个友元 struct Data { /* ... */ }; // 定义了嵌套的Data }; struct Data; // OK:重新声明了全局作用域的Data struct ::Data; // 错误:不能引入一个限定的类型(7.1.6.3) struct Base::Data; // 错误:不能引入一个限定的类型(7.1.6.3) struct Base::Datum; // 错误:Datum未定义 struct Base::Data* pBase; // OK:引用了嵌套的Data
—— 例结束]
3.4.5 类成员访问
1、在一个类成员访问表达式(5.2.5)中,如果 . 或 -> 符记后面立即跟着一个identifier,而该identifier后面跟着一个 < ,该标识符必须被查找以确定 < 是一个模板实参列表(14.2)的开头还是一个小于操作符。该标识符先在对象表达式的类中查找。如果标识符没有被找到,那么它然后在整个postfix-expression的上下文中被查找,并且应该命名一个类模板。
2、如果一个类成员访问中的id-expression(5.2.5)是一个unqualified-id,并且对象表达式的类型是一个类类型C,那么该unqualified-id在类C的作用域中被查找。对于一个伪析构器调用(5.2.4),unqualified-id在完整的postfix-expression的上下文中被查找。
3、如果unqualified-id是一个~type-name,那么此type-name在整个postfix-expression的上下文中被查找。如果对象表达式的类型T是一个类类型C,那么此type-name也在类C的作用域中被查找。至少其中一个查找应该找到引用(可能是cv限定的[译者注:即被const和/或volatile限定的])T的一个名字。[例:
struct A { }; struct B { struct A { }; void f(::A *a); }; void B::f(::A *a) { a->~A(); // OK:在*a中的查找找到injected-class-name // 译者注:这里调用的是全局作用域中struct A的默认析构器 }
—— 例结束]
4、如果在一个类成员访问中的id-expression是以下形式的一个qualified-id
class-name-or-namespace-name::...
那么跟在 . 或 -> 操作符后面的class-name-or-namespace-name在对象表达式和该名字的类中先被查找,如果被找到,那么它就被使用。否则,它在整个postfix-expression的上下文中被查找。[注:见3.4.3,里面描述了在::前的一个名字的查找,而这将仅找到一个类型或名字空间名。 ——注结束]
5、如果qualified-id具有以下形式
::class-name-or-namespace-name::...
那么,class-name-or-namespace-name在全局作用域中作为一个class-name或namespace-name被查找。
6、如果nested-name-specifier包含了一个simple-template-id(14.2),那么在其template-arguments中的名字在整个postfix-expression所发生的上下文中被查找。
7、如果id-expression是一个conversion-function-id,那么其conversion-type-id在对象表达式和该名字的类中先被查找,如果被找到,即被使用。否则,它在整个postfix-expression的上下文中被查找。在每个这些查找中,只有指示类型或其特化是类型的模板的名字才被考虑。[例:
struct A { }; namespace N { struct A { void g() { } template <class T> operator T(); }; } int main() { N::A a; a.operator A(); // 调用N::A::operator N::A }
—— 例结束]
3.4.6 using指示符和名字空间别名
1、在一个using-directive或namespace-alias-definition中,在对一个namespace-name或对在一个nested-name-specifier中的一个名字的查找期间,只有名字空间名被考虑。