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)
Ответ на: комментарий от zurg

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

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

Вроде Полухин делал такое - 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)
Ответ на: комментарий от kvpfs_2

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

AntonI ★★★★★
() автор топика
Ответ на: комментарий от 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
()
Ответ на: комментарий от LamerOk

__COUNTER__

Нестандарт, лучше использовать __LINE__

anonymous
()

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

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

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

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

Да, похоже на то. Странно, что за столько лет не вкрутили способ задавать разделитель в макросах

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

Разве будет работать? Препроцессор тупо считает запятые. Тут такое пройдет (костыль, конечно):

using simple_name = array<char,10>>;
FIELD(simple_name, ...)
kvpfs_2
()
Ответ на: комментарий от kvpfs_2

за столько лет не вкрутили способ задавать разделитель в макросах

WONTFIX, ой, NOTABUG

Официальный C++ всеми силами борется с макросами

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

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

AntonI ★★★★★
() автор топика
Ответ на: комментарий от 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 ★★★★★
() автор топика
Ответ на: комментарий от annulen

Не, Qt тут вообще не вариант.

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

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

#define FIELD(name, comment, ...) name = {__VA_ARGS__}; ...

struct S {	
	int FIELD(i[2], "hello", 0, 0);
};

Имя класса можно выдернуть через __PRETTY_FUNCTION__

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

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

База у нас имени себя, специфическая, не реляционная.

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

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

Там обычная кодогенерация внешней по отношению к проекту утилитой.

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

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

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

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

какая то своя внешняя утилита не самый плохой вариант

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

LamerOk ★★★★★
()
Ответ на: комментарий от 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 ★★★★★
() автор топика

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

А зачем вот это? Я не очень понимаю любовь плюсистов к старым стандартам, если можно использовать новые.

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

Подписался на годный старт плюсосрача.

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

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

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

Пацаны! Тащите канделябр!

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

А что касается рефлексии в общем случае, то можно Qt’шным moc’ом воспользоваться

Выключите свет! Они лезут на свет!

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

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

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

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

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

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

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

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

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

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