Так как уже достаточно много людей посмотрело данный вопрос, то пора уже дать ответ на него.
Ответ выглядит тривиальным. И я так думал до вчерашнего дня. Но, поэкспериментировав с поиском имен на MS VC++ 2010, я стал сомневаться, а действительно ли я все правильно понимаю? Могу лишь с уверенностью сказать, что я обнаружил очередной баг MS VC++ 2010, но тем не менее это не развеяло мои сомнения. Но об этом я напишу в следующей теме через пару дней. А пока ответ на данный вопрос.
Сначала рассмотрим, какие имена присутствуют в глобальном пространстве имен, если исключить директиву
using. Имеется лишь одно имя - это имя пространства имен
X (если не считать функции
main).
Когда была включена директива
using namespace X::Y::Z; , то в глобальное пространство имен добавились члены указанного пространства имен
X::Y::Z, а именно имя класса
Inner.
Поэтому вполне правомерно написание
void Inner::callMe в определении функции, так как неквалифицированное имя
Inner будет найдено в глобальном пространстве имен.
Теперь, что касается имени
Outer в определении функции. Компилятор будет сначала искать это имя в области определения класса
Inner. Но там имя
Outer не определяется. Тогда компилятор будет искать это имя в пространстве имен, в котором заключено определение класса. В данном случае это глобальное пространство имен, так как имя
Inner было туда помещено с помощью директивы
using.
Но в глобальном пространстве имен нет определения этого имени. Поэтому компилятор должен выдать сообщение об ошибке, что имя
Outer не найдено.
Поэтому можно сделать вывод, что компилятор
MS VC++ 2010 в данной ситуации ведет себя в соответствии со стандартом, тогда как
g++ ведет себя не корректно. Более того такое поведение компилятора
g++ ставит под сомнение вообще его способность правильно искать имена. Я покажу ниже, что я имею в виду. А пока возникает вопрос, как сделать так, чтобы компилятор увидел имя
Outer?
Наиболее простой способ, это нужно просто "подцепить" имя
Outer к именам пространства имен
Z, когда имена этого пространства мы переносили в глобальное пространство. Для этого достаточно в свою очередь включить директиву
using в пространство имен
Z. Выглядеть это будет так
namespace X
{
namespace Y
{
class Outer {};
namespace Z
{
using namespace Y;
class Inner
{
void callMe(Outer* param);
};
}
}
}
using namespace X::Y::Z;
void Inner::callMe(Outer* param)
{
}
То есть в пространство имен
Z вчтавлена директива
using namespace Y;. Что изменилось? Теперь в поиск имен мы для компилятора добавили дополнительный шаг - просматривать пространство имен
Y.
Такой код должен нормально компилироваться без сообщений об ошибки.
Но еще лучше не засорять глобальное пространство имен, то есть не включать в него директиву
using. Это к тому же упростило поиск неквалифицированных имен. Соответствующий код будет выглядеть так
namespace X
{
namespace Y
{
class Outer {};
namespace Z
{
class Inner
{
void callMe(Outer* param);
};
}
}
}
void X::Y::Z::Inner::callMe(Outer* param)
{
}
Тогда алгоритм поиска будет следующим. Сначала имя
Outer будет искаться в определении класса
Inner. Так как там оно не будет найдено, то это имя будет искаться в пространстве имен, где находится определение класса
Inner, то есть в пространстве имен
Z. Так как и там имя
Outer не будет найдено, то поиск продолжится в пространстве имен, которое в себе заключает пространство имен
Z, то есть в пространстве имен
Y. И там компилятор, наконец-то, найдет имя
Outer, поэтому код должен нормально скомпилироваться.
Теперь вернемся к моему замечанию, что поведение компилятора
g++ вызывает много вопросов. Для этого изменим первоначальный пример и перегрузим имя функции
callMe в классе
Inner.
namespace X
{
namespace Y
{
class Outer {};
namespace Z
{
class Inner
{
void callMe(Outer* param);
void callMe(int* param);
};
}
}
}
using namespace X::Y::Z;
typedef int Outer;
void Inner::callMe(Outer* param)
{
}
Возникает вопрос, какая из двух функций
callMe определяется? Если следовать логике поиска имен компилятора
g++, то должна возникнуть ситуация неоднозначности, так как компилятор
g++ должен найти два определения имени
Outer. Первое - это то, которое он находил ранее в вышеприведенном примере, а второе - это добавленное нами имя с помощью
typedef.
Компилятор MS VC++ 2010 будет вести себя адекватно. То есть для него никакой неоднозначности не будет, и данное определение
callMe будет соответствовать объявлению
void callMe( int *param ); Вы в этом можете самостоятельно убедиться, задав тело этой функции, например, следующим образом
void Inner::callMe(Outer* param) { std::cout << "void Inner::callMe( int * )\n"; }
А в функцию
main нужно будет вставить следующий код
int main()
{
Inner obj;
int i = 0;
obj.callMe( &i );
}
Интересно так же рассмотреть случай, когда мы "подцепляем" пространство имен
Y к пространству имен
Z с помощью директивы
using, как это было показано выше, и в то же время в глобальном пространстве имен имеется объявление
typedef int Outer; Как тогда компилятору сообщить, какая из двух функций определяется? В этом случае мы должны квалифицировать имя параметра. Например, для функции
callMe с параметром
int * определение будет выглядеть следующим образом
namespace X
{
namespace Y
{
class Outer {};
namespace Z
{
using namespace Y;
class Inner
{
void callMe(Outer* param);
void callMe(int* param);
};
}
}
}
using namespace X::Y::Z;
typedef int Outer;
void Inner::callMe(::Outer* param)
{
}
То есть имя
Outer было квалифицировано как имя из глобального пространства имен:
::Outer, которое является алиасом типа
int.
На этом мой анализ исходного вопроса заканчивается. Если у кого-то есть замечания и возражения, то будет интересно их услышать.
Я в начале написал, что у меня возникли сомнения в моих знаниях того, как происходит поиск имен, так как экспериментируя с подобным поиском, я столкнулся с непонятными для меня сообщениями компилятора
MS VC++ 2010. Но об этом я напишу через пару дней в новой теме.