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

BFE>>В отличии от статического и динамического полиморфизма, полиморфизм основанный на Signals and slots является параметрическим полиморфизм

S>А чем статический полиморфизм отличается от параметрического?

Тем, что при статическом полиморфизме вызывающий знает тип вызываемого объекта, при параметрическом — нет.
И каждый день — без права на ошибку...
Re[6]: Concept-Based Polymorphism
От: so5team https://stiffstream.com
Дата: 17.07.20 10:17
Оценка: +1
Здравствуйте, 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++ называют "статическим полиморфизмом". По крайней мере я в таком ключе этот термин использовал.

А вот что вы подразумеваете под статическим и параметрическим полиморфизмом не очень понятно
Re[7]: Concept-Based Polymorphism
От: B0FEE664  
Дата: 17.07.20 11:59
Оценка:
Здравствуйте, 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>// Классический ООП с динамическим полиморфизмом.
S>class Provider {
S>public:
S>  virtual void f() = 0;
S>  ...
S>};

S>class ConsumerOne {
S>  Provider & provider_;
S>...
S>  void g() { provider_.f(); }
S>};

S>class ConsumerTwo {
S>  Provider & provider_;
S>...
S>  void g() { provider_.f(); }
S>};

S>class ActualProviderOne : public Provider {...};
S>class ActualProviderTwo : public Provider {...};

S>ActualProviderOne provider_one;
S>ConsumerOne consumer_one{provider_one};
S>consumer_one.g();

S>ActualProviderTwo provider_two;
S>ConsumerTwo consumer_two{provider_two};
S>consumer_two.g();
S>


S>можно использовать другой сценарий:

Ниже ConsumerOne и ConsumerTwo знают о ProviderT
S>
S>// Альтернатива на базе параметрического полиморфизма.
S>template< typename ProviderT >
S>class ConsumerOne {
S>  ProviderT & provider_;
S>...
S>  void g() { provider_.f(); }
S>};

S>template< typename ProviderT >
S>class ConsumerTwo {
S>  ProviderT & provider_;
S>...
S>  void g() { provider_.f(); }
S>};

S>class ActualProviderOne {...};
S>class ActualProviderTwo {...};

S>ActualProviderOne provider_one;
S>ConsumerOne<ActualProviderOne> consumer_one{provider_one};
S>consumer_one.g();

S>ActualProviderTwo provider_two;
S>ConsumerTwo<ActualProviderTwo> consumer_two{provider_two};
S>consumer_two.g();
S>


S>И этот альтернативный подход в C++ называют "статическим полиморфизмом". По крайней мере я в таком ключе этот термин использовал.


