LINUX.ORG.RU

Рефлексия в плюсах - обойти все поля структуры в рантайм?

 ,


2

3

Хочется объявить поля в структуре/классе так, что бы потом иметь возможность обойти их в рантайм/иметь возможность обращаться по имени в рантайм. Ограничения:

  1. стандарт не свежее с++-17

  2. всякие толстые сторонние либы а-ля буст не подходят

  3. задание полей должно быть максимально простым, желательно что бы каждое поле упоминалось не больше одного раза.

  4. необходимо иметь возможность довешивать к полям комментарии (можно статические) и потом иметь к ним доступ в рантайм.

Хочется че то такое:

struct A{
    BEGIN();  // некий макрос
    int PAR(x, 0, "число рыбов");
    std::array<double, 2> PAR(y, {0., 0.}, "координаты главрыбы");
    END();
};

но как это сделать фантазии не хватает (есть варианты, но они все ужасно костыльные).

Можно легко сделать что то вроде

struct A{
   double x = 0;
   std::array<double, 2> y = {0., 0.};
   A(){ TABLE(x, "число рыбов")(y, "координаты главрыбы"); }
};

но нарушается п.3.

Можно на худой конец сделать

struct A{
   int x = 0; ///< число рыбов
   std::array<double, 2> y = {0., 0.}; ///< координаты главрыбы
};

и перед сборкой обрабатывать это питоньей утилитой, генерить хидер с какой то оберткой и его инклюдить, но выглядит несколько радикально…

Как бы такое сделать Ъ? @annulen, @fsb4000, @monk, @bugfixer


UPD. Решил чуть подробнее расписать зачем это нужно и что должно выйти в итоге. Есть приложение (HPC) в котором есть вычислительное ядро на плюсах. В ядре есть класс Model со 100500 параметров (входных и выходных содержащих результаты расчета) которые имеют значения по умолчанию, но нужно мочь их менять через конфиги/аргументы командной строки, куда то записывать (в json) и т.д. Если забиндить ядро в питон (через SWIG) то это все делается довольно легко, но такой биндинг не всегда возможен. Хочется иметь аналогичную функциональность на чистых C++. Т.е. в C++ я изначально пишу:

class Model{
...
   double J = 1;      ///< exchange integral
   double T = 2;      ///< temperature
   double c = 0.1;    ///< concentration
   double dt = 1e-2;  ///< time step
   double t = 0;      ///< time
...
   
   void init();
   void calc();
};

Пускач в питоне

model = Model()
config(model)  # это функция из моей либы накатывающая параметры из командной строки
model.init()
while model.t<t_max: model.calc()

при запуске я могу писать что то вроде

./run.py T=4 dt=1e3

хочется мочь писать аналогичный пуска на плюсах.

Для этого необходимо и достаточно иметь в плюсах некую обертку для модели которая:

  1. может перечислить все параметры
  2. может вернуть значение любого параметра в виде строки
  3. может задать значение любого параметра из строки
  4. бонусом (то чего в питоне пока нет, но хочется и туда пробросить) - может вернуть комментарий к любому параметру
★★★★★

Последнее исправление: AntonI (всего исправлений: 7)

Вроде Полухин делал такое - https://github.com/apolukhin/magic_get

Говорят еще такая неплохо работает - https://github.com/felixguendling/cista

всякие толстые сторонние либы а-ля буст не подходят

Тяжко тебе придется)) boost::pfr хотя бы не интрузивное, можно на сторонних либах использовать.

необходимо иметь возможность довешивать к полям комментарии (можно статические) и потом иметь к ним доступ в рантайм.

Зачем тебе такое понадобилось?

PRN
()
Последнее исправление: PRN (всего исправлений: 1)
Ответ на: комментарий от PRN

Зачем тебе такое понадобилось?

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

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

AntonI ★★★★★
() автор топика
Последнее исправление: AntonI (всего исправлений: 3)
Ответ на: комментарий от anonymous

