Почему нельзя предварительно объявить дружественную функцию-член?
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 24.09.23 17:30
Оценка:
Язык допускает предварительное объявление (forward declaration) дружественной (friend) обычной (non-member) функции, но почему-то не допускает такого для функции-члена, класс которой не определен полностью (только предварительно объявлен — "class C;").

Откуда растет это ограничение? Вроде как предварительная декларация функции-члена другого класса обеспечивает компилятор всей информацией об этой функции (по сути, ему должно быть достаточно сигнатуры). Чего здесь не хватает компилятору?
Re: Почему нельзя предварительно объявить дружественную функцию-член?
От: watchmaker  
Дата: 24.09.23 18:23
Оценка: 10 (1)
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Вроде как предварительная декларация функции-члена другого класса обеспечивает компилятор всей информацией об этой функции (по сути, ему должно быть достаточно сигнатуры).


Кажется, это совсем не так.

Декларация обычной функции позволяет компилятору генерировать код её вызова. Собственно, на этом разделная компиляция с header-файлами и построена.

Но для функции-члена у тебя может быть разный код вызова для виртуального метода и для невиртуального. То есть для классов
struct S {
    void foo(int);
};

struct B {
      virtual void foo(int);
};


код вызова S::foo или B::foo во многих ABI будет совсем разный и только одной декларации void B::foo(int); для компилятора недостаточно.

Конечно, ты можешь потребовать дополнительных ограничений на то, что нужно при forward-деклараци обязательно указывать ещё и виртуальность функции.
Но и этого недостаточно, так как код виртуального вызова ещё и зависит от порядка методов в классе.
Например, для классов

struct U {
    virtual void bar(int);
    virtual void foo(int);
};

struct V {
    virtual void foo(int);
    virtual void bar(int);
};


вызов метода foo делается разным кодом, потому что в ABI (например, в itanium-cxx-abi) есть зависимость от порядка описания членов при построении виртуальной таблицы.
Но если у компилятора есть только декларация virtual void U::bar(int);, то у него нет шансов узнать по какому смещению будет расположен в virtual-table указатель на метод:
U* u = ...;
u->foo(1);


А ещё есть и виртуально наследование, которое тоже может вносить свои особенности (так как нужно корректировать при вызове указатель this).
Вот и получается, что для вызова функции в общем случае компилятору нужно видеть очень много других деталей про класс. Как минимум все остальные функции в нём и во всех его базах, и всю схему наследований.
Re: Почему нельзя предварительно объявить дружественную функ
От: rg45 СССР  
Дата: 24.09.23 18:38
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Язык допускает предварительное объявление (forward declaration) дружественной (friend) обычной (non-member) функции...


И сходу неправильно. Объявление функции дружественной не является предварительным объявлением функции:

http://coliru.stacked-crooked.com/a/92590ae0842c85d9

struct A
{
    friend void foo();
};

int main()
{
    foo(); // error: 'foo' was not declared in this scope
}


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

http://coliru.stacked-crooked.com/a/5da8e14983baa2f4

namespace ns
{

void foo();
    
} // namespace ns

struct A
{
    friend void ns::foo(); // Ok
    friend void ns::bar(); // error: 'void ns::bar()' should have been declared inside 'ns'
};


Я думаю, что если ты потрудишься переформулировать свой вопрос без использования ошибочных утверждений, то ответ найдется автоматически.
--
Отредактировано 24.09.2023 18:48 rg45 . Предыдущая версия .
Re[2]: Почему нельзя предварительно объявить дружественную функцию-член?
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 24.09.23 19:23
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Декларация обычной функции позволяет компилятору генерировать код её вызова.


Спасибо за развернутые рассуждения, но при чем они здесь? Если я в классе A указываю дружественную функцию из класса B, это сообщает компилятору лишь то, что эта функция будет иметь право доступа к закрытым членам класса A, и ничего больше. Если бы компилятор выдавал ошибку в точке вызова этой функции, пока ее класс полностью не определен, я бы не удивился. Но он выдает ошибку именно в точке объявления friend, и это странно.
Re[2]: Почему нельзя предварительно объявить дружественную функ
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 24.09.23 19:29
Оценка:
Здравствуйте, rg45, Вы писали:

