Как-то отвечая на
вопрос на сайте www.stackoverflow.com, я хотел подготовить, как мне казалось, замечательный пример, демонстрирующий мною сказанное. Однако этот пример я так и не включил в свой ответ по причине бага компилятора GCC, который я обнаружил, запуская пример с помощью онлайнового компилятора на www.ideone.com.
Вопрос касался дружеских функций и конкретно следующей цитаты из стандарта
C++ (параграф №3, секция 7.3.1.2 Namespace member definitions):
цитата: |
3 Every name first declared in a namespace is a member of that namespace. If a friend declaration in a non-local class first declares a class, function, class template or function template97 the friend is a member of the innermost enclosing namespace. The friend declaration does not by itself make the name visible to unqualified lookup (3.4.1) or qualified lookup (3.4.3). [ Note: The name of the friend will be visible in its namespace if a matching declaration is provided at namespace scope (either before or after the class definition granting friendship). —end note ] If a friend function or function template is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2). If the name in a friend declaration is neither qualified nor a template-id and the declaration is a function or an elaborated-type-specifier, the lookup to determine whether the entity has been previously declared shall not consider any scopes outside the innermost enclosing namespace. [ Note: The other forms of friend declarations cannot declare a new member of the innermost enclosing namespace and thus follow the usual lookup rules. —end note ] |
|
Пример должен был демонстрировать, что хотя объявление функции в классе как дружественной является в то же время объявлением этой функции в пространстве имен, где объявлен класс, тем не менее оно остается невидимым для поиска неквалифицированных имен компилятором, пока она, функция, не будет еще раз объявлена уже вне класса.
Можно было бы сейчас привести мой пример кода целиком, но лучше его разбить на части, чтобы наличие бага компилятора было более очевидным и понятным.
Вот первая часть моего примера, демонстрирующая, что имя дружеской функции, объявленной в классе будет невидимым для поиска имен компилятором:
#include <iostream>
namespace N
{
struct A;
}
void f( int );
struct N::A
{
friend void f( int );
};
namespace N
{
void g() { f( 10 ); }
void f( int ) { std::cout << "void N::f( int )" << std::endl; }
}
void f( int ) { std::cout << "void ::f( int )" << std::endl; }
int main()
{
N::g();
return 0;
}
если запустить это код на выполнение, то он успешно скомпилируется и на консоль будет выведено сообщение
void ::f( int )
Что это означает?
Хотя функция
void N::f( int ) объявлена в
struct N::A в качестве дружественной функции перед определением функции
void N::g(), ее имя не видимо, и компилятор выберет глобальную функцию
void ::f( int ).
Если вы поместите еще одно объявление функции
void N::f( int ) перед определением функции
void g(), тогда компилятор ее "увидет" и выберет именно ее:
#include <iostream>
namespace N
{
struct A;
}
void f( int );
struct N::A
{
friend void f( int );
};
namespace N
{
void f( int );
void g() { f( 10 ); }
void f( int ) { std::cout << "void N::f( int )" << std::endl; }
}
void f( int ) { std::cout << "void ::f( int )" << std::endl; }
int main()
{
N::g();
return 0;
}
На этот раз вывод на консоль будет
void N::f( int )
Вторая часть моего примера должна была продемонстрировать, что несмотря на то, что дружественная функция остается невидимой, если не поместить в пространстве имен еще ее одно объявление, тем не менее компилятор должен ее найти благодаря включению механизма поиска зависимых от аргументов имен (
ADL).
Вот соответствующий код примера:
#include <iostream>
namespace N
{
struct A;
}
void f( const N::A & );
struct N::A
{
friend void f( const A & );
};
namespace N
{
void g() { f( A() ); }
void f( const A & ) { std::cout << "void N::f( const A & )" << std::endl; }
}
void f( const N::A & ) { std::cout << "void ::f( const N::A & )" << std::endl; }
int main()
{
N::g();
return 0;
}
Если запустить этот код с помощью
GCC компилятора на сайте www.ideone.com, то компилятор выдаст сообщение об ошибке:
Ошибка компиляции time: 0 memory: 3340 signal:0prog.cpp: In function ‘void N::g()’:
prog.cpp:42:20: error: call of overloaded ‘f(N::A)’ is ambiguous
void g() { f( A() ); }
^
prog.cpp:42:20: note: candidates are:
prog.cpp:33:6: note: void f(const N::A&)
void f( const N::A & );
^
prog.cpp:37:14: note: void N::f(const N::A&)
friend void f( const A & );
^
На этот раз вызов функции
f неоднозначен. Компилятор найдет глобальную функцию
void ::f( const A & ), так как это имя видимо, а также найдет дружественную функцию
void N::f( const A & ) благодаря механизму поиска имен функций, зависящего от аргументов. То есть хотя дружественная функция по-прежнему невидима, но из-за того, что ее аргумент является определенным пользователем тип, то имя функции также ищется в классе, определяющим этот тип.
Вот мы и подошли к багу компилятора
GCC. Если теперь объединить эти два примера кода в одну программу, то логически мы должны получить то же самое сообщение об ошибке, что и в последнем примере кода. Однако компилятор выдает что-то несуразное. Его сообщение об ошибке может озадачить любого программиста.
Вот код программа, объединяющей два предыдущих примера:
#include <iostream>
namespace N
{
struct A;
}
void f( int );
void f( const N::A & );
struct N::A
{
friend void f( int );
friend void f( const A & );
};
namespace N
{
void g() { f( 10 ); }
void f( int ) { std::cout << "void N::f( int )" << std::endl; }
void f( const A & ) { std::cout << "void N::f( const A & )" << std::endl; }
}
void f( int ) { std::cout << "void ::f( int )" << std::endl; }
void f( const N::A & ) { std::cout << "void ::f( const N::A & )" << std::endl; }
int main()
{
N::g();
return 0;
}
Единственное, что я не сделал, - это не включил вызов функции
f( A() ); в тело функции
void g().
И вот соответствующее сообщение об ошибке:
Ошибка компиляции time: 0 memory: 3340 signal:0prog.cpp: In function ‘void N::g()’:
prog.cpp:68:19: error: ‘f’ was not declared in this scope
void g() { f( 10 ); }
^
prog.cpp:68:19: note: suggested alternatives:
prog.cpp:58:6: note: ‘f’
void f( const N::A & );
^
prog.cpp:63:14: note: ‘N::f’
friend void f( const A & );
^
Неожиданно компилятор указывает на вызов функции
f( 10 );, который ранее никаких нареканий у компилятора не вызывал, и в качестве альтернативы предлагает либо функцию
void f( const N::A & ) либо
void N::f( const A & ).
То есть получается, что глобальную функцию
void ::f( int ) он неожиданно перестал видеть, хотя никаких причин на это у компилятора нет.
Если у кого есть компилятор
MS VC++ 2013, попробуйте протестировать код с его помощью. Было бы интересно узнать, как он себя ведет с этими примерами кода. У меня есть подозрение, что и этот компилятор не сможет корректно его обработать.