Каков объем встроенных функций друзей?
После поиска по SO один вопрос научил меня, что лексическая область действия встроенной функции-друга - это класс, в котором она определена, то есть она может получить доступ, например, к typedef
в классе без квалификации их. Но потом мне стало интересно, какова реальная область применения такой функции? GCC по крайней мере отвергает все мои попытки назвать его. Может ли такая функция, как в примере, когда-либо вызываться с помощью средств, отличных от ADL, что здесь невозможно без аргументов?
Приветствуются стандартные цитаты, так как в настоящее время я не могу получить доступ к своей копии.
namespace foo{
struct bar{
friend void baz(){}
void call_friend();
};
}
int main(){
foo::baz(); // can't access through enclosing scope of the class
foo::bar::baz(); // can't access through class scope
}
namespace foo{
void bar::call_friend(){
baz(); // can't access through member function
}
}
приводит к этим ошибкам:
prog.cpp: In function ‘int main()’:
prog.cpp:9: error: ‘baz’ is not a member of ‘foo’
prog.cpp:10: error: ‘baz’ is not a member of ‘foo::bar’
prog.cpp: In member function ‘void foo::bar::call_friend()’:
prog.cpp:15: error: ‘baz’ was not declared in this scope
6 ответов
Когда вы объявляете friend
Функция с неквалифицированным идентификатором в классе называет функцию в ближайшем окружении пространства имен.
Если эта функция ранее не была объявлена, friend
объявление не делает эту функцию видимой в этой области для обычного поиска. Это делает объявленную функцию видимой для аргументно-зависимого поиска.
Это подчеркивается во многих примечаниях, но окончательное утверждение содержится в 7.3.1.2/3 (ISO/IEC 14882:2011):
Каждое имя, впервые объявленное в пространстве имен, является членом этого пространства имен. Если
friend
Объявление в нелокальном классе сначала объявляет класс или функцию. Другим классом или функцией является член внутреннего вложенного пространства имен. Имя друга не найдено ни в неквалифицированном поиске (3.4.1), ни в квалифицированном поиске (3.4.3), пока в этой области пространства имен не будет предоставлено соответствующее объявление (ни до, ни после определения класса, предоставляющего дружбу). Если вызывается функция друга, ее имя может быть найдено с помощью поиска имен, который рассматривает функции из пространств имен и классов, связанных с типами аргументов функции (3.4.2). Если имя вfriend
объявление не является ни квалифицированным, ни шаблоном-идентификатором, а объявление является функцией или подробным спецификатором типа; поиск, чтобы определить, был ли объект ранее объявлен, не должен рассматривать какие-либо области за пределами внутреннего вложенного пространства имен.
"Язык программирования C++, 3-е издание (Stroustrap)": p279:
I. "Как и декларация члена, декларация друга не вводит имя во вложенную область"
II. "Класс друга должен быть предварительно объявлен во вложенной области или определен в области некласса, немедленно включающей в себя класс, который объявляет его другом"
III. "Другую функцию можно явно объявить точно так же, как дружественные классы, или ее можно найти через ее типы аргументов (§8.2.6), как если бы она была объявлена в области видимости некласса, немедленно охватывающей ее класс".
Внутривенно "Из этого следует, что функция друга должна быть явно объявлена во вложенной области видимости или принимать аргумент своего класса. Если нет, друга нельзя вызвать. Например:"
//no f() here
void g();
class X{
friend void f(); //useless
friend void g(); //can be found because it is declared outside of class scope
friend void h(const X&); //can be found because the arguments access class members
};
void f() { } //enemy of X :)
Но в вашем случае есть еще кое-что, что связано с пространством имен, потому что если вы поместите правильное объявление в foo, например:
namespace foo{
struct bar{
friend void baz(const &bar){};
void call_friend();
}
}
не компилируется. Тем не менее, если вы объявите это за пределами foo, это работает как шарм. Теперь рассмотрим, что на самом деле глобальные, локальные, структура и классы фактически являются пространствами имен. Теперь это приводит к выводу, что baz(const &)
неявно определяется в глобальной области видимости.
Это компилирует:
namespace foo{
struct bar{
friend void baz(const bar&){};
void call_friend();
};
}
int main(){
foo::bar k;
baz(k);
return 0;
}
Поэтому есть две проблемы:
- Декларация друга не вводит имя в прилагаемой области, если только IV. Таким образом, исходная программа не может найти baz(), потому что она не была должным образом объявлена.
- Если IV, то есть ADL, то функция находится в foo, но недоступна как foo::baz(k) из-за ADL. Вам нужно будет явно указать baz(const bar&) в foo, чтобы получить доступ к нему по определенному имени.
Спасибо, надеюсь, это поможет, но, конечно, мне понравился вызов:) .
Интересно!
Кажется, что компилятор не знает, к какой области он принадлежит (и, честно говоря, нет никаких подсказок), и, следовательно, он не входит в область. Полагаю, появятся какие-то стандартные раскопки.
Примечание. Если вы явно добавите объявление в конкретную область действия, оно начнет работать так, как ожидается.
namespace foo
{
void baz(); // declare it here and now it works in foo namespace etc.
struct bar
{
friend void baz(){}
void call_friend();
};
}
Копая стандарт, я нахожу:
11.3 Друзья [class.friend]
Пункт 6
Функция может быть определена в объявлении друга класса тогда и только тогда, когда класс является нелокальным классом (9.8), имя функции является неквалифицированным, а функция имеет область пространства имен.
[ Example:
class M { friend void f() { } // definition of global f, a friend of M,
// not the definition of a member function
};
— end example ]
Пункт 7
Такая функция неявно встроенная. Функция-друг, определенная в классе, находится в (лексической) области действия класса, в котором она определена. Функция друга, определенная вне класса, не является (3.4.1).
Замечания:
Свободно стоящая функция, которая не принимает параметр, не очень полезна в качестве друга. Так как у него не будет объекта, чтобы воспользоваться его дружбой (я предполагаю, что объекты продолжительности хранения статической области видимости файла).
А
#include <iostream>
struct foo
{
struct local_class{};
friend void greet(local_class o, int) { std::cout << "Hello World!\n"; }
};
int main(int argc, char *argv[])
{
foo::local_class o;
greet(o,1);
return 0;
}
В этом примере
namespace foo{
struct bar{
friend void baz(){}
void call_friend();
};
}
int main(){
foo::baz(); // can't access through enclosing scope of the class
foo::bar::baz(); // can't access through class scope
}
namespace foo{
void bar::call_friend(){
baz(); // can't access through member function
}
}
foo::baz()
недоступен, потому что имяbaz
не виден в области именfoo
, Если я правильно помню (§ 3.4/2), здесь применимо.foo::bar::baz()
недоступен, потому что друзья не являются членами класса, а область действия встроенной функции друга - это пространство имен или класс, в котором существует их определение, поэтому вы не можете получить к ним доступ за пределами этой области.
Если вы поставите декларацию baz()
в foo
название функции baz
будет виден в foo
и определение будет рассмотрено во вложенном объеме bar
,
namespace foo{
void baz(); // declaration at namespace scope
struct bar{
friend void baz(){}
};
void call_friend() {
baz(); // ok
}
}
int main()
{
foo::baz(); // ok, bar will be looked up in the nested scope of foo::bar.
}
Я думаю ты путаешь friend
а также private
, Объявляя функцию friend
вы предоставляете ему доступ к своим частным пользователям, а не предоставляете другим функциям доступ к нему. Во всяком случае, любая функция-член struct
доступен любому объекту, потому что struct
участники являются публичными по умолчанию.
Тем не менее, в вашем случае baz
не доступен, потому что, делая friend void baz(){}
Вы действительно не объявили функцию baz
Вы только что сказали, что это friend
функция. Вы можете просто удалить friend
ключевое слово, и это решит все вопросы.