R>И сходу неправильно.


Точнее, Вы с ходу стали искать, к чему прицепиться, и таки нашли.

R>Объявление функции дружественной не является предварительным объявлением функции


Само собой. Где я утверждал обратное?

R>Язык допускает объявление дружественной функции без ее предварительного объявления только для функций из того же пространства имен, что и сам класс.


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

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


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

R>если ты потрудишься переформулировать свой вопрос без использования ошибочных утверждений


Вы твердо уверены в том, что ошибочные утверждения делаю именно я?
Re[3]: Почему нельзя предварительно объявить дружественную ф
От: rg45 СССР  
Дата: 24.09.23 19:57
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Мне нужно указать в качестве дружественной функцию, которая является членом класса, а не просто пространства имен.


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

http://coliru.stacked-crooked.com/a/5da8e14983baa2f4

namespace ns
{

void foo();
    
} // namespace ns

struct A
{
    friend void ns::foo(); // Ok
    friend void ns::bar(); // error: 'void ns::bar()' should have been declared inside 'ns'
};


Исключением являются только дружественные функции из того же пространства имен, что и сам класс. Нужно объяснять, почему подобное исключение невозможно сделать для дружественных функций-членов, или сам напряжешь извилину?
--
Отредактировано 24.09.2023 20:25 rg45 . Предыдущая версия . Еще …
Отредактировано 24.09.2023 20:14 rg45 . Предыдущая версия .
Отредактировано 24.09.2023 20:13 rg45 . Предыдущая версия .
Отредактировано 24.09.2023 20:04 rg45 . Предыдущая версия .
Отредактировано 24.09.2023 20:02 rg45 . Предыдущая версия .
Отредактировано 24.09.2023 20:02 rg45 . Предыдущая версия .
Отредактировано 24.09.2023 19:58 rg45 . Предыдущая версия .
Re[3]: Почему нельзя предварительно объявить дружественную ф
От: rg45 СССР  
Дата: 24.09.23 19:59
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

R>>Объявление функции дружественной не является предварительным объявлением функции


ЕМ>Само собой. Где я утверждал обратное?


Вот здесь
Автор: Евгений Музыченко
Дата: 24.09.23
ты утверждал, что

Язык допускает предварительное объявление (forward declaration) дружественной (friend) обычной (non-member) функции


Зачем отпираться, когда все ходы записаны?
--
Отредактировано 24.09.2023 20:00 rg45 . Предыдущая версия .
Re[4]: Почему нельзя предварительно объявить дружественную ф
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 24.09.23 20:27
Оценка: :)
Здравствуйте, rg45, Вы писали:

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


А чем это обосновано?

R>Нужно объяснять, почему подобное исключение невозможно сделать для дружественных функций-членов


Понятно, что при условии вышеприведенного правила это невозможно. А для чего необходимо само правило?
Re[5]: Почему нельзя предварительно объявить дружественную ф
От: rg45 СССР  
Дата: 24.09.23 20:29
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>А чем это обосновано?

ЕМ>Понятно, что при условии вышеприведенного правила это невозможно. А для чего необходимо само правило?

Вот видишь, ты уже пытаешься переформулировать вопрос, это уже хорошо. Ты не торопись, подумай, глядишь, вопрос-то и отвалится, как дурацкий.

P.S. Что именно тебя удивляет — что функции из одних пространств имен нельзя объявлять в других?
--
Отредактировано 24.09.2023 20:32 rg45 . Предыдущая версия .
Re[4]: Почему нельзя предварительно объявить дружественную ф
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 24.09.23 20:36
Оценка:
Здравствуйте, rg45, Вы писали:

R>Вот здесь
Автор: Евгений Музыченко
Дата: 24.09.23
ты утверждал, что


R>

R>Язык допускает предварительное объявление (forward declaration) дружественной (friend) обычной (non-member) функции


Я утверждал именно то, что в обычной практике (без разделения на пространства имен) он это допускает.