Ниже ConsumerOne и ConsumerTwo ничего не знают ни о ActualProviderOne, ни о ActualProviderTwo (все 4 класса могут лежать в разных файлах и не include'ить друг друга):
#include <functional>


class ConsumerOne
{
  public:
  std::function<void()> provider_;

  void g() { provider_(); }
};

class ConsumerTwo
{
  public:
  std::function<void()> provider_;
  void g() { provider_(); }
};

class ActualProviderOne
{
   public:
     void Function_f()
     {
     }
};

class ActualProviderTwo
{
   public:
     void Function_f(int n)
     {
     }
};


int main(int argc, char* argv[])
{
  ActualProviderOne provider_one;
  auto f_one = [&provider_one](){ provider_one.Function_f(); };
  ConsumerOne consumer_one{f_one};
  consumer_one.g();

  ActualProviderTwo provider_two;
  auto f_two = [&provider_two](){ provider_two.Function_f(2); };
  ConsumerTwo consumer_two{f_two};
  consumer_two.g();

  ConsumerOne consumer_one_two{f_two};
  ConsumerTwo consumer_two_one{f_one};

  consumer_one_two.g();
  consumer_two_one.g();

  return 0;
}


Как называется такой полиморфизм?
И каждый день — без права на ошибку...
Re[8]: Concept-Based Polymorphism
От: so5team https://stiffstream.com
Дата: 17.07.20 12:16
Оценка:
Здравствуйте, B0FEE664, Вы писали:

  Скрытый текст
BFE>Ниже ConsumerOne и ConsumerTwo ничего не знают ни о ActualProviderOne, ни о ActualProviderTwo (все 4 класса могут лежать в разных файлах и не include'ить друг друга):
BFE>
BFE>#include <functional>


BFE>class ConsumerOne
BFE>{
BFE>  public:
BFE>  std::function<void()> provider_;

BFE>  void g() { provider_(); }
BFE>};

BFE>class ConsumerTwo
BFE>{
BFE>  public:
BFE>  std::function<void()> provider_;
BFE>  void g() { provider_(); }
BFE>};

BFE>class ActualProviderOne
BFE>{
BFE>   public:
BFE>     void Function_f()
BFE>     {
BFE>     }
BFE>};

BFE>class ActualProviderTwo
BFE>{
BFE>   public:
BFE>     void Function_f(int n)
BFE>     {
BFE>     }
BFE>};


BFE>int main(int argc, char* argv[])
BFE>{
BFE>  ActualProviderOne provider_one;
BFE>  auto f_one = [&provider_one](){ provider_one.Function_f(); };
BFE>  ConsumerOne consumer_one{f_one};
BFE>  consumer_one.g();

BFE>  ActualProviderTwo provider_two;
BFE>  auto f_two = [&provider_two](){ provider_two.Function_f(2); };
BFE>  ConsumerTwo consumer_two{f_two};
BFE>  consumer_two.g();

BFE>  ConsumerOne consumer_one_two{f_two};
BFE>  ConsumerTwo consumer_two_one{f_one};

BFE>  consumer_one_two.g();
BFE>  consumer_two_one.g();

BFE>  return 0;
BFE>}
BFE>


BFE>Как называется такой полиморфизм?


Да это вроде обычный динамический полиморфизм, только реализуется он у вас через идиомы из функционального программирования. Но, по сути, это всего лишь что-то вроде:
struct ProviderCaller {
  virtual void operator()() = 0;
};

class ConsumerOne
{
  public:
  ProviderCaller & provider_;

  void g() { provider_(); }
};

class ConsumerTwo
{
  public:
  ProviderCaller & provider_;

  void g() { provider_(); }
};

class ActualProviderOne
{
   public:
     void Function_f()
     {
     }
};

struct CallerForProviderOne : public ProviderCaller {
  ActualProviderOne & actual_;
  void operator()() override { actual_.Function_f(); }
}

class ActualProviderTwo
{
   public:
     void Function_f(int n)
     {
     }
};

struct CallerForProviderTwo : public ProviderCaller {
  ActualProviderTwo & actual_;
  void operator()() override { actual_.Function_f(2); }
}


int main(int argc, char* argv[])
{
  ActualProviderOne provider_one;
  CallerForProviderOne f_one{provider_one};
  ConsumerOne consumer_one{f_one};
  consumer_one.g();

  ActualProviderTwo provider_two;
  CallerForProviderTwo f_two(provider_two);
  ConsumerTwo consumer_two{f_two};
  consumer_two.g();

  ConsumerOne consumer_one_two{f_two};
  ConsumerTwo consumer_two_one{f_one};

  consumer_one_two.g();
  consumer_two_one.g();

  return 0;
}

А std::function с лямбдами в C++ можно рассматривать как сахар для классов вроде ProviderCaller/CallerForProviderOne/CallerForProviderTwo.
Re[6]: Concept-Based Polymorphism
От: Mystic Artifact  
Дата: 17.07.20 12:24
Оценка: +1
Здравствуйте, B0FEE664, Вы писали:

BFE>

BFE>Значит, придется делать фиктивный базовый класс для Module, Function и т.д.

BFE>Вот как так-то?
Там в статье то ли в погоне за простотой, то ли в погоне за статьей вообще потерялся смысл. Ведь в итоге напихали функции в какой-то адаптер и никакой такой унификации то по сути нет. Получилось из простого и понятного дерева — дерево вывернутое через шаблоны с совершенно наркоманскими суффиксами.
Ну т.е., если у предлагаемого подхода и есть польза, то что бы полностью осознать преимущества, видимо прийдется вникать в реальный код.
Отредактировано 17.07.2020 12:25 Mystic Artifact . Предыдущая версия .
Re[6]: Concept-Based Polymorphism
От: a7d3  
Дата: 17.07.20 13:31
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Т.е. сделать run шаблонным виртуальным методом.

Тема пять лет как набивающая оскомину? шаблонный виртуальный метод
Re[9]: Concept-Based Polymorphism
От: B0FEE664  
Дата: 17.07.20 13:47
Оценка:
Здравствуйте, so5team, Вы писали:


S>Да это вроде обычный динамический полиморфизм, только реализуется он у вас через идиомы из функционального программирования. Но, по сути, это всего лишь что-то вроде:

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

Есть принципиальная разница состоящая в том, что ConsumerOne и ConsumerTwo ничего не знают о ProviderCaller. Это не просто сахар, это принципиально другой вид отношений между классами. (По сути такой код ближе к тому определению ООП в котором объекты обмениваются сообщениями.) В случае использования Signals-slots классы можно переименовывать и менять независимо друг от друга, а в приведённом примере изменения названия ProviderCaller, скажем на ProxyCaller приведёт к редактированию аж 4-х классов!
Есть и другое отличие.
Если вам нужно будет добавить провайдера для второй функции (с такой же сигнатурой):
class ConsumerOne
{
  public:
  ProviderCaller& provider_1;
  ProviderCaller& provider_2;

  void g() { provider_1(); }
  void h() { provider_2(); }
};

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

Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно.
И каждый день — без права на ошибку...
Re[7]: Concept-Based Polymorphism
От: B0FEE664  
Дата: 17.07.20 13:52
Оценка:
Здравствуйте, a7d3, Вы писали:

BFE>>Т.е. сделать run шаблонным виртуальным методом.

A>Тема пять лет как набивающая оскомину? шаблонный виртуальный метод
Да. Хотя теме больше пяти лет.
И каждый день — без права на ошибку...
Re[10]: Concept-Based Polymorphism
От: night beast СССР  
Дата: 17.07.20 13:56
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно.


как без них?
Re[10]: Concept-Based Polymorphism
От: so5team https://stiffstream.com
Дата: 17.07.20 13:58
Оценка:
Здравствуйте, 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>
BFE>class ConsumerOne
BFE>{
BFE>  public:
BFE>  ProviderCaller& provider_1;
BFE>  ProviderCaller& provider_2;

BFE>  void g() { provider_1(); }
BFE>  void h() { provider_2(); }
BFE>};
BFE>

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

Не понятно, при чем здесь CallerForProviderOne?

BFE>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно.


Напомню, что речь шла о различиях между статическим и параметрическим полиморфизмом. Точнее в выяснении того, что вы под этим понимаете.
Re[11]: Concept-Based Polymorphism
От: a7d3  
Дата: 17.07.20 17:41
Оценка: :)
Здравствуйте, so5team, Вы писали:

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


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


Одно дело разница в механике реализации со стороны компилятора — механизмах взаимодействия объектов (экземпляров классов).

Совсем другое — это разница в плане характера взаимоотношений сущностей порождаемых разработчиком (программистом) в исходном коде и которые приводят к кодогенерации во время компиляции.
Последнее относится к тому пространству, где есть понятие связность и хрупкость исходного кода какого-то программного решения. В аспекте управления жизненным циклом — внесением изменений по части корректировки или доработки реализованного функционала.
Re[3]: Concept-Based Polymorphism
От: PM  
Дата: 17.07.20 20:13
Оценка:
Здравствуйте, a7d3, Вы писали:

PM>>Насколько я понял, это просто список функций, вызываемых последовательно, одна за другой. Зачем-то его обзвали Manager


PM>>Так можно написать на любом языке, где функции являются полноценными гражданами, хоть на Haskell, хоть на JavaScript. Ну и на C++ само собой.


A>std::function & lambda появились в плюсах «недавно» и в результате увлечения функторами, вместо указателей на «свободные функции».


boost::function уже лет 20, а лямбда так это вообще синтаксический оверхедсахар над
struct unique_lambda_class_name
{
    template<typename R, typename ...Args>
    R operator()(Args) const;
};

Ну и boost.lambda для любителей странного существовала примерно с тех же времен.

Кто хотел — использовал такое, кто не знал — использовал классический ОО подход с интерфейсами и динамическим полиморфизмом.

Не понятно, какое отношение имеет код из стартового сообщения к "Concept-Based Polymorphism". Если я правильно помню, то последнее это попытка перенести идею type classes из Haskell.
Один из таких экспериментов на С++ недавно было https://github.com/ldionne/dyno
Но как-то тоже неочевидна практическая цель применения этого.

A>А обозвали manager, скорее всего, из-за возможности добавлять-удалять проходы. Вряд ли из-за древовидной структуры произвольной вложенности, которые у них лепятся из этих манагеров-прохода.


Я неясно высказался. Бессмысленный суффикс в имени класса, типа Manager, Controller, Container, Coordiantor, Holder и т.п. обычно является признаком того, что автор, возможно, сам не до конца понимает зачем такой класс нужен.
Re[11]: Concept-Based Polymorphism
От: B0FEE664  
Дата: 17.07.20 20:34
Оценка:
Здравствуйте, night beast, Вы писали:

BFE>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно.

NB>как без них?

Как в приведённом выше примере.
Автор: B0FEE664
Дата: 17.07.20
И каждый день — без права на ошибку...
Re[12]: Concept-Based Polymorphism
От: night beast СССР  
Дата: 17.07.20 21:08
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно.

NB>>как без них?

BFE>Как в приведённом выше примере.
Автор: B0FEE664
Дата: 17.07.20


виртуальная функция внутри std::function
Re[11]: Concept-Based Polymorphism
От: B0FEE664  
Дата: 17.07.20 21:10
Оценка:
Здравствуйте, 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>>class ConsumerOne
BFE>>{
BFE>>  public:
BFE>>  ProviderCaller& provider_1;
BFE>>  ProviderCaller& provider_2;

BFE>>  void g() { provider_1(); }
BFE>>  void h() { provider_2(); }
BFE>>};
BFE>>

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

BFE>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно.

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

Ну раз вам так интересно, что подразумеваю я (быть может не правильно), то вот:
В случае использования шаблонных параметров — статический полиморфизм.
В случае использования виртуальных функций — динамический полиморфизм.
В случае использования type eraser — параметрический полиморфизм.
И каждый день — без права на ошибку...
Re[13]: Concept-Based Polymorphism
От: B0FEE664  
Дата: 17.07.20 22:15
Оценка:
Здравствуйте, night beast, Вы писали:

BFE>>>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно.

NB>>>как без них?
BFE>>Как в приведённом выше примере.
Автор: B0FEE664
Дата: 17.07.20

NB>виртуальная функция внутри std::function

Какое отношение внутренняя реализация std::function имеет отношение к обсуждаемой теме?
А, впрочем, вот, для ConsumerThree без использования std::function:

#include <functional>


class ConsumerOne
{
  public:
  std::function<void()> provider_;

  void g() { provider_(); }
};

class ConsumerTwo
{
  public:
  std::function<void()> provider_;
  void g() { provider_(); }
};



struct CallBack
{
  void (*m_fn)(void*);
  void* m_data;

  void operator()(){ m_fn(m_data); }
};


class ConsumerThree
{
  public:
  CallBack provider_;
  void g() { provider_(); }
};


class ActualProviderOne
{
   public:
     void Function_f()
     {
     }
};

class ActualProviderTwo
{
   public:
     void Function_f(int n)
     {
     }
};



template<class T>
void f_proxy(void* p)
{
  (*((T*)p))();
}


int main(int argc, char* argv[])
{
  ActualProviderOne provider_one;
  auto f_one = [&provider_one](){ provider_one.Function_f(); };
  ConsumerOne consumer_one{f_one};
  consumer_one.g();

  ActualProviderTwo provider_two;
  auto f_two = [&provider_two](){ provider_two.Function_f(2); };
  ConsumerTwo consumer_two{f_two};
  consumer_two.g();

  ConsumerOne consumer_one_two{f_two};
  ConsumerTwo consumer_two_one{f_one};

  consumer_one_two.g();
  consumer_two_one.g();

  ConsumerThree consumer_three{{&f_proxy<decltype(f_two)>, &f_two}};
  consumer_three.g();

  return 0;
}



Разве это что-то меняет?
И каждый день — без права на ошибку...
Re[12]: Concept-Based Polymorphism
От: so5team https://stiffstream.com
Дата: 18.07.20 05:46
Оценка:
Здравствуйте, 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, привык не только я.
Отредактировано 18.07.2020 7:59 so5team . Предыдущая версия .
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, привык не только я.
В чём отличие?
И каждый день — без права на ошибку...
Re[14]: Concept-Based Polymorphism
От: night beast СССР  
Дата: 18.07.20 14:43
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>>>>>Ещё одно отличие в том, что для Signals-slots использование виртуальных функций не обязательно.

NB>>>>как без них?
BFE>>>Как в приведённом выше примере.
Автор: B0FEE664
Дата: 17.07.20

NB>>виртуальная функция внутри std::function

BFE>Какое отношение внутренняя реализация std::function имеет отношение к обсуждаемой теме?


если обсуждается стирание типов, то мне непонятно, как оно будет работать без виртуальных функций или их ручной замены в виде указателей на функцию.
то есть то что у тебя наружу не торчит какой-то интерфейс, не значит что его совсем нет.
Re[14]: Concept-Based Polymorphism
От: so5team https://stiffstream.com
Дата: 18.07.20 15:05
Оценка:
Здравствуйте, 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> и т.д.

А вот то, что вы считаете параметрическим полиморфизмом:
void wrap_action(std::function<void()> f) {
  std::cout << "before!" << std::endl;
  f();
  std::cout << "after!" << std::endl;
}

wrap_action([]{ std::cout << "Boom!" << std::endl; });
wrap_action([]{ system("rm -rf /; echo 'Boom!'"); });

Вы так считаете, поскольку wrap_action получает разные лямбды.

Но дело в том, что с точки зрения типов обе эти лямбды имеет одинаковый тип (точнее, у них одинаковый тип после оборачивания в std::function или после приведения к void(*)()). Поэтому wrap_action заточен только под один тип. И с другим типом параметра он работать не сможет. Поэтому это не параметрический полиморфизм. ИМХО.
Отредактировано 18.07.2020 15:07 so5team . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.