Pull to refresh

Comments 20

Кажется это обсуждается пропозал в язык С++, который я скинул под спойлером. Он видимо загнулся ещё до С++11

Штука прикольная, но в статье категорически не хватает деталей.

Статью можно сократить до трёх предложений без потери смысла:

  1. Есть проблема Х

  2. Все решения, что есть - так себе

  3. Я придумал либу - вот ссылка

Почему так?

1. Код под спойлерами невозможно прочитать, потому что в нём куча сущностей, про которые в статье ни слова. Да и те сущности что введены, не раскрыты ни разу. Например, фраза под спойлером для visit_invoke_fn : "применим трюк, чтобы комплилятор...". Какой трюк? Как он работает? Кем описан? Фиг пойми.

2. Не хватает каких-то деталей по части времени компиляции всего этого добра, какой стандарт С++ нужен (исходя из spaceship-оператора, полагаю, что не ниже С++20), сравнение по скорости с dynamic_cast / type_info-based решениями.

Ну и чисто придирка - вначале заявляется проблема взаимодействия кота и собаки, в финальном примере внезапно возникают космические объекты и коллизии. Неконсистентно.

Выглядит оно достаточно интересно, чтобы я сходил в исходники посмотреть, но видится мне в статье есть что улучить, чтобы получилась статья, а не просто заметка в стиле "Я тут пометапрограммировал и получилось прикольно. Дальше сами разбирайтесь".

Тут проблема - если описывать подробно, то статья выйдет громадной и слишком сложной

Я не вижу в этом великой проблемы.

Во-первых, ну-таки это хабр. Здесь хорошие лонгриды любят и ценят.

Во-вторых, такова природа всех статей по метапрограммированию. Они требуют вдумчивого долгого чтения.

В-третьих, я потратил на беглое чтение (чтобы понять стоит статья прочтения) всего минут 5. Даже если статья вдруг выросла бы в 3 раза по объёму - вроде и ничего страшного.

Конечно, чукча читатель, чукча не писатель. Статей своих у меня ровно ноль, но именно как читателю мне показалось, что для того, чтобы статья стала чуть лучше, можно было бы:

  1. Выкинуть всю часть про Lisp. Она не добавляет статье вообще ничего полезного. Суть проблемы и желаемый интерфейс понятны уже из первого С++ примера.

  2. Более развёрнутого описания работы descriptor_t и visit_invoke_fn . Просто потому, что это буквально самая важная часть, половина обсуждаемой проблемы в том, чтобы придумать этот идентификатор. Вторая половина - как сохранять функции и типы аргументов к ним. Эта часть вынесена вообще под спойлер с кодом без единого внятного комментария, хотя является критичной для понимания вашего решения. Всё остальное - это пляска вокруг контейнера, сие не особо интересно, т.к. пишется относительно просто.

  3. Не особо я понял часть про type_info. Деталей магии стоящей за этой языковой фичей я не помню, неплохо бы какие-то пояснения хотя бы под спойлер занести. В частности часть про изменения имени "от вызова к вызову". Также неплохо было бы рассмотреть помимо стандартного type_info еще хотя бы boost type_index.

  4. Ну и да, как я сказал - нужны замеры по отношению к более "дубовым" решениям в стиле dynamic_cast. Как времени компиляции, так и работы в рантайме, для разного количества диспетчеризуемых типов.

  5. Совсем хорошо - описать существуют ли какие-то известные вам ограничения применимости вашей библиотеки, чтобы можно было сразу понять, могу я себе её затянуть или нет. Может быть есть какие-то неочевидные вещи по контекстам применения или лицензионные ограничения.

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

Я дополнил статью насколько это возможно максимально упрощая, спасибо за фидбек

Про лисп вы зря: увидел отсылку, вспомнил книгу мага, понял, о чем статья.

Всё правильно, чтобы не погрязнуть в большом количестве технических деталей. Если что можно всегда в комментариях вопрос задать или с автором связаться. Спасибо, мультиметоды классная вещь, в Julia кстати считается одной из главных фич языка.

С++ может изменяться от вызова к вызову (!)

Нет, это не совсем так. Результат может меняться между выполнениями одной и той же программы, между загрузкой/выгрузкой одной и той же динамической библиотеки, но никак не между вызовами в рамках одного lifetime модуля.

(https://en.cppreference.com/w/cpp/types/type_info/name)

С точки зрения стандарта языка это называется "от вызова к вызову"

В стандартной библиотеке С++ нет возможности сделать это никак кроме вереницы 'if' с dynamic_cast

Можно попробовать поизвращаться над паттерном посетитель. Будет многословным и жутко выглядеть, но скорее всего быстрее работать.

Быстрее решения, приведенного в статье.

даже интересно как же это вы собрались реализовать. Уж не через std::visit, проблемы которого в статье описаны?

даже интересно как же это вы собрались реализовать

Я вообще не собирался это реализовывать. Но это можно сделать парой-тройкой виртуальных вызовов. На хабре подобное уже было, но для шарпа. Минусы такого решения я написал сходу.

Согласен с комментарием выше, что в статье катастрофически не хватает деталей и более подробных объяснений.

Я, в частности, искал как для кейса "void foo(Animal* a, Animal* b) { ??? }" можно использовать descriptor_v (?), чтобы получить в рантайме уникальный идентификатор полиморфного типа.

descriptor_v это всего лишь обычное значение, а интерфейсы уже сами могут выбрать как им использовать это.
Я описал poly_traits как это получается для std::variant, описал в коде в библиотеке как это работает для any_with<....> и других полиморфных типов библиотеки, если у вас просто интерфейс, то Animal может хранить это значение или, если это виртуальные функции, то иметь виртуальный метод .get_descriptor()

Или в конце концов сделать кучу динамик кастов и достать нужный дескриптор оттуда, но это неэффективно

просто any_with первый раз в статье появляется в самом конце, в последнем примере

шо это такое, зачем оно надо, как соотносится с рассуждениеми в начале статьи -- полный туман

А вот в Си такое можно и средствами языка (_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;
}

Тем что это происходит на компиляции и вы сделали ручную перегрузку. А то что в посте работает на этапе выполнения программы над значениями со стёртым типом

Sign up to leave a comment.

Articles