Unqualified name lookup
Для
неквалифицированного
имени, то есть имени, которое не находится справа от оператора разрешения области видимости
::
, поиск имени проверяет
области видимости
, как описано ниже, до тех пор пока не найдет хотя бы одно объявление любого вида, после чего поиск останавливается и дальнейшие области видимости не проверяются. (Примечание: поиск из некоторых контекстов пропускает некоторые объявления, например, поиск имени, используемого слева от
::
, игнорирует объявления функций, переменных и перечислений, поиск имени, используемого в качестве спецификатора базового класса, игнорирует все объявления не-типов).
Для целей неполного поиска имени все объявления из пространства имён, указанного в using-директиве , появляются так, как если бы они были объявлены в ближайшем охватывающем пространстве имён, которое содержит, прямо или косвенно, как using-директиву, так и указанное пространство имён.
Поиск неквалифицированного имени для имени, используемого слева от оператора вызова функции (и, аналогично, оператора в выражении), описан в разделе зависимый от аргументов поиск .
Область видимости файла
Для имени, используемого в глобальной области видимости (верхний уровень пространства имён), вне любой функции, класса или пользовательского пространства имён, проверяется глобальная область видимости до момента использования имени:
int n = 1; // объявление n int x = n + 1; // OK: поиск находит ::n int z = y - 1; // Ошибка: поиск завершился неудачей int y = 2; // объявление y
Область видимости пространства имён
Для имени, используемого в пользовательском пространстве имен вне любой функции или класса, это пространство имен ищется перед использованием имени, затем пространство имен, содержащее это пространство имен перед объявлением этого пространства имен, и т.д. до достижения глобального пространства имен.
int n = 1; // объявление namespace N { int m = 2; namespace Y { int x = n; // OK, поиск находит ::n int y = m; // OK, поиск находит ::N::m int z = k; // Ошибка: поиск завершился неудачей } int k = 3; }
Определение вне своего пространства имён
Для имени, используемого в определении переменной-члена пространства имён вне пространства имён, поиск происходит так же, как и для имени, используемого внутри пространства имён:
namespace X { extern int x; // объявление, не определение int n = 1; // найдено первым } int n = 2; // найдено вторым int X::x = n; // находит X::n, устанавливает X::x в 1
Определение функции, не являющейся членом класса
Для имени, используемого в определении функции, либо в её теле или в качестве части аргумента по умолчанию, где функция является членом пользовательского или глобального пространства имён, блок, в котором имя используется, просматривается до использования имени, затем охватывающий блок просматривается до начала этого блока и т.д., пока не будет достигнут блок, являющийся телом функции. Затем пространство имён, в котором объявлена функция, просматривается до определения (не обязательно объявления) функции, использующей имя, затем охватывающие пространства имён и т.д.
namespace A { namespace N { void f(); int i = 3; // найден 3-м (если 2-й отсутствует) { int i = 4; // найден 4-м (если 3-й отсутствует) { int i = 5; // найден 5-м (если 4-й отсутствует) void A::N::f() { int i = 2; // найден 2-м (если 1-й отсутствует) while (true) { int i = 1; // найден 1-м: поиск завершен std::cout << i; { { // int i; // не найден namespace A { namespace N { // int i; // не найден { {
Определение класса
Для имени, используемого в любом месте определения класса (включая спецификаторы базовых классов и определения вложенных классов), за исключением тела функции-члена, аргумента по умолчанию функции-члена, спецификации исключений функции-члена или инициализатора члена по умолчанию, где член может принадлежать вложенному классу, определение которого находится в теле объемлющего класса, производится поиск в следующих областях видимости:
Для friend объявления поиск для определения того, ссылается ли оно на ранее объявленную сущность, выполняется как описано выше, за исключением того, что он останавливается после самого внутреннего охватывающего пространства имён.
namespace M { // const int i = 1; // никогда не найдено class B { // static const int i = 3; // найдено 3-м (но не пройдет проверку доступа) }; } // const int i = 5; // найдено 5-м namespace N { // const int i = 4; // найдено 4-м class Y : public M::B { // static const int i = 2; // найдено 2-м class X { // static const int i = 1; // найдено 1-м int a[i]; // использование i // static const int i = 1; // никогда не найдено }; // static const int i = 2; // никогда не найдено }; // const int i = 4; // никогда не найдено } // const int i = 5; // никогда не найдено
Введённое имя класса
Для имени класса или шаблона класса, используемого внутри определения этого класса или шаблона, или производного от него, поиск неквалифицированного имени находит класс, который определяется, как если бы имя было введено объявлением члена (с доступом члена public). Для более подробной информации см. injected-class-name .
Определение функции-члена
Для имени, используемого внутри тела функции-члена, аргумента по умолчанию функции-члена, спецификации исключения функции-члена или инициализатора члена по умолчанию, области поиска такие же, как в определении класса , за исключением того, что рассматривается вся область класса, а не только часть до объявления, использующего имя. Для вложенных классов выполняется поиск по всему телу объемлющего класса.
class B { // int i; // найдено 3-м }; namespace M { // int i; // найдено 5-м namespace N { // int i; // найдено 4-м class X : public B { // int i; // найдено 2-м void f(); // int i; // также найдено 2-м }; // int i; // найдено 4-м } } // int i; // найдено 6-м void M::N::X::f() { // int i; // найдено 1-м i = 16; // int i; // никогда не найдено } namespace M { namespace N { // int i; // никогда не найдено } }
- В любом случае, при анализе базовых классов, от которых наследуется данный класс, применяются следующие правила, иногда называемые доминированием при виртуальном наследовании :
Имя члена, найденное в подобъекте
B
, скрывает то же имя члена в любом подобъекте
A
, если
A
является базовым классом подобъекта
B
. (Заметим, что это не скрывает имя в любых дополнительных, невиртуальных копиях
A
в решетке наследования, которые не являются базовыми классами
B
: это правило имеет эффект только при виртуальном наследовании.) Имена, введенные using-объявлениями, рассматриваются как имена в классе, содержащем объявление. После проверки каждого базового класса результирующий набор должен либо включать объявления статического члена из подобъектов одного типа, либо объявления нестатических членов из одного подобъекта.
|
(до C++11) |
Строится
набор поиска
, который состоит из объявлений и подобъектов, в которых эти объявления были найдены. Using-объявления заменяются членами, которые они представляют, а объявления типов, включая injected-class-names, заменяются типами, которые они представляют. Если
C
- это класс, в области видимости которого использовалось имя, сначала проверяется
C
. Если список объявлений в
C
пуст, набор поиска строится для каждого из его прямых базовых классов
Bi
(с рекурсивным применением этих правил, если
Bi
имеет собственные базовые классы). После построения наборы поиска для прямых базовых классов объединяются в набор поиска в
C
следующим образом:
|
(начиная с C++11) |
struct X { void f(); }; struct B1: virtual X { void f(); }; struct B2: virtual X {}; struct D : B1, B2 { void foo() { X::f(); // OK, вызов X::f (квалифицированный поиск) f(); // OK, вызов B1::f (неквалифицированный поиск) } }; // Правила C++98: B1::f скрывает X::f, поэтому даже если X::f доступен из D // через B2, он не находится поиском имени из D. // Правила C++11: набор поиска для f в D ничего не находит, переходит к базовым классам // набор поиска для f в B1 находит B1::f и завершается // слияние заменяет пустой набор, теперь набор поиска для f в C содержит B1::f в B1 // набор поиска для f в B2 ничего не находит, переходит к базовым классам // поиск для f в X находит X::f // слияние заменяет пустой набор, теперь набор поиска для f в B2 содержит X::f в X // слияние в C обнаруживает, что каждый подобъект (X) в наборе поиска в B2 является базовым // для каждого подобъекта (B1), уже объединенного, поэтому набор B2 отбрасывается // C остается только с B1::f, найденным в B1 // (если использовалась struct D : B2, B1, то последнее слияние *заменило* бы // до сих пор объединенный X::f в X, потому что каждый подобъект, уже добавленный в C (то есть X) // был бы базовым для хотя бы одного подобъекта в новом наборе (B1), конечный // результат был бы тем же: набор поиска в C содержит только B1::f, найденный в B1)
-
Поиск неквалифицированного имени, который находит статические члены
B, вложенные типыBи перечислители, объявленные вB, является однозначным, даже если в дереве наследования исследуемого класса присутствует несколько невиртуальных базовых подобъектов типаB:
struct V { int v; }; struct B { int a; static int s; enum { e }; }; struct B1 : B, virtual V {}; struct B2 : B, virtual V {}; struct D : B1, B2 {}; void f(D& pd) { ++pd.v; // OK: только один v, потому что только один виртуальный базовый подобъект ++pd.s; // OK: только один статический B::s, даже если найден и в B1, и в B2 int i = pd.e; // OK: только один перечислитель B::e, даже если найден и в B1, и в B2 ++pd.a; // ошибка, неоднозначность: B::a в B1 и B::a в B2 }
Определение дружественной функции
Для имени, используемого в определении friend функции внутри тела класса, предоставляющего дружественный доступ, неполный поиск имени выполняется так же, как для функции-члена. Для имени, используемого в friend функции, которая определена вне тела класса, неполный поиск имени выполняется так же, как для функции в пространстве имен.
int i = 3; // найден 3-й для f1, найден 2-й для f2 struct X { static const int i = 2; // найден 2-й для f1, никогда не найден для f2 friend void f1(int x) { // int i; // найден 1-й i = x; // находит и изменяет X::i } friend int f2(); // static const int i = 2; // найден 2-й для f1 в любой области класса }; void f2(int x) { // int i; // найден 1-й i = x; // находит и изменяет ::i }
Объявление дружественной функции
Для имени, используемого в деклараторе объявления friend функции, которая объявляет дружественной функцию-член из другого класса, если имя не является частью какого-либо шаблонного аргумента в идентификаторе декларатора , неподписанный поиск сначала исследует всю область видимости класса функции-члена. Если не найдено в этой области (или если имя является частью шаблонного аргумента в идентификаторе декларатора), поиск продолжается как для функции-члена класса, который предоставляет дружественный доступ.
template<class T> struct S; // класс, чьи функции-члены объявляются дружественными struct A { typedef int AT; void f1(AT); void f2(float); template<class T> void f3(); void f4(S<AT>); }; // класс, который предоставляет дружественный доступ для f1, f2 и f3 struct B { typedef char AT; typedef float BT; friend void A::f1(AT); // поиск AT находит A::AT (AT найден в A) friend void A::f2(BT); // поиск BT находит B::BT (BT не найден в A) friend void A::f3<AT>(); // поиск AT находит B::AT (поиск не выполняется в A, потому что // AT находится в идентификаторе декларатора A::f3<AT>) }; // шаблон класса, который предоставляет дружественный доступ для f4 template<class AT> struct C { friend void A::f4(S<AT>); // поиск AT находит A::AT // (AT не находится в идентификаторе декларатора A::f4) };
Аргумент по умолчанию
Для имени, используемого в аргументе по умолчанию в объявлении функции, или имени, используемого в выражении части инициализатора члена конструктора, имена параметров функции находятся в первую очередь, до исследования объемлющих областей видимости блока, класса или пространства имен:
class X { int a, b, i, j; public: const int& r; X(int i): r(a), // инициализирует X::r для ссылки на X::a b(i), // инициализирует X::b значением параметра i i(i), // инициализирует X::i значением параметра i j(this->i) // инициализирует X::j значением X::i {} }; int a; int f(int a, int b = a); // ошибка: поиск a находит параметр a, а не ::a // и параметры не допускаются в качестве аргументов по умолчанию
Определение статического члена данных
Для имени, используемого в определении static data member , поиск осуществляется тем же способом, что и для имени, используемого в определении функции-члена.
struct X { static int x; static const int n = 1; // найдено первым }; int n = 2; // найдено вторым int X::x = n; // находит X::n, устанавливает X::x в 1, а не в 2
Объявление перечислителя
Для имени, используемого в инициализирующей части объявления перечислителя , сначала находятся ранее объявленные перечислители в том же перечислении, прежде чем поиск неполного имени переходит к проверке объемлющего блока, класса или пространства имен.
const int RED = 7; enum class color { RED, GREEN = RED + 2, // RED находит color::RED, а не ::RED, поэтому GREEN = 2 BLUE = ::RED + 4 // квалифицированный поиск находит ::RED, BLUE = 11 };
Обработчик функции try блока
Для имени, используемого в обработчике блока function try block , поиск имени выполняется так, как если бы имя использовалось в самом начале самого внешнего блока тела функции (в частности, параметры функции видны, но имена, объявленные в этом самом внешнем блоке, не видны)
int n = 3; // найден 3-й int f(int n = 2) // найден 2-й try { int n = -1; // никогда не найден } catch(...) { // int n = 1; // найден 1-й assert(n == 2); // поиск n находит параметр функции f throw; }
Перегруженный оператор
Для оператора , используемого в выражении (например, operator + в a + b ), правила поиска несколько отличаются от оператора, используемого в явном выражении вызова функции, таком как operator + ( a, b ) : при разборе выражения выполняются два отдельных поиска: для перегрузок операторов-нечленов и для перегрузок операторов-членов (для операторов, где разрешены обе формы). Эти наборы затем объединяются со встроенными перегрузками операторов на равных основаниях, как описано в разрешении перегрузки . Если используется явный синтаксис вызова функции, выполняется обычный неквалифицированный поиск имени:
struct A {}; void operator+(A, A); // пользовательский operator+ не-член struct B { void operator+(B); // пользовательский operator+ член void f(); }; A a; void B::f() // определение функции-члена B { operator+(a, a); // ошибка: обычный поиск имени из функции-члена // находит объявление operator+ в области видимости B // и останавливается там, не достигая глобальной области a + a; // OK: поиск членов находит B::operator+, поиск не-членов // находит ::operator+(A, A), разрешение перегрузки выбирает ::operator+(A, A) }
Определение шаблона
Для независимого имени , используемого в определении шаблона, поиск неквалифицированного имени происходит при анализе определения шаблона. Привязка к объявлениям, сделанным в этой точке, не зависит от объявлений, видимых в точке инстанцирования. Для зависимого имени , используемого в определении шаблона, поиск откладывается до момента определения аргументов шаблона, после чего ADL анализирует объявления функций с внешним связыванием (до C++11) , видимые из контекста определения шаблона, а также из контекста инстанцирования шаблона, в то время как не-ADL поиск анализирует только объявления функций с внешним связыванием (до C++11) , видимые из контекста определения шаблона (другими словами, добавление нового объявления функции после определения шаблона не делает его видимым, за исключением ADL). Поведение не определено, если существует лучшее соответствие с внешним связыванием в пространствах имён, анализируемых ADL поиском, объявленное в некоторой другой единице трансляции, или если поиск был бы неоднозначным при анализе этих единиц трансляции. В любом случае, если базовый класс зависит от параметра шаблона, его область видимости не анализируется неквалифицированным поиском имени (ни в точке определения, ни в точке инстанцирования).
void f(char); // первое объявление f template<class T> void g(T t) { f(1); // независимое имя: поиск находит ::f(char) и связывает его сейчас f(T(1)); // зависимое имя: поиск отложен f(t); // зависимое имя: поиск отложен // dd++; // независимое имя: поиск не находит объявления } enum E { e }; void f(E); // второе объявление f void f(int); // третье объявление f double dd; void h() { g(e); // инстанцирует g<E>, в этот момент // второе и третье использование имени 'f' // ищутся и находят ::f(char) (поиском) и ::f(E) (по ADL) // затем разрешение перегрузки выбирает ::f(E). // Это вызывает f(char), затем f(E) дважды g(32); // инстанцирует g<int>, в этот момент // второе и третье использование имени 'f' // ищутся и находят только ::f(char) // затем разрешение перегрузки выбирает ::f(char) // Это вызывает f(char) три раза } typedef double A; template<class T> class B { typedef int A; }; template<class T> struct X : B<T> { A a; // поиск A находит ::A (double), а не B<T>::A };
Примечание: см. правила поиска зависимых имён для объяснения и следствий этого правила.
Имя шаблона
|
Этот раздел не завершён
Причина: двусторонний поиск имени шаблона после -> и . |
Член шаблона класса вне шаблона
| Этот раздел не завершён |
Отчёты о дефектах
Следующие отчеты о дефектах, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Applied to | Behavior as published | Correct behavior |
|---|---|---|---|
| CWG 490 | C++98 |
любое имя в аргументе шаблона в объявлении
дружественной функции-члена не выполнялось поиск в области видимости класса функции-члена |
исключает только имена
в аргументах шаблона в идентификаторе декларатора |
| CWG 514 | C++98 |
любое неквалифицированное имя, используемое в области
видимости пространства имен, сначала искалось в этой области |
неквалифицированные имена, используемые для определения
переменной-члена пространства имен вне этого пространства имен, сначала ищутся в этом пространстве имен |
Ссылки
- Стандарт C++23 (ISO/IEC 14882:2024):
-
- 6.5 Поиск имен [basic.lookup] (стр: 44-45)
-
- 6.5.2 Поиск имен членов [class.member.lookup] (стр: 45-47)
-
- 13.8 Разрешение имен [temp.res] (стр: 399-403)
- Стандарт C++20 (ISO/IEC 14882:2020):
-
- 6.5 Поиск имен [basic.lookup] (стр: 38-50)
-
- 11.8 Поиск имен членов [class.member.lookup] (стр: 283-285)
-
- 13.8 Разрешение имен [temp.res] (стр: 385-400)
- Стандарт C++17 (ISO/IEC 14882:2017):
-
- 6.4 Поиск имен [basic.lookup] (стр: 50-63)
-
- 13.2 Поиск имен членов [class.member.lookup] (стр: 259-262)
-
- 17.6 Разрешение имен [temp.res] (стр: 375-378)
- Стандарт C++14 (ISO/IEC 14882:2014):
-
- 3.4 Поиск имен [basic.lookup] (стр: 42-56)
-
- 10.2 Поиск имен членов [class.member.lookup] (стр: 233-236)
-
- 14.6 Разрешение имен [temp.res] (стр: 346-359)
- Стандарт C++11 (ISO/IEC 14882:2011):
-
- 3.4 Поиск имен [basic.lookup]
-
- 10.2 Поиск имен членов [class.member.lookup]
-
- 14.6 Разрешение имен [temp.res]
- Стандарт C++98 (ISO/IEC 14882:1998):
-
- 3.4 Поиск имен [basic.lookup]
-
- 10.2 Поиск имен членов [class.member.lookup]
-
- 14.6 Разрешение имен [temp.res]