Осталось понять, почему не допускает это для функции-члена. Если объявить заранее пространство имен нельзя (тоже, кстати, искусственное ограничение), то класс-то заранее объявить можно.
Re[5]: Почему нельзя предварительно объявить дружественную ф
От: rg45 СССР  
Дата: 24.09.23 20:38
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Я утверждал именно то, что в обычной практике (без разделения на пространства имен) он это допускает.

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

Да у тебя на каждом шагу "исскусственные ограничения". Тебе самому не надоело жить в выдуманном мире? Подучил бы уже язык, с которым работаешь, хотя бы на уровне "объявления-определения" да "аз-буки".
--
Отредактировано 24.09.2023 20:40 rg45 . Предыдущая версия . Еще …
Отредактировано 24.09.2023 20:39 rg45 . Предыдущая версия .
Re[5]: Почему нельзя предварительно объявить дружественную ф
От: rg45 СССР  
Дата: 24.09.23 20:51
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Я утверждал именно то, что в обычной практике (без разделения на пространства имен) он это допускает.

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

И кстати, стандарт языка не вводит термина "forward declaration" как такового. Есть "declaration" и есть "definition", а слово forward встречается исключительно в качестве второстепенного члена предложения в примерах и пояснениях к ним.
--
Re[5]: Почему нельзя предварительно объявить дружественную ф
От: rg45 СССР  
Дата: 25.09.23 00:01
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>...в обычной практике (без разделения на пространства имен)...


--
Отредактировано 25.09.2023 5:07 rg45 . Предыдущая версия . Еще …
Отредактировано 25.09.2023 0:08 rg45 . Предыдущая версия .
Re: Почему нельзя предварительно объявить дружественную функ
От: Sm0ke Россия ksi
Дата: 25.09.23 12:03
Оценка: 12 (1)
Здравствуйте, Евгений Музыченко

Вы хотите указать метод того класса, который ещё не определён, как дружественный?
Если этот класс, который с дружесвенным методом, принимать как параметр шаблона, то это может получится

#include <iostream>

template <typename T>
struct restricted
{
  friend void T::go(const restricted &) const;
private:
  int value{55};
};

struct agent
{
  template <typename T>
  void go(const T & r) const
  {
    std::cout << r.value << '\n';
  }
};

int main()
{
  restricted<agent> v;
  agent a;
  a.go(v);
  return 0;
}

Output:

55


Если имя метода, который вы хотите указать как friend, сделать зависимым от шаблонного параметра, то
разрешение имён откладывается до мемента инстанцирования.

Про это есть видео на ютуб канале Константина Владимирова
  Магистерский курс C++ (МФТИ, 2022-2023). Лекция 4. Разрешение имён в шаблонах и One Definition Rule.
https://www.youtube.com/watch?v=8mCSDR1NpoU
Отредактировано 25.09.2023 12:32 Sm0ke . Предыдущая версия . Еще …
Отредактировано 25.09.2023 12:12 Sm0ke . Предыдущая версия .
Отредактировано 25.09.2023 12:11 Sm0ke . Предыдущая версия .
Re[2]: variadic
От: Sm0ke Россия ksi
Дата: 25.09.23 12:27
Оценка:
Здравствуйте, Sm0ke, Вы писали:

ради эксперимента
Попробовал этот трюк провернуть с вариадиками, но что-то пошло не так: https://godbolt.org/z/9rdGfzn76

#include <type_traits>
#include <iostream>

  template <typename Type, typename ... U>
  concept c_any_of = (std::is_same_v<Type, U> || ...);

template <typename ... T>
struct restricted
{
  template <c_any_of<T ...> U>
  friend void U::go(const restricted &) const;
private:
  int value{55};
};

struct agent_A
{
  template <typename T>
  void go(const T & r) const
  {
    std::cout << "A: " << r.value << '\n'; // fail
  }
};

struct agent_B
{
  template <typename T>
  void go(const T & r) const
  {
    std::cout << "B: " << r.value << '\n';
  }
};

