Информация об изменениях

Сообщение Re[12]: Concept-Based Polymorphism от 18.07.2020 5:46

Изменено 18.07.2020 7:59 so5team

Re[12]: Concept-Based Polymorphism
Здравствуйте, B0FEE664, Вы писали:

S>>>>А std::function с лямбдами в C++ можно рассматривать как сахар для классов вроде ProviderCaller/CallerForProviderOne/CallerForProviderTwo.

BFE>>>Есть принципиальная разница состоящая в том, что ConsumerOne и ConsumerTwo ничего не знают о ProviderCaller.
S>>Зато они вынуждены знать про std::function<void()>. Тоже самое, что и знать про ProviderCaller с operator()
BFE>Нет, не тоже самое. От std::function<void()> никто не наследуется.

Это не важно. Важно то, что Consumer-ы должны иметь какую-то сущность, которую в данном случае логично было бы назвать "слот". Через "слот" Consumer молучает возможность вызвать Provider-а.

Поскольку в C++ каждая сущность должна быть представлена экземпляром какого-то типа, то для представления "слота" нужно выбрать подходящий тип. В вашем примере это std::function<void()>, в моем примере -- абстрактный класс ProviderCaller.

Но суть от этого не меняется, т.к. Consumer-ы хранят в себе "слоты" через которые происходит вызов.

BFE>Взгляните на архитектуру примеров. У них разная архитектура.


Не вижу разной архитектуры. Вижу различия в реализациях, но архитектура как раз практически эквивалентная.

BFE>>>В случае использования Signals-slots классы можно переименовывать и менять независимо друг от друга, а в приведённом примере изменения названия ProviderCaller, скажем на ProxyCaller приведёт к редактированию аж 4-х классов!

S>>Тоже самое произойдет если вы решите заменить std::function на какой-нибудь my_fast_delegate.
BFE>Нет конечно. В ConsumerOne можно заменить std::function на какой-нибудь my_fast_delegate, а в ConsumerTwo оставить как есть.

Тогда что мешает вам в ConsumerOne заменть ProviderCaller на ProxyCaller, а в ConsumerTwo оставить ProviderCaller?

Еще раз подчеркну: суть в том, что идею "слота" вам в программе нужно через что-то выразить. Либо через std::function, либо через my_fast_delegate, либо через ProviderCaller. И после того, как вы выразили "слот" тем или иным образом, замена реализации "слота" потребует модификации мест использования слота. Вне зависимости от того, как именно "слот" реализован.

S>>Ну и, как уже было сказано, в случае с лямбдами у вас есть сахар со стороны компилятора, который автоматически делает классы, который мне пришлось выписывать руками.

BFE>Не согласен. В std::function есть стиратель типа, а у вас его нет. Вот если бы вы предложили заменить std::function указателем на функцию, тогда можно было бы согласится.

Не вижу какой-либо существенной роли "стирателя типа" здесь.

S>>Но с точки зрения типа применяющегося здесь полиморфизма разницы нет.

BFE>Т.е. вы хотите поговорить об определениях.

Не поговорить, а договорится.

BFE>Ну так какой это полиморфизм?


Я не понял, этот вопрос относится к цитате из русской Wiki или?

Вообще, за определениями лучше заглядывать в англоязычную Wiki, ее больше читают и вероятность, что оттуда вымарают разную ересь повыше: https://en.wikipedia.org/wiki/Polymorphism_(computer_science)

BFE>>>то как вы будете это реализовывать в CallerForProviderOne ?

S>>Не понятно, при чем здесь CallerForProviderOne?
BFE>Ну а как ? Заведёте ещё CallerForProviderThree? И так для каждого нового метода?

Послушайте, мне не понятен сам вопрос. Но вы вместо того, чтобы его уточнить, задаете следующий вопрос, что озадачивает меня еще больше.
Подозреваю, что вас интересует ситуация следующего рода:
class ActualProviderTwo
{
   public:
     void Function_f(int n);
     void Function_g(double d);
};

И вы хотите, чтобы в ConsumerOne можно было вызывать как ActualProviderTwo::Function_f, так и ActualProviderTwo::Function_g.

Если вопрос именно в этом, то да, будет еще один класс CallerForProviderTwoG.

Только тут нужно подчеркнуть важную вещь: я специально привожу примеры в классическом ООП-стиле без использования обобщенного программирования. Поэтому получается, что под каждого Provider-а нужно вручную делать собственного наследника от ProviderCaller. Делаю это для того, чтобы показать, что точно такой же результат, как у вас, достигается исключительно посредством динамического полиморфизма.

Откуда можно сделать вывод, что то, что вы называете "параметрическим полиморфизмом" есть не что иное, как динамический полиморфизм, вид в профиль.

BFE>Ну раз вам так интересно, что подразумеваю я (быть может не правильно), то вот:

BFE>В случае использования шаблонных параметров — статический полиморфизм.
BFE>В случае использования виртуальных функций — динамический полиморфизм.
BFE>В случае использования type eraser — параметрический полиморфизм.

