Разница между указателем на автономную и дружественную функцию - PullRequest
20 голосов
/ 03 августа 2020

Я не понимаю, почему следующее не компилируется (например, в g cc 9.10 или MS VS C ++ 2019):

class X {
  public:
    friend bool operator==(int, X const &);
};

int main() {
  2 == X();  // ok...
  static_cast<bool (*)(int, X const &)>(&operator==);  // Error: 'operator==' not defined
  return 0;
}

, но следующий код компилируется без проблем:

class X {
  public:
};
bool operator==(int, X const &);

int main() {
  2 == X();  // ok...
  static_cast<bool (*)(int, X const &)>(&operator==);  // OK!
  return 0;
}

Я ожидаю, что дружественная функция (operator ==) X будет вести себя как отдельная функция (operator ==). Что мне не хватает? Спасибо

Ответы [ 3 ]

23 голосов
/ 03 августа 2020

Что мне не хватает?

Встроенное объявление друга не делает функцию доступной для обычного поиска имени.

Обратите особое внимание на ошибку. Он не говорит, что функция неправильного типа, он просто не может найти ничего с именем operator==. Это сделано намеренно.

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

class X {
  public:
    friend bool operator==(int, X const &) { /* ... */ }
};

bool operator==(int, X const &);
5 голосов
/ 03 августа 2020

Из стандарта 11.9.3.7 :

Такая функция неявно является встроенной функцией ([dcl.inline]), если она присоединена к глобальному модуль. Дружественная функция, определенная в классе, находится в (лексической) области видимости класса, в котором она определена. Функция друга, определенная вне класса, не ([basi c .lookup.unqual]).

Из стандартного namespace.memdef / 3 : (Спасибо @StoryTeller)

Если объявление друга в нелокальном классе сначала объявляет класс, функцию, шаблон класса или шаблон функции, друг является членом самого внутреннего включающего пространства имен. Объявление друга не делает имя видимым для неквалифицированного поиска ([basi c .lookup.unqual]) или квалифицированного поиска ([basi c .lookup.qual]). [ Примечание: имя друга будет видно в его пространстве имен, если соответствующее объявление предоставлено в области пространства имен (до или после определения класса, предоставляющего дружбу). - конец примечания] Если вызывается дружественная функция или шаблон функции, ее имя может быть найдено с помощью поиска по именам, который рассматривает функции из пространств имен и классов, связанных с типами аргументов функции ([basi c .lookup. argdep]) . Если имя в объявлении друга не является ни квалифицированным, ни идентификатором шаблона, а объявление является функцией или спецификатором детализированного типа, поиск для определения того, был ли объект объявлен ранее, не должен учитывать какие-либо области за пределами самого внутреннего включающего пространства имен. .

Следующее не работает, потому что функция не отображается в текущей области.

  static_cast<bool (*)(int, X const &)>(&operator==);  // Error: 'operator==' not defined
3 голосов
/ 03 августа 2020

Разница в том, что в первом фрагменте вы объявляете оператор только в пределах X, но не за его пределами. operator==(int,X const &) недоступен из main. Если вы исправите это и объявите это также снаружи, вы получите только предупреждения:

class X {
  public:
    friend bool operator==(int, X const &);
};

bool operator==(int,X const&);    // <--

int main() {
  2 == X();  // ok...
  static_cast<bool (*)(int, X const &)>(&operator==);  // Error: 'operator==' not defined
  return 0;
}

Обратите внимание, что в обоих случаях вам нужно определение для фактического вызова оператора.

Для иллюстрации учтите, что с

struct foo {
    friend void bar() { 
         std::cout << "this is a inline definition of friend funtion";
    }
};

Единственный способ получить доступ к bar извне foo - это добавить объявление вне foo:

void bar();
...