Re[13]: Concept-Based Polymorphism
От: B0FEE664  
Дата: 18.07.20 13:25
Оценка:
Здравствуйте, so5team, Вы писали:

BFE>>>>Есть принципиальная разница состоящая в том, что ConsumerOne и ConsumerTwo ничего не знают о ProviderCaller.

S>>>Зато они вынуждены знать про std::function<void()>. Тоже самое, что и знать про ProviderCaller с operator()
BFE>>Нет, не тоже самое. От std::function<void()> никто не наследуется.
S>Это не важно. Важно то, что Consumer-ы должны иметь какую-то сущность, которую в данном случае логично было бы назвать "слот". Через "слот" Consumer получает возможность вызвать Provider-а.

Структура слота также важна, так как от устройства слота зависит сложность его использования.

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

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

Смотря что называть сутью. Если сутью называть наличие полиморфизма, то он есть в обоих случаях, а если сутью называть "степень связности классов", т.е. эта степень различна.

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

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

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

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

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


Конечно потребуется изменение в точке связывания, но суть совершенно не в этом, а в том, что в моём случае добавление ещё одного слота не ведёт к написанию ещё одного класса, как это сделано у вас. Классы CallerForProviderOne и CallerForProviderTwo не нужны. Их не должно существовать. Это лишние сущности не выражающие ничего существенного.

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

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

А она есть и важна. Если у нас есть стиратель типа, то один и тот же код мы вызываем для неважно каких типов, так как информацию о типе мы стёрли и не используем. Посмотрите на пример для языка C:

В языке Си функции не являются объектами первого класса, но возможно определение указателей на функции, что позволяет строить функции высших порядков[96]. Также доступен небезопасный параметрический полиморфизм за счёт явной передачи необходимых свойств типа через бестиповое подмножество языка, представленное нетипизированным указателем void*[97] (называемым в сообществе языка «обобщённым указателем» (англ. generic pointer). Назначение и удаление информации о типе при приведении типа к void* и обратно не является ad-hoc-полиморфизмом, так как не меняет представление указателя, однако, его записывают явно для обхода системы типов компилятора

В C++ тоже можно стирать тип и тоже можно это делать различными способами. Стирание типа приводит к настоящему параметрическому полиморфизму, так как мы получаем возможность выполнять один и тот же код для любых типов.

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

S>Я не понял, этот вопрос относится к цитате из русской Wiki или?
Какой тип полиморфизма в приведённом мною примере?

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

Упоминание слова ересь тут к чему?
Ну возьмём английское определение:

Parametric polymorphism: when one or more types are not specified by name but by abstract symbols that can represent any type.

std::function<void()> — это абстрактный символ, который может представлять любой тип. Согласны?

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

Ещё раз повторюсь, конечно результат тот же — и там и там полиморфизм. Дело не в результате, а в том, с помощью каких средств он достигается.

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

Если под динамическим полиморфизмом понимать такой полиморфизм, который может изменятся во время выполнения программы, то — да. И тот и другой являются динамическими. Но полиморфизм основанный на Signal-Slot ещё и параметрический.

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

BFE>>В случае использования шаблонных параметров — статический полиморфизм.
BFE>>В случае использования виртуальных функций — динамический полиморфизм.
BFE>>В случае использования type eraser — параметрический полиморфизм.
S>Понятно. Но такое определение параметрического полиморфизма кардинально противоречит тому, к чему я привык. И, судя по определениям из Wiki, привык не только я.
В чём отличие?
И каждый день — без права на ошибку...
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.