Понятно. Но такое определение параметрического полиморфизма кардинально противоречит тому, к чему я привык. И, судя по определениям из Wiki, привык не только я.
Re[12]: Concept-Based Polymorphism
Здравствуйте, B0FEE664, Вы писали:

S>>>>А std::function с лямбдами в C++ можно рассматривать как сахар для классов вроде ProviderCaller/CallerForProviderOne/CallerForProviderTwo.

BFE>>>Есть принципиальная разница состоящая в том, что ConsumerOne и ConsumerTwo ничего не знают о ProviderCaller.
S>>Зато они вынуждены знать про std::function<void()>. Тоже самое, что и знать про ProviderCaller с operator()
BFE>Нет, не тоже самое. От std::function<void()> никто не наследуется.

Это не важно. Важно то, что Consumer-ы должны иметь какую-то сущность, которую в данном случае логично было бы назвать "слот". Через "слот" Consumer получает возможность вызвать Provider-а.

Поскольку в C++ каждая сущность должна быть представлена экземпляром какого-то типа, то для представления "слота" нужно выбрать подходящий тип. В вашем примере это std::function<void()>, в моем примере -- абстрактный класс ProviderCaller.

Но суть от этого не меняется, т.к. Consumer-ы хранят в себе "слоты" через которые происходит вызов.

BFE>Взгляните на архитектуру примеров. У них разная архитектура.


Не вижу разной архитектуры. Вижу различия в реализациях, но архитектура как раз практически эквивалентная.

BFE>>>В случае использования Signals-slots классы можно переименовывать и менять независимо друг от друга, а в приведённом примере изменения названия ProviderCaller, скажем на ProxyCaller приведёт к редактированию аж 4-х классов!

S>>Тоже самое произойдет если вы решите заменить std::function на какой-нибудь my_fast_delegate.
BFE>Нет конечно. В ConsumerOne можно заменить std::function на какой-нибудь my_fast_delegate, а в ConsumerTwo оставить как есть.

Тогда что мешает вам в ConsumerOne заменть ProviderCaller на ProxyCaller, а в ConsumerTwo оставить ProviderCaller?

Еще раз подчеркну: суть в том, что идею "слота" вам в программе нужно через что-то выразить. Либо через std::function, либо через my_fast_delegate, либо через ProviderCaller. И после того, как вы выразили "слот" тем или иным образом, замена реализации "слота" потребует модификации мест использования слота. Вне зависимости от того, как именно "слот" реализован.

S>>Ну и, как уже было сказано, в случае с лямбдами у вас есть сахар со стороны компилятора, который автоматически делает классы, который мне пришлось выписывать руками.

BFE>Не согласен. В std::function есть стиратель типа, а у вас его нет. Вот если бы вы предложили заменить std::function указателем на функцию, тогда можно было бы согласится.

Не вижу какой-либо существенной роли "стирателя типа" здесь.

S>>Но с точки зрения типа применяющегося здесь полиморфизма разницы нет.

BFE>Т.е. вы хотите поговорить об определениях.

Не поговорить, а договорится.

BFE>Ну так какой это полиморфизм?


Я не понял, этот вопрос относится к цитате из русской Wiki или?

Вообще, за определениями лучше заглядывать в англоязычную Wiki, ее больше читают и вероятность, что оттуда вымарают разную ересь повыше: https://en.wikipedia.org/wiki/Polymorphism_(computer_science)

BFE>>>то как вы будете это реализовывать в CallerForProviderOne ?

S>>Не понятно, при чем здесь CallerForProviderOne?
BFE>Ну а как ? Заведёте ещё CallerForProviderThree? И так для каждого нового метода?

Послушайте, мне не понятен сам вопрос. Но вы вместо того, чтобы его уточнить, задаете следующий вопрос, что озадачивает меня еще больше.
Подозреваю, что вас интересует ситуация следующего рода:
class ActualProviderTwo
{
   public:
     void Function_f(int n);
     void Function_g(double d);
};

И вы хотите, чтобы в ConsumerOne можно было вызывать как ActualProviderTwo::Function_f, так и ActualProviderTwo::Function_g.

Если вопрос именно в этом, то да, будет еще один класс CallerForProviderTwoG.

Только тут нужно подчеркнуть важную вещь: я специально привожу примеры в классическом ООП-стиле без использования обобщенного программирования. Поэтому получается, что под каждого Provider-а нужно вручную делать собственного наследника от ProviderCaller. Делаю это для того, чтобы показать, что точно такой же результат, как у вас, достигается исключительно посредством динамического полиморфизма.

Откуда можно сделать вывод, что то, что вы называете "параметрическим полиморфизмом" есть не что иное, как динамический полиморфизм, вид в профиль.

BFE>Ну раз вам так интересно, что подразумеваю я (быть может не правильно), то вот:

BFE>В случае использования шаблонных параметров — статический полиморфизм.
BFE>В случае использования виртуальных функций — динамический полиморфизм.
BFE>В случае использования type eraser — параметрический полиморфизм.

Понятно. Но такое определение параметрического полиморфизма кардинально противоречит тому, к чему я привык. И, судя по определениям из Wiki, привык не только я.