Форум » C/C++ » friend functions, их видимость и баг компилятора GCC. » Ответить

friend functions, их видимость и баг компилятора GCC.

Сыроежка: Как-то отвечая на вопрос на сайте www.stackoverflow.com, я хотел подготовить, как мне казалось, замечательный пример, демонстрирующий мною сказанное. Однако этот пример я так и не включил в свой ответ по причине бага компилятора GCC, который я обнаружил, запуская пример с помощью онлайнового компилятора на www.ideone.com. Вопрос касался дружеских функций и конкретно следующей цитаты из стандарта C++ (параграф №3, секция 7.3.1.2 Namespace member definitions): [quote] 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 ][/quote] Пример должен был демонстрировать, что хотя объявление функции в классе как дружественной является в то же время объявлением этой функции в пространстве имен, где объявлен класс, тем не менее оно остается невидимым для поиска неквалифицированных имен компилятором, пока она, функция, не будет еще раз объявлена уже вне класса. Можно было бы сейчас привести мой пример кода целиком, но лучше его разбить на части, чтобы наличие бага компилятора было более очевидным и понятным. Вот первая часть моего примера, демонстрирующая, что имя дружеской функции, объявленной в классе будет невидимым для поиска имен компилятором: [pre2] #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; } [/pre2] если запустить это код на выполнение, то он успешно скомпилируется и на консоль будет выведено сообщение [pre2] void ::f( int ) [/pre2] Что это означает? Хотя функция void N::f( int ) объявлена в struct N::A в качестве дружественной функции перед определением функции void N::g(), ее имя не видимо, и компилятор выберет глобальную функцию void ::f( int ). Если вы поместите еще одно объявление функции void N::f( int ) перед определением функции void g(), тогда компилятор ее "увидет" и выберет именно ее: [pre2] #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; } [/pre2] На этот раз вывод на консоль будет [pre2] void N::f( int ) [/pre2] Вторая часть моего примера должна была продемонстрировать, что несмотря на то, что дружественная функция остается невидимой, если не поместить в пространстве имен еще ее одно объявление, тем не менее компилятор должен ее найти благодаря включению механизма поиска зависимых от аргументов имен (ADL). Вот соответствующий код примера: [pre2] #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; } [/pre2] Если запустить этот код с помощью GCC компилятора на сайте www.ideone.com, то компилятор выдаст сообщение об ошибке: [pre2] Ошибка компиляции 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 & ); ^ [/pre2] На этот раз вызов функции f неоднозначен. Компилятор найдет глобальную функцию void ::f( const A & ), так как это имя видимо, а также найдет дружественную функцию void N::f( const A & ) благодаря механизму поиска имен функций, зависящего от аргументов. То есть хотя дружественная функция по-прежнему невидима, но из-за того, что ее аргумент является определенным пользователем тип, то имя функции также ищется в классе, определяющим этот тип. Вот мы и подошли к багу компилятора GCC. Если теперь объединить эти два примера кода в одну программу, то логически мы должны получить то же самое сообщение об ошибке, что и в последнем примере кода. Однако компилятор выдает что-то несуразное. Его сообщение об ошибке может озадачить любого программиста. Вот код программа, объединяющей два предыдущих примера: [pre2] #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; } [/pre2] Единственное, что я не сделал, - это не включил вызов функции f( A() ); в тело функции void g(). И вот соответствующее сообщение об ошибке: [pre2] Ошибка компиляции 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 & ); ^ [/pre2] Неожиданно компилятор указывает на вызов функции f( 10 );, который ранее никаких нареканий у компилятора не вызывал, и в качестве альтернативы предлагает либо функцию void f( const N::A & ) либо void N::f( const A & ). То есть получается, что глобальную функцию void ::f( int ) он неожиданно перестал видеть, хотя никаких причин на это у компилятора нет. Если у кого есть компилятор MS VC++ 2013, попробуйте протестировать код с его помощью. Было бы интересно узнать, как он себя ведет с этими примерами кода. У меня есть подозрение, что и этот компилятор не сможет корректно его обработать.

Ответов - 2

Сыроежка: У этой темы, которая была создана 17 апреля этого, 2014, года неожиданно появилось продолжение. Оно неожиданное потому, что я возвратился к этой теме сегодня совершенно случайно, так как увидел, что кто-то читает эту тему, и мне стало интересно, исправили ли баг компилятора GCC, о котором я изначально писал в этой теме. Как оказалось, ситуация стала еще более запутанной, так как я для тестирования примеров кода задействовал еще один компилятор - онлайновый компилятор MS VC++. Итак, рассмотрим те же самые примеры кода из предыдущего сообщения и какова сейчас реакция на них двух указанных компиляторов. Вот первый пример кода [pre2] #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; } [/pre2] В этом примере имеются две одноименные функции f. Одна из них объявлена в глобальном пространстве имен, другая объявляется в пространстве имен с именем N и является дружественной для класса A. Функция g вызывает функцию f. Осталось разобраться, какая функция из двух будет вызвана, и будет ли код вообще компилироваться. На момент объявления функции g дружественная функция невидима в пространстве имен N. Поэтому компилятор для этого вызова обнаружит функцию f, объявленную в глобальном пространстве имен согласно правилам поиска неквалифицированных имен. С другой стороны, согласно поиску, зависящему от аргументов функции, компилятор также обнаружит дружественную функцию f, В результате чего возникнет неоднозначность. Это по теории, если следовать "букве стандарта", и если я ничего не напутал. Теперь интересно посмотреть, а что по этому поводу думают компиляторы. GCC 4.8.1 на www.ideone.com со мной полностью согласен, а потому выдает сообщение об ошибке [pre2] prog.cpp: In function ‘void N::g()’: prog.cpp:17:21: error: call of overloaded ‘f(N::A)’ is ambiguous void g() { f( A() ); } ^ [/pre2] А онлайновый компилятор MS VC++ "проглатывает" этот код, "не поперхнувшись", и выдает [pre2] void N::f( const A & ) [/pre2] Далее начинается совсем уж непонятное. Если слегка изменить вышеприведенную программу и добавить всего лишь одну перегруженную функцию с именем f следующим образом [pre2] #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( A() ); } 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; } [/pre2] то теперь неожиданно компилятор GCC 4.8.1 меняет свое мнение о неоднозначности и успешно компилирует данный код, выводя на консоль [pre2] void N::f( const A & ) [/pre2] Что касается онлайнового компилятора MS VC++, то он, как партизан, не сдается и, по-прежнему, компилирует код. Прямо какой-то детектив!:) .

Сыроежка: Как мне уже сообщили на форуме по обсуждению стандарта C++ комитета по стандартизации C++ компилятор Clang 3.4 выдает ошибку компиляции для обеих вышеприведенных программ в виду неоднозначности вызова функции f. Итак, три разных компилятора C++ и три различных результата компиляции для одних и тех же программ!



полная версия страницы