All streams
Search
Write a publication
Pull to refresh

Comments 45

Спасибо, было действительно интересно почитать! Активно интересуюсь темой рефлексии в 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++. Такой "жёлтый" аргумент сразу кидает тень на всю статью. Советую пересмотреть эту часть.

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

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

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

UFO landed and left these words here

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

UFO landed and left these words here

Кроме 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

UFO landed and left these words here

Есть

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, но в последствии отказался от него.

Не совсем понял, умеет ли этот сериализатор работать с полиморфизмом?

struct Base {
    virtual int data() {
        return 1;
    }
};

struct Derived : Base {
    int data() override {
        return 2;
    }
};

struct Client {
    Base* data;
};

void test() {
    Client client;
    client.data = new Derived;
    auto text = serialize(client);
    Client* test = deserialize(text);
    std::cout << test->data->data() << std::endl;
}
Сериализует и десериализует Derived или Base?
Sign up to leave a comment.

Articles