Здравствуйте, 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 расчин на одну сигнатуру, а вот типы разные.
Разговор, который начинался для выяснения того, что вы понимаете под "параметрическим полиморфизимом" начинает перерастать в спор о том, чьи определения полиморфизма должны считаться правильными. По ряду причин у меня нет ни возможности, ни желания развивать этот спор. Поэтому предлагаю закончить, т.к. я выяснил то, что мне было нужно.
Но, т.к. моя точка зрения могла остаться недопонятой, то тезисно по основным пунктам.
От классификации зависит понимание предмета собеседниками. Если у каждого из нас собственное представление о "параметрическом полиморфизме", то когда вы говорите "у меня в коде используется параметрический полиморфизм", то я ваши слова буду воспринимать совершенно не так. И наоборот.
Динамический полиморфизм в С и в 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". Разница на первых этапах обучения существует только у детей, для которых подсчитывание палочек на парте и черточек на доске может выглядеть принципиально разными занятиями. Но по мере обучения развивается абстрактное мышление и эта разница исчезает.
Поэтому принципальное значение с точки зрения архитектуры является применение абстракции "слот". Детали же реализации "слота" на архитектуру влияния не оказывают, т.к. эти детали рассматриваются несколькими уровнями ниже.
Здравствуйте, 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 — это как самолет без крыльев.