Здравствуйте, so5team, Вы писали:
S>Здравствуйте, a7d3, Вы писали:
A>>Долго спорили за виды полиморфизма в ООП применимо к С++ (в RAII и исключения в конструкторе) A>>А вот пример https://habr.com/ru/company/samsung/blog/505850/ A>>того, что давно используется без претензий на отдельную парадигму, выходящую за пределы ООП.
S>А вот интересно, здесь вы тоже ООП отыщите?
Опять демонстрация подхода — кидать ссылки, но при этом не смотреть таковые?
Сразу под заголовком видно, что её уже давно определили в ООП-хаб
И, да, это комично в 2016-м рассказывать о том, что используется чуть ли не со времён BCC 3.1 (Borland C++ от 1992-го года).
Ясно, что человеку хочется пиарить свою особо ценную библиотечку на том же хабре и для этого типа «прокачивает», но смотрится это по-идиотски.
Надо подкинуть ему идею рассказать такой же статейкой про The Curiously Recurring Template Pattern (CRTP) — сто процентов не промахнётся
Здравствуйте, a7d3, Вы писали:
S>>Вопрос был не про новизну идеи. А про то, найдете ли вы там ООП или нет.
A>Таки усё ишо зудит с предыдущего диалога? A>Охота и здесь поговорить «за ООП в целом» — не опять, а снова?
Сказал персонаж, которому было не лень создать новую тему для "утверждения" своей точки зрения.
Тем не менее, на вопрос сможете ответить или в очередной раз надуете щеки и заявите, что вам не пристало общаться с быдл колхозником?
Давайте посмотрим на код, в чем здесь проблема? Мы видим, что в этой иерархии методы запуска прохода различны в зависимости от того, над чем они должны выполняться (над функцией — runOnFunction, модулем — runOnModule, циклом — runOnLoop и тд). В свою очередь, это делает невозможным обрабатывать коллекцию проходов, которые работают с разными IR сущностями, единым способом (собственно применять полиморфизм). Казалось бы, очевидно, как сделать правильно: нужен виртуальный метод run, который будет переопределяться в наследниках. Но тут же возникает проблема: у методов run в классах-наследниках будет разная сигнатура, поскольку передается параметр всегда своего типа — функция, модуль и так далее.
Вот очевидно же, что раз "у методов run в классах-наследниках будет разная сигнатура", то её и надо поддержать. Т.е. сделать run шаблонным виртуальным методом. Но автор делает странный вывод:
Значит, придется делать фиктивный базовый класс для Module, Function и т.д.
Здравствуйте, B0FEE664, Вы писали:
BFE>>>В отличии от статического и динамического полиморфизма, полиморфизм основанный на Signals and slots является параметрическим полиморфизм S>>А чем статический полиморфизм отличается от параметрического?
BFE>Тем, что при статическом полиморфизме вызывающий знает тип вызываемого объекта, при параметрическом — нет.
Боюсь, здесь две проблемы с моим восприятием вашего определения:
1. Там, где "знает тип", там полиморфизм, если и есть, то разве что ad-hoc. Который в C++ реализуется посредством перегрузки функций под разные типы параметров (вроде to_string(int) и to_string(double)).
2. Параметрическим полиморфизмом принято называть то, что в C++ называется шаблонами. Т.е. когда алгоритмы и/или структуры данных оперируют не конкретными типами (аргументов, атрибутов), а именами этих типов. Т.е. вот это уже параметрический полиморфизм:
template< typename T > T min(T a, T b);
Соответственно, в C++ параметрический полиморфизм используется для реализации альтернативы динамическому полиморфизму. Т.е. вместо:
// Классический ООП с динамическим полиморфизмом.class Provider {
public:
virtual void f() = 0;
...
};
class ConsumerOne {
Provider & provider_;
...
void g() { provider_.f(); }
};
class ConsumerTwo {
Provider & provider_;
...
void g() { provider_.f(); }
};
class ActualProviderOne : public Provider {...};
class ActualProviderTwo : public Provider {...};
ActualProviderOne provider_one;
ConsumerOne consumer_one{provider_one};
consumer_one.g();
ActualProviderTwo provider_two;
ConsumerTwo consumer_two{provider_two};
consumer_two.g();
можно использовать другой сценарий:
// Альтернатива на базе параметрического полиморфизма.template< typename ProviderT >
class ConsumerOne {
ProviderT & provider_;
...
void g() { provider_.f(); }
};
template< typename ProviderT >
class ConsumerTwo {
ProviderT & provider_;
...
void g() { provider_.f(); }
};
class ActualProviderOne {...};
class ActualProviderTwo {...};
ActualProviderOne provider_one;
ConsumerOne<ActualProviderOne> consumer_one{provider_one};
consumer_one.g();
ActualProviderTwo provider_two;
ConsumerTwo<ActualProviderTwo> consumer_two{provider_two};
consumer_two.g();
И этот альтернативный подход в C++ называют "статическим полиморфизмом". По крайней мере я в таком ключе этот термин использовал.
А вот что вы подразумеваете под статическим и параметрическим полиморфизмом не очень понятно
BFE>Значит, придется делать фиктивный базовый класс для Module, Function и т.д.
BFE>Вот как так-то?
Там в статье то ли в погоне за простотой, то ли в погоне за статьей вообще потерялся смысл. Ведь в итоге напихали функции в какой-то адаптер и никакой такой унификации то по сути нет. Получилось из простого и понятного дерева — дерево вывернутое через шаблоны с совершенно наркоманскими суффиксами.
Ну т.е., если у предлагаемого подхода и есть польза, то что бы полностью осознать преимущества, видимо прийдется вникать в реальный код.
Здравствуйте, so5team, Вы писали:
S>Ну и, как уже было сказано, в случае с лямбдами у вас есть сахар со стороны компилятора, который автоматически делает классы, который мне пришлось выписывать руками.
S>Но с точки зрения типа применяющегося здесь полиморфизма разницы нет.
Одно дело разница в механике реализации со стороны компилятора — механизмах взаимодействия объектов (экземпляров классов).
Совсем другое — это разница в плане характера взаимоотношений сущностей порождаемых разработчиком (программистом) в исходном коде и которые приводят к кодогенерации во время компиляции.
Последнее относится к тому пространству, где есть понятие связность и хрупкость исходного кода какого-то программного решения. В аспекте управления жизненным циклом — внесением изменений по части корректировки или доработки реализованного функционала.
Разговор, который начинался для выяснения того, что вы понимаете под "параметрическим полиморфизимом" начинает перерастать в спор о том, чьи определения полиморфизма должны считаться правильными. По ряду причин у меня нет ни возможности, ни желания развивать этот спор. Поэтому предлагаю закончить, т.к. я выяснил то, что мне было нужно.
Но, т.к. моя точка зрения могла остаться недопонятой, то тезисно по основным пунктам.
От классификации зависит понимание предмета собеседниками. Если у каждого из нас собственное представление о "параметрическом полиморфизме", то когда вы говорите "у меня в коде используется параметрический полиморфизм", то я ваши слова буду воспринимать совершенно не так. И наоборот.
Динамический полиморфизм в С и в C++ приниципально один и тот же. Различия лишь в том, что в C поддержка динамического полиморфизма полностью лежит на программисте, тогда как C++ предоставляет языковые средства для нее.
Типов полиморфизма, с которыми мне доводилось иметь дело, три:
Ad-hoc полиморфизм. В C++ реализуется как перегрузка:
bool equal(const char * a, const char * b);
bool equal(const std::string & a, const std::string & b);
bool equal(int a, int b);
bool equal(float a, float b);
Параметрический полиморфизм. В C++ реализуется в виде шаблонов:
template<typename T> bool equal(const T & a, const T & b) {...}
template<typename T> T min(T a, T b) {...}
Динамический или subtyping полиморфизм. В C++ реализуется из коробки через наследование с виртуальными методами. Либо же может быть реализован вручную на указателях на функции или std::function. Но традиционно понимается как наследование с виртуальными методами.
При этом в C++ термин "статический полиморфизм" является синонимом "параметрическому полиморфизму". Просто так сложилось. Поскольку перегрузка функций в C++ была с самого начала, еще до того, как термин "полиморфизм" вообще пришел в массы и в мире C++ перегрузку функций как полиморфизм рассматривают далеко не все C++ программисты.
Не трудно заметить, что и Ad-hoc, и параметрический полиморфизмы разрешаются в compile-time. Поэтому они оба могут называться "статическим полиморфизмом". Из-за чего сам по себе термин "статический полиморфизм" (в отрыве от C++ где у него исторически один смысл) не точный, за ним могут скрываться принципиально разные вещи.
То, что в случае с параметрическим полиморфизмом компилятор может сгенерировать разный код для разных типов параметров с точки зрения разновидностей полиморфизма роли не играет. Т.е. разработчик имеет дело только с исходным кодом, который оказывается один вне зависимости от типа параметров.
Сигнатура функции определяет тип функции. Поэтому, если у вас есть void(*)(), то уже без разницы, указатель на какие именно функции вы будете туда помещать. Поскольку вот это:
void say_hello() {...}
void format_disk() {...}
using pfn = void (*)();
void consume(pfn fn);
pfn a = say_hello;
pfn b = format_disk;
consume(a);
consume(b);
ничем не отличается вот от этого:
void consume(int v);
int a = 1234/45 + 56;
int b = 56 - 345/7 + 12*9;
consume(a);
consume(b);
Поскольку оба фрагмента завязаны на конкретные типы: в первом случае на void(), во втором на int. Но типы зафиксированы, а то, что у экземпляров этих типов разные значения, то это обычное дело.
Параметрический полиморфизм бы здесь появился если бы consume научился бы брать разные типы. В случае с функциями consume должен бы был воспринимать не только void(), но и, скажем, int() или void(char*).
Детали реализации механизма слотов не важны по той же самой причине, по которой в математике не важно считаете ли вы количество яблок, количество палочек на парте или количество черточек, нарисованных на доске. Важно лишь то, что 2+2=4, вне зависимости от того, количество чего обозначено числами "2". Разница на первых этапах обучения существует только у детей, для которых подсчитывание палочек на парте и черточек на доске может выглядеть принципиально разными занятиями. Но по мере обучения развивается абстрактное мышление и эта разница исчезает.
Поэтому принципальное значение с точки зрения архитектуры является применение абстракции "слот". Детали же реализации "слота" на архитектуру влияния не оказывают, т.к. эти детали рассматриваются несколькими уровнями ниже.
Здравствуйте, a7d3, Вы писали:
S>>А вот интересно, здесь вы тоже ООП отыщите?
A>Сразу под заголовком видно, что её уже давно определили в ООП-хаб
Т.е. для вас критерий ООП/неООП -- это название хаба на Хабре?
A>И, да, это комично в 2016-м рассказывать о том, что используется чуть ли не со времён BCC 3.1 (Borland C++ от 1992-го года).
Вопрос был не про новизну идеи. А про то, найдете ли вы там ООП или нет.
Здравствуйте, PM, Вы писали:
PM>Насколько я понял, это просто список функций, вызываемых последовательно, одна за другой. Зачем-то его обзвали Manager
PM>Так можно написать на любом языке, где функции являются полноценными гражданами, хоть на Haskell, хоть на JavaScript. Ну и на C++ само собой.
std::function & lambda появились в плюсах «недавно» и в результате увлечения функторами, вместо указателей на «свободные функции».
А обозвали manager, скорее всего, из-за возможности добавлять-удалять проходы. Вряд ли из-за древовидной структуры произвольной вложенности, которые у них лепятся из этих манагеров-прохода.
Давайте посмотрим на код, в чем здесь проблема? Мы видим, что в этой иерархии методы запуска прохода различны в зависимости от того, над чем они должны выполняться (над функцией — runOnFunction, модулем — runOnModule, циклом — runOnLoop и тд). В свою очередь, это делает невозможным обрабатывать коллекцию проходов, которые работают с разными IR сущностями, единым способом (собственно применять полиморфизм). Казалось бы, очевидно, как сделать правильно: нужен виртуальный метод run, который будет переопределяться в наследниках. Но тут же возникает проблема: у методов run в классах-наследниках будет разная сигнатура, поскольку передается параметр всегда своего типа — функция, модуль и так далее. Значит, придется делать фиктивный базовый класс для Module, Function и т.д., передавать в run указатель на этот класс, а внутри метода делать down-cast, в зависимости от того, что за объект находится по данному указателю. И начинается что-то странное: при появлении новой нижестоящей сущности мы вынуждены теперь переписывать каждый раз вышестоящий код, что противоречит всем принципам проектирования.
Вместо того, чтобы добавить честные шаблонные виртуальные функции в язык (это же писатели компилятора или кто?) занимаются поиском обходных путей...
Здравствуйте, B0FEE664, Вы писали:
BFE> BFE>Вместо того, чтобы добавить честные шаблонные виртуальные функции в язык (это же писатели компилятора или кто?) занимаются поиском обходных путей...
Это Роман Русяев
Expert Engineer
AI Compiler Team
Samsung R&D Institute, Russia
Занимается разработкой компилятора нейронных сетей для NPU (Neural Processing Unit) в российском центре Samsung R&D. Скомпилированные нейронные сети поставляются в составе флагманских телефонов Samsung, таких как Galaxy Note 10.
Имеется опыт разработки оптимизирующего компилятора под VLIW-архитектуру «Эльбрус» и архитектуру SPARC с языков C, C++, Fortran. В процессе разработки оптимизирующего компилятора, занимался реализацией:
платформозависимых и платформонезависимых оптимизаций;
оптимальной обработки исключений С++ (zero-cost exception handling) в части middle-end компилятора;
санитайзеров: AddressSanitizer, MemorySanitizer, LeakSanitizer, включая портирование библиотеки compiler-rt на платформу «Эльбрус».
Здравствуйте, so5team, Вы писали:
S>Сказал персонаж, которому было не лень создать новую тему для "утверждения" своей точки зрения.
S>Тем не менее, на вопрос сможете ответить или в очередной раз надуете щеки и заявите, что вам не пристало общаться с быдл колхозником?
Если будет говорить про вот этот самый вид полиморфизма, то почему бы и не подискутировать?
А коли он опять в своём маня мирке — приписывает другим людям свои выдумки — и хочет трендеть на другие темы, то ему дорога лишь к собутыльникам.
Здравствуйте, so5team, Вы писали:
S>Здравствуйте, a7d3, Вы писали:
A>>Если будет говорить про вот этот самый вид полиморфизма, то почему бы и не подискутировать?
S>Да блин, вы на простой вопрос ответить в состоянии? Есть код, видите ли в этом коде ООП или нет?
Ещё раз — учимся самодисциплине, прекращаем оффтоп.
Здравствуйте, a7d3, Вы писали:
BFE>> BFE>>Вместо того, чтобы добавить честные шаблонные виртуальные функции в язык (это же писатели компилятора или кто?) занимаются поиском обходных путей...
A>Это Роман Русяев
К чему нам эти персональные данные?
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, a7d3, Вы писали:
BFE>>> BFE>>>Вместо того, чтобы добавить честные шаблонные виртуальные функции в язык (это же писатели компилятора или кто?) занимаются поиском обходных путей...
A>>Это Роман Русяев BFE>К чему нам эти персональные данные?
Здравствуйте, a7d3, Вы писали:
PM>>Насколько я понял, это просто список функций, вызываемых последовательно, одна за другой. Зачем-то его обзвали Manager PM>>Так можно написать на любом языке, где функции являются полноценными гражданами, хоть на Haskell, хоть на JavaScript. Ну и на C++ само собой. A>std::function & lambda появились в плюсах «недавно» и в результате увлечения функторами, вместо указателей на «свободные функции».
И что? Технология Signals and slots придумана была ещё до Qt (хотя Википедия врёт об обратном). В отличии от статического и динамического полиморфизма, полиморфизм основанный на Signals and slots является параметрическим полиморфизм, а введение lambda и вариадик параметров делает такой полиморфизм строготипизированным (при желании). При этом, в отличии от других, полиморфизм основанный на Signals and slots позволяет связывать вызовы объектов, которые ничего не знают друг о друге и полностью независимы. Информация о типах нужна только в точке их связывания.
В частности, вместо:
template <typename IR, typename... ArgTs> class PassManager {
std::vector<std::unique_ptr<detail::PassConcept<IR, ArgTs...>>> Passes;
};
можно использовать:
template <typename IR, typename... ArgTs> class PassManager {
std::vector<std::function<R, Args...>> Passes;
};
в качестве элемента в Passes можно добавить функтор хранящий указатель на произольный объект PassT (например лямбду, где в captures захватывается объект для вызова) и тогда ни PassModel, ни PassConcept будут не нужны.
Единственная проблема с таким подходом — скорость выполнения (хотя, кто её измерял?). При этом, если вместо обобщённых std::function и лямбды написать свои обёртки, то скорость тожно не будет хуже, чем вызов функции через указатель на неё.
Здравствуйте, B0FEE664, Вы писали:
BFE>В отличии от статического и динамического полиморфизма, полиморфизм основанный на Signals and slots является параметрическим полиморфизм
А чем статический полиморфизм отличается от параметрического?
Здравствуйте, so5team, Вы писали:
BFE>>В отличии от статического и динамического полиморфизма, полиморфизм основанный на Signals and slots является параметрическим полиморфизм S>А чем статический полиморфизм отличается от параметрического?
Тем, что при статическом полиморфизме вызывающий знает тип вызываемого объекта, при параметрическом — нет.
Здравствуйте, so5team, Вы писали: S>Боюсь, здесь две проблемы с моим восприятием вашего определения:
Об определениях спорить не буду, просто покажу ещё одну альтернативу.
Скрытый текст
S>1. Там, где "знает тип", там полиморфизм, если и есть, то разве что ad-hoc. Который в C++ реализуется посредством перегрузки функций под разные типы параметров (вроде to_string(int) и to_string(double)).
S>2. Параметрическим полиморфизмом принято называть то, что в C++ называется шаблонами. Т.е. когда алгоритмы и/или структуры данных оперируют не конкретными типами (аргументов, атрибутов), а именами этих типов. Т.е. вот это уже параметрический полиморфизм: S>
template< typename T > T min(T a, T b);
S>Соответственно, в C++ параметрический полиморфизм используется для реализации альтернативы динамическому полиморфизму. Т.е. вместо:
Ниже ConsumerOne и ConsumerTwo знают о Provider S>
S>И этот альтернативный подход в C++ называют "статическим полиморфизмом". По крайней мере я в таком ключе этот термин использовал.
Ниже ConsumerOne и ConsumerTwo ничего не знают ни о ActualProviderOne, ни о ActualProviderTwo (все 4 класса могут лежать в разных файлах и не include'ить друг друга):
BFE>Ниже ConsumerOne и ConsumerTwo ничего не знают ни о ActualProviderOne, ни о ActualProviderTwo (все 4 класса могут лежать в разных файлах и не include'ить друг друга): BFE>
Да это вроде обычный динамический полиморфизм, только реализуется он у вас через идиомы из функционального программирования. Но, по сути, это всего лишь что-то вроде:
Здравствуйте, B0FEE664, Вы писали:
BFE>Т.е. сделать run шаблонным виртуальным методом.
Тема пять лет как набивающая оскомину? шаблонный виртуальный метод
S>Да это вроде обычный динамический полиморфизм, только реализуется он у вас через идиомы из функционального программирования. Но, по сути, это всего лишь что-то вроде: S>А std::function с лямбдами в C++ можно рассматривать как сахар для классов вроде ProviderCaller/CallerForProviderOne/CallerForProviderTwo.
Есть принципиальная разница состоящая в том, что ConsumerOne и ConsumerTwo ничего не знают о ProviderCaller. Это не просто сахар, это принципиально другой вид отношений между классами. (По сути такой код ближе к тому определению ООП в котором объекты обмениваются сообщениями.) В случае использования Signals-slots классы можно переименовывать и менять независимо друг от друга, а в приведённом примере изменения названия ProviderCaller, скажем на ProxyCaller приведёт к редактированию аж 4-х классов!
Есть и другое отличие.
Если вам нужно будет добавить провайдера для второй функции (с такой же сигнатурой):
Здравствуйте, a7d3, Вы писали:
BFE>>Т.е. сделать run шаблонным виртуальным методом. A>Тема пять лет как набивающая оскомину? шаблонный виртуальный метод
Да. Хотя теме больше пяти лет.
Здравствуйте, B0FEE664, Вы писали:
S>>А std::function с лямбдами в C++ можно рассматривать как сахар для классов вроде ProviderCaller/CallerForProviderOne/CallerForProviderTwo.
BFE>Есть принципиальная разница состоящая в том, что ConsumerOne и ConsumerTwo ничего не знают о ProviderCaller.
Зато они вынуждены знать про std::function<void()>. Тоже самое, что и знать про ProviderCaller с operator()
BFE>Это не просто сахар, это принципиально другой вид отношений между классами.
Пока не видно почему.
BFE>В случае использования Signals-slots классы можно переименовывать и менять независимо друг от друга, а в приведённом примере изменения названия ProviderCaller, скажем на ProxyCaller приведёт к редактированию аж 4-х классов!
Тоже самое произойдет если вы решите заменить std::function на какой-нибудь my_fast_delegate.
Ну и, как уже было сказано, в случае с лямбдами у вас есть сахар со стороны компилятора, который автоматически делает классы, который мне пришлось выписывать руками.
Но с точки зрения типа применяющегося здесь полиморфизма разницы нет.
BFE>Есть и другое отличие. BFE>Если вам нужно будет добавить провайдера для второй функции (с такой же сигнатурой): BFE>
Здравствуйте, a7d3, Вы писали:
PM>>Насколько я понял, это просто список функций, вызываемых последовательно, одна за другой. Зачем-то его обзвали Manager
PM>>Так можно написать на любом языке, где функции являются полноценными гражданами, хоть на Haskell, хоть на JavaScript. Ну и на C++ само собой.
A>std::function & lambda появились в плюсах «недавно» и в результате увлечения функторами, вместо указателей на «свободные функции».
boost::function уже лет 20, а лямбда так это вообще синтаксический оверхедсахар над
Ну и boost.lambda для любителей странного существовала примерно с тех же времен.
Кто хотел — использовал такое, кто не знал — использовал классический ОО подход с интерфейсами и динамическим полиморфизмом.
Не понятно, какое отношение имеет код из стартового сообщения к "Concept-Based Polymorphism". Если я правильно помню, то последнее это попытка перенести идею type classes из Haskell.
Один из таких экспериментов на С++ недавно было https://github.com/ldionne/dyno
Но как-то тоже неочевидна практическая цель применения этого.
A>А обозвали manager, скорее всего, из-за возможности добавлять-удалять проходы. Вряд ли из-за древовидной структуры произвольной вложенности, которые у них лепятся из этих манагеров-прохода.
Я неясно высказался. Бессмысленный суффикс в имени класса, типа Manager, Controller, Container, Coordiantor, Holder и т.п. обычно является признаком того, что автор, возможно, сам не до конца понимает зачем такой класс нужен.
Здравствуйте, night beast, Вы писали:
BFE>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно. NB>как без них?
Здравствуйте, B0FEE664, Вы писали:
BFE>>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно. NB>>как без них?
BFE>Как в приведённом выше примере.
Здравствуйте, so5team, Вы писали:
S>>>А std::function с лямбдами в C++ можно рассматривать как сахар для классов вроде ProviderCaller/CallerForProviderOne/CallerForProviderTwo. BFE>>Есть принципиальная разница состоящая в том, что ConsumerOne и ConsumerTwo ничего не знают о ProviderCaller. S>Зато они вынуждены знать про std::function<void()>. Тоже самое, что и знать про ProviderCaller с operator()
Нет, не тоже самое. От std::function<void()> никто не наследуется.
BFE>>Это не просто сахар, это принципиально другой вид отношений между классами. S>Пока не видно почему.
Взгляните на архитектуру примеров. У них разная архитектура.
BFE>>В случае использования Signals-slots классы можно переименовывать и менять независимо друг от друга, а в приведённом примере изменения названия ProviderCaller, скажем на ProxyCaller приведёт к редактированию аж 4-х классов! S>Тоже самое произойдет если вы решите заменить std::function на какой-нибудь my_fast_delegate.
Нет конечно. В ConsumerOne можно заменить std::function на какой-нибудь my_fast_delegate, а в ConsumerTwo оставить как есть.
S>Ну и, как уже было сказано, в случае с лямбдами у вас есть сахар со стороны компилятора, который автоматически делает классы, который мне пришлось выписывать руками.
Не согласен. В std::function есть стиратель типа, а у вас его нет. Вот если бы вы предложили заменить std::function указателем на функцию, тогда можно было бы согласится.
S>Но с точки зрения типа применяющегося здесь полиморфизма разницы нет.
Т.е. вы хотите поговорить об определениях.
Существует несколько разновидностей полиморфизма. Две принципиально различных из них были описаны Кристофером Стрэчи[en] в 1967 году: это параметрический полиморфизм[⇨] и ad-hoc-полиморфизм[⇨], причём первая является истинной формой, а вторая — мнимой[1][4]; прочие формы являются их подвидами или сочетаниями.
Ну так какой это полиморфизм?
BFE>>Есть и другое отличие. BFE>>Если вам нужно будет добавить провайдера для второй функции (с такой же сигнатурой): BFE>>
BFE>>то как вы будете это реализовывать в CallerForProviderOne ? S>Не понятно, при чем здесь CallerForProviderOne?
Ну а как ? Заведёте ещё CallerForProviderThree? И так для каждого нового метода?
BFE>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно. S>Напомню, что речь шла о различиях между статическим и параметрическим полиморфизмом. Точнее в выяснении того, что вы под этим понимаете.
Ну раз вам так интересно, что подразумеваю я (быть может не правильно), то вот:
В случае использования шаблонных параметров — статический полиморфизм.
В случае использования виртуальных функций — динамический полиморфизм.
В случае использования type eraser — параметрический полиморфизм.
Здравствуйте, night beast, Вы писали:
BFE>>>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно. NB>>>как без них? BFE>>Как в приведённом выше примере.
Какое отношение внутренняя реализация std::function имеет отношение к обсуждаемой теме?
А, впрочем, вот, для ConsumerThree без использования std::function:
Здравствуйте, 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? И так для каждого нового метода?
Послушайте, мне не понятен сам вопрос. Но вы вместо того, чтобы его уточнить, задаете следующий вопрос, что озадачивает меня еще больше.
Подозреваю, что вас интересует ситуация следующего рода:
И вы хотите, чтобы в ConsumerOne можно было вызывать как ActualProviderTwo::Function_f, так и ActualProviderTwo::Function_g.
Если вопрос именно в этом, то да, будет еще один класс CallerForProviderTwoG.
Только тут нужно подчеркнуть важную вещь: я специально привожу примеры в классическом ООП-стиле без использования обобщенного программирования. Поэтому получается, что под каждого Provider-а нужно вручную делать собственного наследника от ProviderCaller. Делаю это для того, чтобы показать, что точно такой же результат, как у вас, достигается исключительно посредством динамического полиморфизма.
Откуда можно сделать вывод, что то, что вы называете "параметрическим полиморфизмом" есть не что иное, как динамический полиморфизм, вид в профиль.
BFE>Ну раз вам так интересно, что подразумеваю я (быть может не правильно), то вот: BFE>В случае использования шаблонных параметров — статический полиморфизм. BFE>В случае использования виртуальных функций — динамический полиморфизм. BFE>В случае использования type eraser — параметрический полиморфизм.
Понятно. Но такое определение параметрического полиморфизма кардинально противоречит тому, к чему я привык. И, судя по определениям из Wiki, привык не только я.
Здравствуйте, 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, привык не только я.
В чём отличие?
Здравствуйте, B0FEE664, Вы писали:
BFE>>>>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно. NB>>>>как без них? BFE>>>Как в приведённом выше примере.
NB>>виртуальная функция внутри std::function
BFE>Какое отношение внутренняя реализация std::function имеет отношение к обсуждаемой теме?
если обсуждается стирание типов, то мне непонятно, как оно будет работать без виртуальных функций или их ручной замены в виде указателей на функцию.
то есть то что у тебя наружу не торчит какой-то интерфейс, не значит что его совсем нет.
Здравствуйте, B0FEE664, Вы писали:
BFE>Структура слота также важна, так как от устройства слота зависит сложность его использования.
При выяснении того, какой тип полиморфизма вы подразумевали, устройство слота принципиального значения не имеет.
BFE>Смотря что называть сутью. Если сутью называть наличие полиморфизма, то он есть в обоих случаях, а если сутью называть "степень связности классов", т.е. эта степень различна.
Сутью в этом обсуждении я называю тип полиморфизма, который в вашей формулировке обозначен как "параметрический".
BFE>>>Взгляните на архитектуру примеров. У них разная архитектура. S>>Не вижу разной архитектуры. Вижу различия в реализациях, но архитектура как раз практически эквивалентная. BFE>Ну как она может быть одинакова, когда в вашем случае есть наследование, а у меня его нет?
Между Consumer-ами и Provider-ами никакого наследования нет. И прямой связи между ними нет как в моем, так и в вашем примере. Поэтому архитектура, в которую входят Consumer-ы и Provider-ы принципиально не различается.
А вот архитектура самого механизма слотов -- да, разная.
Но. Я специально показал вам реализацию слотов на базе наследования. Чтобы более четко показать, что здесь имеет место быть обычный динамический полиморфизм.
А так-то слоты можно было и без наследования сделать. Вы сами где-то в этой теме вариант с указателями на функцию уже это показали.
BFE>Вы путаете, я говорю про то, что изменение названия, переименование ProviderCaller в ProxyCaller, приведёт к изменениям, а не про замену одного другим.
Ну так если комитет примет решения переименовать std::function в std::functor, например, то и у вас будет та же самая проблема.
BFE>Конечно потребуется изменение в точке связывания, но суть совершенно не в этом, а в том, что в моём случае добавление ещё одного слота не ведёт к написанию ещё одного класса, как это сделано у вас. Классы CallerForProviderOne и CallerForProviderTwo не нужны. Их не должно существовать. Это лишние сущности не выражающие ничего существенного.
См. выше про причины того, почему эти классы вообще появились.
BFE>А она есть и важна.
К сожалению, ваша аргументация не выглядит убедительно.
BFE>В C++ тоже можно стирать тип и тоже можно это делать различными способами. Стирание типа приводит к настоящему параметрическому полиморфизму, так как мы получаем возможность выполнять один и тот же код для любых типов.
У меня есть впечатление, что вы неправильно понимаете "параметрический полиморфизм". А так же не правильно интерпретируете "возможность выполнять один и тот же код для любых типов".
BFE>Какой тип полиморфизма в приведённом мною примере?
Динамический. Я об этом уже вроде бы говорил.
BFE>std::function<void()> — это абстрактный символ, который может представлять любой тип. Согласны?
Нет. Это конкретный тип, который обозначает функцию без аргументов, возвращающую void. И ваше решение завязано конкретно на этот тип.
Если вы смените его на тип void(int) или void(int, string), то работать оно уже не будет.
BFE>Ещё раз повторюсь, конечно результат тот же — и там и там полиморфизм. Дело не в результате, а в том, с помощью каких средств он достигается.
Нет, еще раз повторюсь. Дело не в средствах, а в типе полиморфизма. Это динамический полиморфизм. А реализуется он разными (на первый взгляд) средствами.
BFE>>>Ну раз вам так интересно, что подразумеваю я (быть может не правильно), то вот: BFE>>>В случае использования шаблонных параметров — статический полиморфизм. BFE>>>В случае использования виртуальных функций — динамический полиморфизм. BFE>>>В случае использования type eraser — параметрический полиморфизм. S>>Понятно. Но такое определение параметрического полиморфизма кардинально противоречит тому, к чему я привык. И, судя по определениям из Wiki, привык не только я. BFE>В чём отличие?
Вот пример параметрического полиморфизма:
template< typename T > T min(T a, T b) { return a < b ? a : b; }
Это код, который будет работать с разными типами T: int, float, complex, string, vector<char> и т.д.
А вот то, что вы считаете параметрическим полиморфизмом:
Вы так считаете, поскольку wrap_action получает разные лямбды.
Но дело в том, что с точки зрения типов обе эти лямбды имеет одинаковый тип (точнее, у них одинаковый тип после оборачивания в std::function или после приведения к void(*)()). Поэтому wrap_action заточен только под один тип. И с другим типом параметра он работать не сможет. Поэтому это не параметрический полиморфизм. ИМХО.
Здравствуйте, night beast, Вы писали:
BFE>>Какое отношение внутренняя реализация std::function имеет отношение к обсуждаемой теме?
NB>если обсуждается стирание типов, то мне непонятно, как оно будет работать без виртуальных функций или их ручной замены в виде указателей на функцию. NB>то есть то что у тебя наружу не торчит какой-то интерфейс, не значит что его совсем нет.
Я нигде и не утверждал, что Signals-slots работает на какой-то неведомой науке магии. Я всего лишь говорю, что это третий (причём довольно старый, если не самый старый) способ организации полиморфизма в C++. Меня удивляет, что его мало кто использует и вообще осознаёт.
Здравствуйте, so5team, Вы писали:
BFE>>Структура слота также важна, так как от устройства слота зависит сложность его использования. S>При выяснении того, какой тип полиморфизма вы подразумевали, устройство слота принципиального значения не имеет.
Это почему?
BFE>>Смотря что называть сутью. Если сутью называть наличие полиморфизма, то он есть в обоих случаях, а если сутью называть "степень связности классов", т.е. эта степень различна. S>Сутью в этом обсуждении я называю тип полиморфизма, который в вашей формулировке обозначен как "параметрический".
Что изменится от классификации? Почему это важно?
S>Между Consumer-ами и Provider-ами никакого наследования нет. И прямой связи между ними нет как в моем, так и в вашем примере. Поэтому архитектура, в которую входят Consumer-ы и Provider-ы принципиально не различается.
Что вы называете принципиальным различием? Мне вспоминается притча Дейкстры.
BFE>>Вы путаете, я говорю про то, что изменение названия, переименование ProviderCaller в ProxyCaller, приведёт к изменениям, а не про замену одного другим. S>Ну так если комитет примет решения переименовать std::function в std::functor, например, то и у вас будет та же самая проблема.
Нет, мне придётся изменить всего два класса, а не 4.
BFE>>А она есть и важна. S>К сожалению, ваша аргументация не выглядит убедительно.
Мне тоже жаль. Впрочем, это всегда сложно — объяснять очевидные вещи. Скажите, а вы вообще видите различие для полиморфизма в языке C и в C++?
BFE>>В C++ тоже можно стирать тип и тоже можно это делать различными способами. Стирание типа приводит к настоящему параметрическому полиморфизму, так как мы получаем возможность выполнять один и тот же код для любых типов. S>У меня есть впечатление, что вы неправильно понимаете "параметрический полиморфизм". А так же не правильно интерпретируете "возможность выполнять один и тот же код для любых типов".
Ну объясните в чем ошибка.
BFE>>Какой тип полиморфизма в приведённом мною примере? S>Динамический. Я об этом уже вроде бы говорил.
Согласно википедии русской у вас есть выбор: параметрический полиморфизм или ad-hoc-полиморфизм.
Согласно википедии английской у вас есть выбор: Ad hoc polymorphism / Parametric polymorphism / Subtyping polymorphism
Согласно википедии итальяноской у вас есть выбор: polimorfismo per inclusione / polimorfismo parametrico
Согласно нимецкой википедии выбор интересный:
Согласно португальской википедии так же как у немцев.
На хинди есть такой выбор: तदर्थ पोलिमोर्फ़िज्म или पैरामीट्रिक पोलिमोर्फ़िज्म, т.е. такойче как в российской.
В испанской википедии выбор такой: Polimorfismo dinámico (o polimorfismo paramétrico) / Polimorfismo estático (o polimorfismo ad hoc)
И только у китайцев классификация начинается с: 动态多态(dynamic polymorphism) и 静态多态(static polymorphism).
Т.е. если вы учились у китайцев или испанцев, то ваш ответ "Динамический" можно понять, но ведь вы вроде как за английскую версию, а там написано, что
Polymorphism can be distinguished by when the implementation is selected: statically (at compile time) or dynamically (at run time, typically via a virtual function).
Т.е. это классификация по воплощению, а не по типу. А мы же тип обсуждаем или нет?
BFE>>std::function<void()> — это абстрактный символ, который может представлять любой тип. Согласны? S>Нет. Это конкретный тип, который обозначает функцию без аргументов, возвращающую void. И ваше решение завязано конкретно на этот тип. S>Если вы смените его на тип void(int) или void(int, string), то работать оно уже не будет.
Вы же видели, что я заменил на struct CallBack и всё работает. А то, что сигнатура нужна та же — то что в том удивительного?
BFE>>Ещё раз повторюсь, конечно результат тот же — и там и там полиморфизм. Дело не в результате, а в том, с помощью каких средств он достигается. S>Нет, еще раз повторюсь. Дело не в средствах, а в типе полиморфизма. Это динамический полиморфизм.
Как указано выше "динамический полиморфизм" — это не тип полиморфизма, по крайней мере не во всех культурах.
S>А реализуется он разными (на первый взгляд) средствами.
Ну так реализация намного интереснее, чем абстрактные академизмы.
BFE>>В чём отличие? S>Вот пример параметрического полиморфизма: S>
template< typename T > T min(T a, T b) { return a < b ? a : b; }
S>Это код, который будет работать с разными типами T: int, float, complex, string, vector<char> и т.д.
Согласно определению у вас должен быть один и тот же код для разнвх типов, а фактически для каждого типа T будет создана своя функция. Так что это не ad-hoc полиморфизм. Или вы макросы тоже называете полиморфными функциями?
S>А вот то, что вы считаете параметрическим полиморфизмом: S>
S>Вы так считаете, поскольку wrap_action получает разные лямбды.
Да. И?
S>Но дело в том, что с точки зрения типов обе эти лямбды имеет одинаковый тип (точнее, у них одинаковый тип после оборачивания в std::function или после приведения к void(*)()). Поэтому wrap_action заточен только под один тип. И с другим типом параметра он работать не сможет. Поэтому это не параметрический полиморфизм. ИМХО.
wrap_action расчин на одну сигнатуру, а вот типы разные.
Здравствуйте, so5team, Вы писали: S>Разговор, который начинался для выяснения того, что вы понимаете под "параметрическим полиморфизимом" начинает перерастать в спор о том, чьи определения полиморфизма должны считаться правильными. По ряду причин у меня нет ни возможности, ни желания развивать этот спор. Поэтому предлагаю закончить, т.к. я выяснил то, что мне было нужно.
Ну, хорошо, что вы выяснили то, что вам было нужно, я правда так и не понял, что именно. Замечу ещё, что с самого начала я пытался дважды уклониться от выяснения определений, так как знал, что это бессмысленное занятие. S>От классификации зависит понимание предмета собеседниками. Если у каждого из нас собственное представление о "параметрическом полиморфизме", то когда вы говорите "у меня в коде используется параметрический полиморфизм", то я ваши слова буду воспринимать совершенно не так. И наоборот.
Именно поэтому я привёл код, который всё проясняет и работает не зависимо от того, что и как называть. S>Динамический полиморфизм в С и в C++ приниципально один и тот же. Различия лишь в том, что в C поддержка динамического полиморфизма полностью лежит на программисте, тогда как C++ предоставляет языковые средства для нее. S>Типов полиморфизма, с которыми мне доводилось иметь дело, три: S>Ad-hoc полиморфизм. В C++ реализуется как перегрузка: S>
S>bool equal(const char * a, const char * b);
S>bool equal(const std::string & a, const std::string & b);
S>bool equal(int a, int b);
S>bool equal(float a, float b);
S>
S>Параметрический полиморфизм. В C++ реализуется в виде шаблонов: S>
S>template<typename T> bool equal(const T & a, const T & b) {...}
S>template<typename T> T min(T a, T b) {...}
S>
S>Динамический или subtyping полиморфизм. В C++ реализуется из коробки через наследование с виртуальными методами. Либо же может быть реализован вручную на указателях на функции или std::function. Но традиционно понимается как наследование с виртуальными методами.
Есть ещё один вид, самый первый, который используется, например, в qsort. qsort — общий код для неважно каких типов сортируемых массивов.
S>При этом в C++ термин "статический полиморфизм" является синонимом "параметрическому полиморфизму". Просто так сложилось. Поскольку перегрузка функций в C++ была с самого начала, еще до того, как термин "полиморфизм" вообще пришел в массы и в мире C++ перегрузку функций как полиморфизм рассматривают далеко не все C++ программисты.
Я знаю про общее правило: если что-то в классификации обусловлено историческими причинами, aka "так сложилось", то значит в классификации есть проблемы. Такой эффект можно наблюдать в любой области: от классификации плодов растений (фрукт/овощ/ягода ...), до классификации планет в астрономии.
А вообще интересно: вот если в C++ добавить шаблонный виртуальный метод (что не сложно сделать вручную), то какой тип будет у такого полиморфизма в вашей классификации?
S>Детали реализации механизма слотов не важны по той же самой причине, по которой в математике не важно считаете ли вы количество яблок, количество палочек на парте или количество черточек, нарисованных на доске. Важно лишь то, что 2+2=4, вне зависимости от того, количество чего обозначено числами "2". Разница на первых этапах обучения существует только у детей, для которых подсчитывание палочек на парте и черточек на доске может выглядеть принципиально разными занятиями. Но по мере обучения развивается абстрактное мышление и эта разница исчезает.
В том-то и дело, что для для программистов, в отличии от математиков, разница в конкретном и абстрактном есть. Возьмём сумму 2 + 2 + 2 + 2, математику не важно сколько операций сложения в этом выражении, а программист должен уметь получать результат такого выражения за (максимум) две операции сложения. S>Поэтому принципальное значение с точки зрения архитектуры является применение абстракции "слот". Детали же реализации "слота" на архитектуру влияния не оказывают, т.к. эти детали рассматриваются несколькими уровнями ниже.
Ну так я вам об этом же и писал:
BFE>std::function<void()> — это абстрактный символ, который может представлять любой тип. Согласны?
Нет. Это конкретный тип, который обозначает функцию без аргументов, возвращающую void. И ваше решение завязано конкретно на этот тип.
std::function<void()> — это слот и его реализация не важна. Так что я опять не понимаю, о чём вы толкуете.
Впрочем для меня это не важно, важно другое: использование слотов вместо виртуальных функций меняет подходы к задачам и их решение. В частности, задачу исходного сообщения (так, которая на хабре), технология signal-slot решит довольно просто. Важно это, а не то как в этот году называется тот или иной полиморфизм.
Здравствуйте, B0FEE664, Вы писали:
BFE>Ну, хорошо, что вы выяснили то, что вам было нужно, я правда так и не понял, что именно.
Я выяснил какая картина мира в вашей голове. И что именно вы подразумеваете под "параметрическим полиморфизмом". Это позволило понять о чем вы говорите. До этого было не понятно.
BFE>Есть ещё один вид, самый первый, который используется, например, в qsort. qsort — общий код для неважно каких типов сортируемых массивов.
Это обычный динамический полиморфизм.
BFE>Я знаю про общее правило: если что-то в классификации обусловлено историческими причинами, aka "так сложилось", то значит в классификации есть проблемы. Такой эффект можно наблюдать в любой области: от классификации плодов растений (фрукт/овощ/ягода ...), до классификации планет в астрономии.
Речь идет не о достоинствах классификации. О том, какова классификация и какими свойствами обладают те или иные сущности согласно данной классификации. Если классификация определена, то она позволяет избежать непонимания и недоразумений. Вне зависимости от того, идеальна или классификация или нет.
BFE>А вообще интересно: вот если в C++ добавить шаблонный виртуальный метод (что не сложно сделать вручную), то какой тип будет у такого полиморфизма в вашей классификации?
Я не представляю себе, что вы вкладываете в понятие "шаблонный виртуальный метод".
BFE>В том-то и дело, что для для программистов, в отличии от математиков, разница в конкретном и абстрактном есть. Возьмём сумму 2 + 2 + 2 + 2, математику не важно сколько операций сложения в этом выражении, а программист должен уметь получать результат такого выражения за (максимум) две операции сложения.
По-моему, вы глубоко заблуждаетесь. Но, честно говоря, мне фиолетово. Вы можете заблужаться сколько угодно и как угодно.
S>>Поэтому принципальное значение с точки зрения архитектуры является применение абстракции "слот". Детали же реализации "слота" на архитектуру влияния не оказывают, т.к. эти детали рассматриваются несколькими уровнями ниже.
BFE>Ну так я вам об этом же и писал: BFE>
BFE>>std::function<void()> — это абстрактный символ, который может представлять любой тип. Согласны?
BFE>Нет. Это конкретный тип, который обозначает функцию без аргументов, возвращающую void. И ваше решение завязано конкретно на этот тип.
У вас есть дурная привычка брать цитаты, относящиеся к одной теме, и приплетать их к другой теме. Процитированное только что относилось к разговору о типах полиморфизма. Что становится совершенно очевидным, если процитировать с контекстом:
BFE>В C++ тоже можно стирать тип и тоже можно это делать различными способами. Стирание типа приводит к настоящему параметрическому полиморфизму, так как мы получаем возможность выполнять один и тот же код для любых типов.
У меня есть впечатление, что вы неправильно понимаете "параметрический полиморфизм". А так же не правильно интерпретируете "возможность выполнять один и тот же код для любых типов".
BFE>Какой тип полиморфизма в приведённом мною примере?
Динамический. Я об этом уже вроде бы говорил.
BFE>std::function<void()> — это абстрактный символ, который может представлять любой тип. Согласны?
Нет. Это конкретный тип, который обозначает функцию без аргументов, возвращающую void. И ваше решение завязано конкретно на этот тип.
Если вы смените его на тип void(int) или void(int, string), то работать оно уже не будет.
BFE>Ещё раз повторюсь, конечно результат тот же — и там и там полиморфизм. Дело не в результате, а в том, с помощью каких средств он достигается.
Нет, еще раз повторюсь. Дело не в средствах, а в типе полиморфизма. Это динамический полиморфизм. А реализуется он разными (на первый взгляд) средствами.
Теперь вы цитаты из обсуждения типов полиморфизма приплетаете к обсуждениям архитектуры. Не нужно так делать. А то складывается впечатление, что вы не можете удержать в голове нить разговора.
BFE>std::function<void()> — это слот и его реализация не важна. Так что я опять не понимаю, о чём вы толкуете.
Жаль, конечно, но пересказывать все еще раз нет возможности. Так что могу посоветовать разве что перечитать обсуждение еще раз.
BFE>Впрочем для меня это не важно, важно другое: использование слотов вместо виртуальных функций меняет подходы к задачам и их решение. В частности, задачу исходного сообщения (так, которая на хабре), технология signal-slot решит довольно просто.
Это ваше личное мнение, которое пока что не было доказано практикой. На хабре рассказали как люди решили стоящую перед ними проблему. Вы, судя по всему, саму проблему восприняли сильно по своему. И на словах предложили решение, которое, якобы, должно проблему решить проще.
Но это все слова. Сделайте свое решение, опишите в статье. Люди смогут сравнить и сделать выводы.
Пока же это все из категории "один чувак на форуме уверен, что можно сделать проще".
BFE>Важно это, а не то как в этот году называется тот или иной полиморфизм.
То, что подразумевается под полиморфизмом, важно для того, чтобы вас поняли быстро и однозначно. А не задавали 100500 допонительных вопросов, чтобы выяснить, что оказывается в вашей голове "параметрический полиморфизм" это то, что в этом году принято называть "динамическим полиморфизмом".
Здравствуйте, so5team, Вы писали:
BFE>>А вообще интересно: вот если в C++ добавить шаблонный виртуальный метод (что не сложно сделать вручную), то какой тип будет у такого полиморфизма в вашей классификации? S>Я не представляю себе, что вы вкладываете в понятие "шаблонный виртуальный метод".
Я в вкладываю в это понятие ровно то, что оно подразумевает, а именно: шаблонный метод, который можно переопределить в производном классе и при этом этот переопределённый метод может быть вызван через указатель на базовый класс.
Т.е. виртуальный метод класса являющийся одновременно с этим шаблонным методом.
Например:
class A
{
public:
template<class T>
virtual void method(T t)
{
...
}
};
class B : public A
{
public:
template<class T>
virtual void method(T t)
{
...
}
};
class C : public A
{
public:
template<class T>
virtual void method(T t)
{
...
}
};
....
A* arr[] = {new A, new B, new C, new D, new E};
std::string str("asdf");
for(A* p : arr)
{
p->method(1);
p->method('c');
p->method(1.1f);
p->method(str);
p->method("any other type");
}
При выполнении этого кода должны вызыватся функции того класса в которое они переопределены ( overrided ).
К сожалению C++ не поддерживает (пока) такой синтаксис и поэтому реализацию приходится прописывать руками.
Здравствуйте, B0FEE664, Вы писали:
BFE>Я в вкладываю в это понятие ровно то, что оно подразумевает, а именно: шаблонный метод, который можно переопределить в производном классе и при этом этот переопределённый метод может быть вызван через указатель на базовый класс. BFE>Т.е. виртуальный метод класса являющийся одновременно с этим шаблонным методом.
Я не думаю, что в C++, где шаблоны -- это именно что текстовые шаблоны, а не генерики с ограничениями на типа параметров на базе интерфейсов, такое вообще возможно. Ибо как тогда компилятор будет формировать vtbl в условиях раздельной компиляции не представляю.
BFE>К сожалению C++ не поддерживает (пока) такой синтаксис и поэтому реализацию приходится прописывать руками.
Здравствуйте, so5team, Вы писали:
S>Я не думаю, что в C++, где шаблоны -- это именно что текстовые шаблоны, а не генерики с ограничениями на типа параметров на базе интерфейсов, такое вообще возможно. Ибо как тогда компилятор будет формировать vtbl в условиях раздельной компиляции не представляю.
Ну да, раздельная компиляция всё портит, но это не языковое ограничение, а техническое.
BFE>>К сожалению C++ не поддерживает (пока) такой синтаксис и поэтому реализацию приходится прописывать руками. S>И как это выглядит вручную?
Здравствуйте, B0FEE664, Вы писали:
BFE>В комментариях там (на хабре) правильно заметили, что PassConcept + PassModel — это, по сути, реализация std::function написанная вручную.
Я откровенно говоря вижу в этом классический "молоточек". Это странно не видеть его везде, если ты программист.
Другое дело, что я знаком с llvm весьма поверхностно и конфигурация проходов не вызывала проблем.
Как я и написал — если за этим, что-то и есть — то статья не объясняет этого.
В основном потому, что ставит неразрешимую задачу либо объявляет ее неправильно. Единообразные коллекции никак не смогут помочь с тем, что до функций еще надо добраться, и выбрать нужные — итого по смыслу проходы сами по себе они не равнозначны. И на этом фоне, на этом примере я вижу только исключительно C++ template головного мозга, т.к. (исходя из статьи) — ни одна из поставленных задач (в статье) — не решена.
Естественно тут нет никакого concept-based полиформизма, и если бы в C++ не было бы концептов, я бы даже бы не читал этот хлам.
По прежнему, хочу заметить, что решение возможно, вполне имеет смысл в контексте проекта, если оно что-то действительно упрощает. Настораживает присутствие легаси версии и сложилось ощущение, что эту магию не осиливают нормальные разработчики и C API, которое вполне функционально.
Библиотека такого размера без C API — это как самолет без крыльев.