плюсую. Если ты хочешь что-нибудь типа OpenAPI, то принципиально важно генерировать код по схеме, а не схему по коду.

А следовательно генератор на питоне/яваскрипте будет куда как практичнее, чем магический рефлектоскоп

max_lapshin ★★★★★
()
#include <unordered_map>
#include <string>
#include <iostream>
using namespace std;

unordered_map<string, string> comments;
#define STR_(a) #a
#define STR(a) STR_(a)
#define FIELD(type, name, val, comment) type name {val}; static inline int name##12321 = []() {\
   comments[STR(CLASS_TYPE) "::" #name] = comment;\
   return 0;\
}();

struct S {
#define CLASS_TYPE S
   FIELD(int, i, 0, "hello");
   FIELD(double, length, 3, "world");
#undef CLASS_TYPE
};

struct Dog {
#define CLASS_TYPE Dog
   FIELD(int, height, 20, "any comment about dog height");
   FIELD(int, weight, 30, "dog weight");
#undef CLASS_TYPE
};

int main() {
   cout << comments["Dog::height"] << '\n'
      << comments["Dog::weight"] << '\n';
}

PS: Для инициализации массивов (много значение через запятую) макрос FIELD нужно переделать в variadic макрос, передавать инициализаторы после коммента, и раскрывать через VA_ARGS

kvpfs_2
()
Последнее исправление: kvpfs_2 (всего исправлений: 1)
Ответ на: комментарий от kvpfs_2

Статическая лямбда создается и сразу вызывается (в конструкторе?) потому что там () в конце? Сходу даже не рспарсил, и даже не знал что так можно;-)

Классно, спасибо!

AntonI ★★★★★
() автор топика
Ответ на: комментарий от her_s_gory

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

AntonI ★★★★★
() автор топика
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от AntonI

в конструкторе?

Во время инициализации глобальных переменных и статиков. Инициализируется статическая заглушка в классе (не оказывает влияние на размер структуры, она глобальная).

Это решает вопрос лишь с комментами к полям. Задачи из серии «Ну и что бы значения полей задавать через аргументы командной строки» посложнее будут (например, разные типы)

kvpfs_2
()
Последнее исправление: kvpfs_2 (всего исправлений: 1)
Ответ на: комментарий от AntonI

Ну да, в принципе. Можно в неком объекте с указателем на член держать tag с именем типа, а вызывающая сторона уже сама решает как с типом работать (как значение присвоить, что из команд строки передали). Вроде должно ехать, и не сложно должно быть, вроде

kvpfs_2
()
Ответ на: комментарий от kvpfs_2

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

Еще конечно хотелось бы таблицы статическими членами класса сделать, но вот тут я сходу споткнулся - что то там не то с последовательностью инициализации, сегофлтится…

AntonI ★★★★★
() автор топика
Ответ на: комментарий от kvpfs_2

Мелкие замечания

FIELD(int, i, 0, "hello")

Будут проблемы со сложными (шаблонными) типами map<int, array<char,10>>.

Будут проблемы со сложными, составными значениями.

static inline int name##12321

Место 12321 можно использовать макрос LINE для уникальности.

anonymous
()

X Macro еще в Quake использовались в сетевом коде, и до него еще много лет, так ничего и не поменялось. Все 4 пункта выполняются.

Из плюсов в сравнении с предложенными вариантами, можно лучше контролировать размещение информации о полях, и не выполнять в рантайме лишних действий.

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от AntonI

писать значения полей в базу

Есть вот такой ORM с кодогенерацией:

https://codesynthesis.com/products/odb/

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

Напрямую привязывать поля объектов к аргументам командной строки — на мой вкус очень странная идея. Но можно определять аргументы прямо в том файле, где они используются — вот такой штукой, например: https://abseil.io/docs/cpp/guides/flags

annulen ★★★★★
()

