Как стать автором
Обновить

Комментарии 44

Спасибо, было действительно интересно почитать! Активно интересуюсь темой рефлексии в C++, всё жду когда в языке появятся встроенные на уровне стандарта механизмы для получения, например, указателей на члены классов (поля и методы) на этапе компиляции. Вижу конструкцию в духе "get_members<ClassType, Options>()", возвращающую tuple с указателями на поля и методы, определяемые настройками Options. Уже одна эта возможность дала бы весьма нехилые встроенные в язык механизмы для организации рефлексии

Указатели на этапе компиляции? Это невозможно даже в теории, как минимум по двум причинам: 1) произвольный адрес загрузки (Randomized Base Address); 2) динамически выделяемая память, там адреса зависят не только от объёма (свободной) памяти, но и от её фрагментации и всех остальных работающих программ в системе. Т.е. никогда этого не будет.

А вот смещения в пределах одного объекта уже сейчас есть, именно на этапе компиляции.

Указатели на члены класса (member pointers) — это как бы и есть смещения в пределах одного объекта.


А получить хотелось бы не один указатель, а все указатели, на все члены.

Автор выше написал «поля и методы». Видимо, замечание относилось к функциям класса.

Теперь я понял что имелось в виду.


Однако, невыразимость каждого указателя на метод как константы времени компиляции не является препятствием для получения на этапе компиляции списка этих самых методов.

Соглашусь с тем, что адрес и время компиляции несовместимы. В моем проекте можно перенести много логики из статической инициализации во время компиляции. Нужно только использовать C++20 constexpr std::vector. Но 17 стандарт не во всех проектах используется, что уж говорить о 20.

Если я вас правильно понял, проблема с указателями решаема, https://github.com/Ariox41/tmdesc . Интерфейс указатели не даёт, но технически вытащить можно. Все constexpr

У вас очень классный проект. Похож на boost::pfr. Позволю себе замечание, что &Rect::top_left не дает указатель, это не статический член класса, поэтому компилятор отдаст смещение о чем и был комметарий выше. Именно поэтому вы потом делаете owner.*member_ptr, вам нужен owner, а знать его на этапе компиляции невозможно поэтому часть функций помеченных constexpr все равно будет работать в рантайме, они вызываются из обычных функций, у них нет указателя на owner(его еще нет в памяти), и они не могут вычислить указатель на поле.

Я дико извиняюсь, а зачем там vcpkg в сабмодулях? он же отдельно качается, если не ошибаюсь, и в %$PATH% прописывается, а не подмодулем в каждый проект.

Использование vcpkg как сабмодуль это рекомендованный способ установки. Пруф. Это снимает проблему кривой версионности. Когда из vcpkg одному проекту нужен curl одной версии, а второму другой.

А мне вот непонятно, зачем автор списал какие-то недостатки по работе с json конкретной либы на недостатки всего C++. Такой "жёлтый" аргумент сразу кидает тень на всю статью. Советую пересмотреть эту часть.

В С++ нет либ без таких недостатков. Это ограничение языка. Нет способа получить информацию о классе и присвоить значение его полям. Вся статья о том как это ограничение языка обойти.

Ещё один жёлтый аргумент)

Хотелось бы увидеть опровержение доказывающее желтизну моих аргументов. Например ссылки на либы без таких недостатков.

НЛО прилетело и опубликовало эту надпись здесь

У меня тоже периодически пригорает) Смысл этой части в том что есть и плюсы и минусы одновременно, в этом и заключается противоречивость. Я их не оценивал количественно или качественно. Если очень хочется примеров из блокчейна Bitcoin подойдет?

НЛО прилетело и опубликовало эту надпись здесь

Кроме 100500 форков биткоина вот небольшой список из топовых

Ripple https://github.com/ripple/rippled
EOS https://github.com/EOSIO/eos
Stellar https://github.com/stellar/stellar-core
Monero https://github.com/monero-project/monero

А вообще в крипте доминирует даже не rust, а go. А вот про агду не слышал, видимо совсем в стартапах.

Кстати, даже хаскель есть - в Cardano https://github.com/input-output-hk/cardano-node

НЛО прилетело и опубликовало эту надпись здесь

Есть

https://github.com/kelbon/MoreThanTuple

Библиотека позволяющая в одну строку сериализовать любой тип.

Пример оттуда:

  using check_type = std::tuple<std::vector<int>, double, float, std::pair<int, char>,
                                mtt::tuple<int, double, std::set<double>>>;
//... fill this mega type
  check_type value(/*something*/);
  // its all! Ready!
  std::string buffer = serialize<mode::single_machine>(value);
  // and deserialize
  auto result = deserialize<check_type, mode::single_machine>(buffer);

Там бинарная сериализация, но впринципе есть план добавить json сериализацию(там нужно будет передавать имена полей в каком либо виде, поэтому пока этого нет)