int main()
{
  restricted<agent_A, agent_B> v;
  agent_A a;
  agent_B b;
  a.go(v);
  b.go(v);
  return 0;
}
Отредактировано 25.09.2023 12:28 Sm0ke . Предыдущая версия .
Re[6]: Почему нельзя предварительно объявить дружественную ф
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.09.23 13:06
Оценка: :))
Здравствуйте, rg45, Вы писали:

R>Тебе самому не надоело жить в выдуманном мире?


Это еще большой вопрос, у кого мир более выдуманный. Я-то языком пользуюсь сугубо прагматично, для получения кода, надежно работающего в пределах заданных допущений, и не претендующего ни на полное соответствие Стандарту, ни на "каноничность". Так уж сделали язык, что для этого его регулярно приходится гнуть об колено. А Вы вместе с теми, кто меня этим попрекает, ратуете за рафинированную корректность и чуть ли не математическую чистоту. Кто из нас ближе к реальности?

R>Подучил бы уже язык, с которым работаешь, хотя бы на уровне "объявления-определения" да "аз-буки".


На уровне "для работы" я его понимаю гораздо лучше среднего заурядного плюсовика, который тупо кодит алгоритмы по заданию. Изучать же на уровне, достаточном для сдачи экзамена или преподавания, не вижу ни малейшего смысла, ибо результатов моей работы это ни разу не улучшит.
Re[6]: Почему нельзя предварительно объявить дружественную ф
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.09.23 13:14
Оценка:
Здравствуйте, rg45, Вы писали:

R>стандарт языка не вводит термина "forward declaration" как такового. Есть "declaration" и есть "definition", а слово forward встречается исключительно в качестве второстепенного члена предложения в примерах и пояснениях к ним.


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

А коли уж Вы взялись буквоедствовать, то еще раз подчеркну, что пространства имен (namespace) в данном случае не используются, так что оба класса вместе со своими методами находятся в одном и том же пространстве имен. Функция, которую я хочу объявить, как дружественную, находится в другой области видимости (scope).

В обоих случаях не вижу никаких принципиальных препятствий для компилятора принимать имя функции с квалификатором области видимости ее класса.
Re[2]: Почему нельзя предварительно объявить дружественную функ
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.09.23 13:19
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Вы хотите указать метод того класса, который ещё не определён, как дружественный?


Да.

S>Если этот класс, который с дружесвенным методом, принимать как параметр шаблона


Если нет менее кривого решения, я лучше объявлю дружественным весь класс, хотя пытаюсь этого избежать.
Re[7]: Почему нельзя предварительно объявить дружественную ф
От: so5team https://stiffstream.com
Дата: 25.09.23 13:29
Оценка: +2
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Я-то языком пользуюсь сугубо прагматично, для получения кода, надежно работающего в пределах заданных допущений, и не претендующего ни на полное соответствие Стандарту, ни на "каноничность".


Если ваш код не соответствует стандарту, то он запросто может перестать работать при обновлении компилятора.

Да, да, тут уже все знают, что это не ваш случай и что у вас компиляторов этих всего 1-2 и обчелся, да к тому же все они от Microsoft.
Но доблести, увы, в этом нет. И у людей, которые набили шишек при переносе кода между vc++, gcc и clang (хотя бы этими тремя), подобная бравада одобрения не вызывает, мягко говоря.
Re[7]: Почему нельзя предварительно объявить дружественную ф
От: rg45 СССР  
Дата: 25.09.23 13:54
Оценка: :)
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Это еще большой вопрос, у кого мир более выдуманный. Я-то языком пользуюсь сугубо прагматично, для получения кода, надежно работающего в пределах заданных допущений, и не претендующего ни на полное соответствие Стандарту, ни на "каноничность". Так уж сделали язык, что для этого его регулярно приходится гнуть об колено. А Вы вместе с теми, кто меня этим попрекает, ратуете за рафинированную корректность и чуть ли не математическую чистоту. Кто из нас ближе к реальности?


Да какое там, нафиг, полное соответствие, когда ты азов не знаешь. Невежество на невежестве, так еще и гордится этим. Языком он пользуется. Как шимпанзе смартфоном.
--
Отредактировано 25.09.2023 14:11 rg45 . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.