А что касается рефлексии в общем случае, то можно Qt’шным moc’ом воспользоваться — он всё сгенерирует, и можно будет потом в рантайме хоть вдоль эти поля обходить, хоть поперёк. Но придётся тащить QtCore и наследовать объекты от QObject (или QGadget, с ограниченной функциональностью, но без лишнего жира)

annulen ★★★★★
()
Ответ на: комментарий от AntonI

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

Если кратко, то можно так FOO((std::map<int, int>), map_var), взять аргумент в скобки. Тип из этого аргумента можно «отчистить» через шаблон, ну а сформировать строку обрезав скобки - как два пальца об асфальт.

kvpfs_2
()
Последнее исправление: kvpfs_2 (всего исправлений: 1)
Ответ на: комментарий от annulen

Почему напрямую? У меня есть класс с полями, я хочу некоторые из этих полей (какие известно только в рантайм) задать через аргументы командной строки. В питоне это делается тривиально, но хочется что то такого в плюсах с минимумом телодвижений.

AntonI ★★★★★
() автор топика
Ответ на: комментарий от LamerOk

Ну кстати в данном случае это (какая то своя внешняя утилита) не самый плохой вариант кмк. То что хочется можно видимо сделать чисто на плюсах сломав мосг, но внешней утилитой это делается гораздо проще.

Особенно если можно как то свои прагмы ввести безболезненно.

AntonI ★★★★★
() автор топика
Ответ на: комментарий от LamerOk

Можно, но хочется это сделать изящно с минимальными усилиями. Парсить ручками C++ код такое себе, хотя конечно можно магических комментариев навтыкать в стиле doxygen-а;-)

AntonI ★★★★★
() автор топика
Ответ на: комментарий от AntonI

Я пока вот над таким думаю

Запрещать сишные массивы и всё. Делать проверку в ран тайме (можно и компайл тайме), если в строке с именем есть хотя бы один ‘[’, то срабатывает assert. Если нужен массив, то брать std::array. Иначе хз как

kvpfs_2
()
Последнее исправление: kvpfs_2 (всего исправлений: 2)
Ответ на: комментарий от AntonI

Компилятся оно конечно будет, но вот отделить имя переменной от прямоуг скобок силами препрцессора? Я не знаю. В итоге в мепы пойдёт имя со скобками, вряд ли хочется искать смещение поля/коммент через i[2]

kvpfs_2
()
Последнее исправление: kvpfs_2 (всего исправлений: 1)
Ответ на: комментарий от kvpfs_2

Ну дык мапы обрабатываются потом уже плюсовым кодом.

Но меня захватила мысль о магических комментариях и внешней утилите… остановите меня!!!111;-)

AntonI ★★★★★
() автор топика
Ответ на: комментарий от hateyoufeel

Для меня большая.

ЛОР такой ЛОР… у Вас на работе когда Заказчик в ТЗ требования озвучивает Вы тоже начинаете обсуждать зачем да почему? Не нравится ТЗ не беритесь за задачу. Нет блин, надо на пустом месте срач развести.

AntonI ★★★★★
() автор топика
Ответ на: комментарий от AntonI

у Вас на работе когда Заказчик в ТЗ требования озвучивает Вы тоже начинаете обсуждать зачем да почему?

Ага. Потому что вполне возможно – хуже того, зачастую именно так и бывает! – что часть требований высосаны из жопы и на самом деле даже близко не являются требованиями.

hateyoufeel ★★★★★
()
Ответ на: комментарий от hateyoufeel

Ну молодцы, че. Сделали фсе по своему, а потом так опа - все переделывать, потому что машина на которой будет развертывание анально огорожена, софт собирается из сырцов, никаких дополнительных пакетов поставить низя, и конпелятор хорошо если не gcc4.8 а посвежее. И эта же машина и является сборочным сервером, упс? И вообще собрать будет кто то другой, если у него make не прошел - работа не принята.

Но ТЗ же адиеты пишут, которые ничего не знают и не понимают, да-да.

AntonI ★★★★★
() автор топика