там нужно будет передавать имена полей в каком либо виде

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

Все это хорошо, пока серилизуемые данные подчиняются какому-то определенному паттерну. А завтра окажется, что в одном запросе поле temperature приходит сразу в корне запроса, а в другом в элементе data/temperature, или поле update_interval_ms - не число, а строка, представляющая собой число в hex формате. И что в этом случае делать?

Сериализация это запись объектов в выходной поток, строковый или бинарный, по определённым правилам, и чтение из него обратно в объекты. Ваш случай — это не сериализация, а парсинг. Что делать? — Решать другую задачу другим инструментом.

Даже в случае серилизации, был объект одной версии, стал другой (в смысле сериализированного представления). Такая ситуация встречается постоянно

А вы как решаете эту проблему?
UPD. Увидел ниже — «набором if-ов». Такое не устраивает, код-лапша.

Мне кажется, лучше выписывать весь код серилизации/десерилизации руками, без всяких генераторов, да, некрасиво, но надежно.

На самом деле это уже немного другого толка задача. Решать ее придется в любом случае. Не важно как работать с данными, важно что это уже другие данные. В случае использования DOM поменяется имя/тип/путь к полю и код так же придется переписывать. Чтобы из-за этого не сесть в лужу нужны тесты. Ну и за версией API хотя бы минимально следить.

В случае DOM можно решить набором if-ов. Да, коряво, но просто и понятно и работает. В данном случае, боюсь придется дописывать/переписывать фреймворк серилизации

Да ну бросьте) Все же гораздо проще. Если нужна поддержка двух разных версий: запиливаем TempHumDataV1 и TempHumDataV2. И обрабатываем их отдельно, достаем из разных таблиц, получаем через разные REST API. Если хочется извращений и стрельбы по ногам, то бросаем в конец данных признак версии - один байт с числом. Если он есть - v2(v3, etc.) Если там '}' - v1. Но повторю свою мысль. Это не очевидно, а значит признак плохого проектирования. Разные данные должны обрабатываться отдельно. Проектируете новый датчик, посылающий другие данные? Пусть он делает PUT https://api/v2/temp_hum.

Ну ок, видимо у вас задача действительно другого плана. Я просто немного побит жизнью, и сталкивался с запросами, которые на самом деле об одном и том же, но между которыми какое-нибудь мелкое но противное отличие все равно есть. И тут только руками разбирать остается, несмотря на то, что различных полей там было под 50

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

Это могут быть просто разные клиенты, которые шлют/получают запросы. К ним не придешь и не исправишь естественно

"Мир розовых пони" его нет

Исходя из текущих своих проектов

Версионность для формата представления данных/объекта - необходимость. Пример датчик на этапе проектирования - заложили поля

  • name //название

  • check //поверка

прошло время, добавилось:

  • check_by_superspeсial_lab_srvice // специфичная проверка

т.е. датчик(объект) остался тот же , но его описательная часть изменилась

т.е. в

struct SensorMetaInfo - добавили поля, возможно поменяли местами, а некоторые удалили. А заказчику ПО поставлено и оно шлет инфу в централизованное хранилище и endpoint -там один, надо по инфе разобраться какой десериализатор используется.

или в более простом случае - вы пишете сохранение настроек работы системы (датчиков) в файл. Накатываете обновление ПО, которое старый файл настроек должен прочитать и применить.

А потом приходит понимание, новое ПО глючное - накатываем старую версию, но файлы настроек уже обновлены - как реагировать? Тут хотя бы по более новой (не поддерживаемой) версии можно принять решение - сообщить об ошибке или попробовать дефолтную инициализацию.

Ну и вопрос общего плана. Представление float/double

как сериализуется ?

double d_inf = std::numeric_limits<double>::infinity();

double d_nan = std::numeric_limits<double>::nan();

json это

"dfdsf" - string

1 - int

1.0 - real

True/False - bool

{} - obj

[] - array

ничего другого нет.

---

nlohman::json - имеет возможность кастомной сериализации, читать adl_serializer

по полям классов структур можно ходить через boost_hana_adapt_struct (но там через макросы)

пример (из проекта) получения json из объекта (поля структуры предварительно обозначены через boost_hana макрос)

template <typename T>
static void makeJsonFromStruct(nlohmann::json &j, const T &t) {
  boost::hana::for_each(
      boost::hana::accessors<T>(), [&j, &t](const auto &meta) {
        try {
          j[boost::hana::to<const char *>(boost::hana::first(meta))] =
              boost::hana::second(meta)(t);
        }
        catch (std::exception &e) {
          LOG_ERROR << "Can`t convert obj with type "
                    << boost::core::demangle(typeid(T).name())
                    << " exception: " << e.what();
        }
      });
}

