Qubc
@Qubc
Ненавижу полисемию.

Почему дружественная функция, определённая внутри класса с первым параметром встроенного типа, недоступна вне определения класса?

Перегрузка операторов это синтаксический сахар. Сами операторы в основе своей просто функции. Все операторы в выражениях, содержащих только встроенные типы данных, изменяться не могут. Увидел пример, где определение перегрузки глобального бинарного оператора зачем-то делается внутри класса.

У функции f5() список параметров начинается со встроенного типа. Почему компилятор не видит f5() если в списке параметров нет хотя бы одного параметра с типом A ?

void f1(int var) { ; }
class A {
  int data;
public:
  friend void f1(int var);
  friend void f2(A a) { ; } 
  friend void f3(A a, int var) { ; } 
  friend void f4(int var, A a) { ; } 
  friend void f5(int var) { ; }      //???
  friend void operator% (int var, A a) { ; }
};
int main(void) {
  A a;

//! a.f1(1);    // class A' has no member named 'f1' , OK
//! a.f2(a);    // class A' has no member named 'f2' , OK
//! a.f3(a, 3); // class A' has no member named 'f3' , OK
//! a.f4(4, a); //'class A' has no member named 'f4' , OK
//! a.f5(5);    //'class A' has no member named 'f5' , OK

  f1(1);        // OK, global function
  f2(a);        // OK, global function
  f3(a, 3);     // OK, global function
  f4(4, a);     // OK, global function
//! f5(5);      //  was not declared in this scope; ??? 

//! a.operator%(1 , a); // 'class A' has no member named 'operator%' , OK
  1%a;         // OK, global function

  return 0;
}
  • Вопрос задан
  • 400 просмотров
Решения вопроса 1
@MarkusD Куратор тега C++
все время мелю чепуху :)
Согласно стандарту, дружественная функция может быть определена по месту объявления только для нелокального класса и только если имя функции не является квалифицированным.
Первое ограничение нас мало интересует, а второе - является довольно существенным.

Первично объявленная дружественной функция, согласно стандарту, становится участником пространства имен того типа, для которого объявлена дружественной.
Получаем такую ситуацию. У нас есть пространство имен, пусть даже глобальное, в котором определяется тип. Во время определения этого типа делается первичное объявление функции как дружественной этому типу. В результате этого функция, как бы, объявляется принадлежащей пространству имен в котором определяется тип, но не совсем.

Функция f5 не просто первично объявлена, она и определена по месту объявления дружественности. Ее имя является однозначно неквалифицированным в следствии своего определения.

Еще из стандарта мы можем узнать о том, что первично объявленное дружественное имя не может быть найдено средствами стандартного поиска квалифицированных или неквалифицированных имен.
Именно этот результат мы и можем наблюдать в вопросе. Имя f5 не находится.

Все потому что т.н. имена скрытых друзей могут быть найдены только средствами ADL.
Если коротко, Argument-Dependent Lookup опирается на типы аргументов при вызове функции, пространства их имен и пространства имен, в которых эти типы объявлены.
ADL не выполняет поиск в пространствах имен в отношении фундаментальных типов. Поэтому код f5(5); буквально обречен на ошибку трансляции.

Первично объявленная дружественной функция будет исключительно ADL-доступной до момента своего объявления или определения в том же пространстве имен, в котором определен тип друга. В этом случае функция станет доступна для поиска квалифицированных или неквалифицированных имен.
Для f5 доступен только способ с повторным объявлением, т.к. она уже определена.
Да только в этом случае f5 окончательно потеряет всякий смысл быть дружественной для A и станет просто сильно спрятанной и сбивающей с толку глобальной функцией.
Суть дружественности в раскрытии доступа, которым f5 относительно A не пользуется, т.к. среди ее параметров нет ни одного с типом A.

В результате.
Чтобы ADL нашел функцию f5, среди ее параметров обязан быть параметр и с типом A.
Чтобы UNL или QNL смогли найти функцию f5, ее надо дополнительно объявить за пределами типа A в его пространстве имен.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
wataru
@wataru Куратор тега C++
Разработчик на С++, экс-олимпиадник.
Вы пытаетесь вызвать f1 и т.д., как будто это члены класса A. Но подумайте, какой смысл объявлять члены класса friend? Зачем вообще дружественность нужна? Чтобы иметь доступ к приватным членам класса, но ведь члены класса и так имеют туда доступ. Это бесмыссленная операция.

Но так уж стандарт сделан, что вместо ошибки компиляции, определенные через friend функции становятся глобальными.

По идее вы в классе должны их только объявлять (сигнатура без тела), а определение (тело функции) должно быть уже снаружи где-то, как вы с f1 сделали.

Почему f5 не находится? Попробуйте передать туда, допустим a. Компилятор, внезапно, найдет функцию и ругнется, что не может преобразовать A к int. Но если передать туда int, то компилятор функцию потеряет.

Тут хитрая магия описанная в стандарте - всякие нагромождения правил посика имен.
В стандарте сказано, что вот при определении friend функций:
A name first declared in a friend declaration within a class or class template X becomes a member of the innermost enclosing namespace of X, but is not visible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided - see namespaces for details.


Я не знаю, как это внятно объяснить, вот просто не работает и все. Стандарт такой. С остальными функциями срабатывает, потому что там в аргументах есть A, поэтому объявления в классе каким-то образом попадают в область поиска имен.

Просто не надо определять функции-друзей в классе. Это не имеет смысла. Или делайте там статичные функции, или определяйте всю функцию во внешнем пространстве имен а в классе указывайте, что она имеет доступ ко всему классу.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы