Comments 20
Вроде что-то похожее было на rsdn в 2003:
Штука прикольная, но в статье категорически не хватает деталей.
Статью можно сократить до трёх предложений без потери смысла:
Есть проблема Х
Все решения, что есть - так себе
Я придумал либу - вот ссылка
Почему так?
1. Код под спойлерами невозможно прочитать, потому что в нём куча сущностей, про которые в статье ни слова. Да и те сущности что введены, не раскрыты ни разу. Например, фраза под спойлером для visit_invoke_fn
: "применим трюк, чтобы комплилятор...". Какой трюк? Как он работает? Кем описан? Фиг пойми.
2. Не хватает каких-то деталей по части времени компиляции всего этого добра, какой стандарт С++ нужен (исходя из spaceship-оператора, полагаю, что не ниже С++20), сравнение по скорости с dynamic_cast / type_info-based решениями.
Ну и чисто придирка - вначале заявляется проблема взаимодействия кота и собаки, в финальном примере внезапно возникают космические объекты и коллизии. Неконсистентно.
Выглядит оно достаточно интересно, чтобы я сходил в исходники посмотреть, но видится мне в статье есть что улучить, чтобы получилась статья, а не просто заметка в стиле "Я тут пометапрограммировал и получилось прикольно. Дальше сами разбирайтесь".
Тут проблема - если описывать подробно, то статья выйдет громадной и слишком сложной
Я не вижу в этом великой проблемы.
Во-первых, ну-таки это хабр. Здесь хорошие лонгриды любят и ценят.
Во-вторых, такова природа всех статей по метапрограммированию. Они требуют вдумчивого долгого чтения.
В-третьих, я потратил на беглое чтение (чтобы понять стоит статья прочтения) всего минут 5. Даже если статья вдруг выросла бы в 3 раза по объёму - вроде и ничего страшного.
Конечно, чукча читатель, чукча не писатель. Статей своих у меня ровно ноль, но именно как читателю мне показалось, что для того, чтобы статья стала чуть лучше, можно было бы:
Выкинуть всю часть про Lisp. Она не добавляет статье вообще ничего полезного. Суть проблемы и желаемый интерфейс понятны уже из первого С++ примера.
Более развёрнутого описания работы
descriptor_t
иvisit_invoke_fn
. Просто потому, что это буквально самая важная часть, половина обсуждаемой проблемы в том, чтобы придумать этот идентификатор. Вторая половина - как сохранять функции и типы аргументов к ним. Эта часть вынесена вообще под спойлер с кодом без единого внятного комментария, хотя является критичной для понимания вашего решения. Всё остальное - это пляска вокруг контейнера, сие не особо интересно, т.к. пишется относительно просто.Не особо я понял часть про type_info. Деталей магии стоящей за этой языковой фичей я не помню, неплохо бы какие-то пояснения хотя бы под спойлер занести. В частности часть про изменения имени "от вызова к вызову". Также неплохо было бы рассмотреть помимо стандартного type_info еще хотя бы boost type_index.
Ну и да, как я сказал - нужны замеры по отношению к более "дубовым" решениям в стиле
dynamic_cast
. Как времени компиляции, так и работы в рантайме, для разного количества диспетчеризуемых типов.Совсем хорошо - описать существуют ли какие-то известные вам ограничения применимости вашей библиотеки, чтобы можно было сразу понять, могу я себе её затянуть или нет. Может быть есть какие-то неочевидные вещи по контекстам применения или лицензионные ограничения.
В любом случае, спасибо за труд, библиотеку гляну.
Всё правильно, чтобы не погрязнуть в большом количестве технических деталей. Если что можно всегда в комментариях вопрос задать или с автором связаться. Спасибо, мультиметоды классная вещь, в Julia кстати считается одной из главных фич языка.
С++ может изменяться от вызова к вызову (!)
Нет, это не совсем так. Результат может меняться между выполнениями одной и той же программы, между загрузкой/выгрузкой одной и той же динамической библиотеки, но никак не между вызовами в рамках одного lifetime модуля.
В стандартной библиотеке С++ нет возможности сделать это никак кроме вереницы 'if' с dynamic_cast
Можно попробовать поизвращаться над паттерном посетитель. Будет многословным и жутко выглядеть, но скорее всего быстрее работать.
Согласен с комментарием выше, что в статье катастрофически не хватает деталей и более подробных объяснений.
Я, в частности, искал как для кейса "void foo(Animal* a, Animal* b) { ??? }" можно использовать descriptor_v (?), чтобы получить в рантайме уникальный идентификатор полиморфного типа.
descriptor_v это всего лишь обычное значение, а интерфейсы уже сами могут выбрать как им использовать это.
Я описал poly_traits как это получается для std::variant, описал в коде в библиотеке как это работает для any_with<....> и других полиморфных типов библиотеки, если у вас просто интерфейс, то Animal может хранить это значение или, если это виртуальные функции, то иметь виртуальный метод .get_descriptor()
Или в конце концов сделать кучу динамик кастов и достать нужный дескриптор оттуда, но это неэффективно
А вот в Си такое можно и средствами языка (_Generic) начиная с C11, посмотрите:
#include <stdio.h>
typedef struct {} spaceship;
typedef struct {} star;
typedef struct {} asteroid;
void ship_asteroid(spaceship s, asteroid a){printf("ship_asteroid\n");}
void ship_star(spaceship s, star){printf("ship_star\n");}
void star_star(star a, star b){printf("star_star\n");}
void ship_ship(spaceship a, spaceship b){printf("ship_ship\n");}
#define dispatch(x, y) \
_Generic((x), \
spaceship: _Generic((y), \
asteroid : ship_asteroid, \
star : ship_star, \
spaceship : ship_ship), \
star : star_star)(x, y)
int main(int argc, char *argv[]) {
spaceship sh;
asteroid as;
star st;
dispatch(sh, as);
dispatch(sh, st);
dispatch(st, st);
dispatch(sh, sh);
return 0;
}
Вывод ожидаемый:
ship_asteroid
ship_star
star_star
ship_ship
К сожалению, если типов сделать больше и ко всем еще добавлять функции, принимающие их, то из этого выйдет мешанина. Но базово сделать все-таки что-то можно!
И не очень понятно, раз мы говорим про C++, то чем плоха специализация шаблонов?
#include <iostream>
#include <string>
struct spaceship {};
struct asteroid {};
struct star {};
template <typename A, typename B> void dispatch(A a, B b) {
static_assert(false, "specialization required");
}
template <> void dispatch<spaceship, asteroid>(spaceship a, asteroid b) {
std::cout << "spaceship, asteroid" << std::endl;
}
template <> void dispatch<spaceship, star>(spaceship a, star b) {
std::cout << "spaceship, star" << std::endl;
}
template <> void dispatch<star, star>(star a, star b) {
std::cout << "star, star" << std::endl;
}
template <> void dispatch<spaceship, spaceship>(spaceship a, spaceship b) {
std::cout << "spaceship, spaceship" << std::endl;
}
int main(int argc, char *argv[]) {
spaceship sh;
asteroid as;
star st;
dispatch(1, 2); // ошибка компиляции
dispatch(sh, as);
dispatch(sh, st);
dispatch(st, st);
dispatch(sh, sh);
return 0;
}
Пишем мультиметоды из Lisp в С++