Вы описываете проблему:

обновили ПО и конфиг -> забаговано, надо откатывать -> откатываете только ПО оставив конфиг.

Верно? Это звучит не как проблема сериализации/десериализации, а как проблема с развертыванием.

да это не проблема сериализатора, это реакция на комент

Если нужна поддержка двух разных версий: запиливаем TempHumDataV1 и TempHumDataV2. И обрабатываем их отдельно, достаем из разных таблиц, получаем через разные REST API

Объект (датчик, класс) остается один и тот же формат данных - меняется - и условное требование поля int version - вполне разумное.

Реализацию в чем-то подобную вашей писали в 2019 в своем проекте.

Но потом перешли на использование boost_hana + nlohman::json

ну и повторюсь, как сериализуются?

double d_inf = std::numeric_limits<double>::infinity();

double d_nan = std::::nan();

Ваше мнение - Объект один и тот же, а данные отличаются. Мое мнение другое - разным данным разные объекты и наоборот, иначе это не сериализация. Это натягивание данных одного объекта, на другой. Тут естественно только руками.

А как вы хотите сериализовать? Как поток байт? Не вижу проблем, все будет скопировано бит в бит и обратно. Как строка? Поддержки таких значений у меня нет, но не забывайте это опенсорс проект одного человека. Если сделаете пулл реквест с их поддержкой я обещаю, сто вмерджу его первым.

Кроме того. Вы уверены, что сериализовать числа с плавающей точкой в json т.е. как строку это хорошая идея?

то что выложили и поделились наработками - прекрасно.

Через подобные реализации проходит куча команд разработки плюсовиков с разной степенью велосипедности.

Если ваш подход закрывает потребности вашего проекта - супер.

Но в моем случае (по требованиям)

smthObj smth {smthObj::fromJson(json)}

должно вернуть валидный объект, с максимальной инициализацией своих полей из любой версии сериализации.

Обмен посредством json/xml api - это необходимость и тогда double -> string - необходимость. std::nan() infinity() - валидные значения для вещественных чисел - и это надо передать через json.

---

тут был комментарий

Ну, я лет 15 любил плюсы, а потом как-то в итоге так пригорело, что я их выкинул из своей профессиональной жизни, и в итоге всё стало очень хорошо.

вот к этому приходят многие, у меня это python - совсем другая крайность, но и задачи поменялись и делать их на плюсах - себе дороже.

Пока не появится сериализация на уровне языка в стандарте - все тлен и костыли ограничения (не в этой моей жизни, а в реинкарнацию и бессмертность души я не верю).

---

За статью - спасибо

Вообще-то, полноценная рефлексия будет включена в будущий стандарт C++(25??)

У меня где-то валялся драфт.

++

Рефлексия была в ROOT (https://root.cern.ch/), с момента его появления в 1995 году.

Она используется для сериализации ROOT I/O (включая Sheema Evolution)

и в C++ интерпретаторе.

Не вижу ничего похожего в будущем стандарте.

Вы ссылаетесь на 23-й стандарт, а она появится не раньше 25-го.
Краткий обзор был недавно habr.com/en/post/598981

У меня на этот счет несколько важных мыслей:

  • Сейчас 2022, рефлексия появится в 26, не уж то мне страдать еще 4 года?

  • Можно примерно предполагать только о том, что войдет в следующий стандарт(С++23) и то не факт, это все еще черновик.

  • На С++26 даже черновика нет, то о чем говорите вы, к сожалению, всего лишь PROPOSAL и нет никаких гарантий, что оно войдет в С++26. Есть гарантия, что оно НЕ войдет в С++23.

  • Не факт что новый стандарт будет в 26, увы.

  • Даже после утверждения стандарта, в компиляторах это появится не сразу.

  • Даже после появления в компиляторах как скоро production код будет портирован на С++26?

Я же вам предлагаю готовое, быстрое решение уже сейчас. Нужен С++17, который +/- довольно широко распространен. Если воспользоваться С++20 и растыкать по проекту constexpr, то часть логики уйдет в compile time.

Повторюсь. Вот прямо сейчас.

Да, а networking ts должен был войти в с++20… Наличие драфта мало о чем говорит
Странно, что в комментах не написали о github.com/boostorg/pfr. Сам не пробовал, но вроде с его помощью можно также писать такие сериализаторы для простых типов. Причем в compile time. И это работает с текущим стандартом, не нужны никакие внешние генераторы, которые никто себе ставить не будет.

Большое спасибо что вспомнили про pfr! @antoshkka сделал огромную и очень крутую работу, но у такого подхода довольно много ограничений, которые я хотел обойти. Например у меня есть поддержка алиасов, можно анализировать все что угодно, в том числе печатать в консоль для дебага const и static. Вначале я хотел воспользоваться pfr, но в последствии отказался от него.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории