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)

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

annulen вроде утверждал

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

Но я всё-таки больше симпатизирую магическим макросам, чем комментариям. Например, вот так в Qt сделано: есть макрос Q_SLOTS, который раскрывается в пустое место, но при этом служит маркером для кодогенератора. И есть ряд других макросов, у которых определения не пустые, но которые тоже выполняют роль маркеров. Код пишется всё-таки главным образом для человека, а не для машины, и человеку проще осознать, что перед ним «кодирующие последовательности», когда он видит макросы, чем когда он видит комментарии с каким-то странноватым содержанием. Плюс, при написании макросов, в отличие от комментариев, работает автодополнение, если нужно упоминать, например, названия типов или переменных.

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

Админ там есть. Не буду вдаваться в подробности

Это не админ. Я проработал в HPC много лет. Админил небольшой кластер на 20 вычислительных узлов с инфинибендом и люстрой. Элементарно базовые библиотеки линейной алгебры могут потребовать конфигурации на число потоков перед компиляцией.

Лично я не знаю как это делается.

И это нормально. Большая часть моей работы как админа кластера уходила на написание инструкций, общение с пользователями, проведение тренингов. А не на сборку ПО.

Да блин. Это работа админа кластера выдать компиляторы и библиотеки. И сказать как их легко использовать. Иначе нафига было тратить кучу бабла на железо?

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

Я утверждал, что магические комментарии в латехе это плохо) Потому что там никто не ожидает магических комментариев.

Ок. Ну в конце концов латеховская конструкция как маркер для сторонней утилиты ничем не хуже.

человеку проще осознать, что перед ним «кодирующие последовательности», когда он видит макросы, чем когда он видит комментарии с каким-то странноватым содержанием.

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

Вот хочется что бы оно само играло с комментированием и кучненько результаты складывало;-) Причем если его не трогать, то что бы оно работало так как раскомменчено сейчас.

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

namespace config {
    enum class AlgFoo { A, B, C };
    constexpr AlgFoo alg_foo = AlgFoo::A;
};

// main.cpp

static inline int foo(int b, int c) {
    if constexpr (config::alg_foo == config::AlgFoo::A) {
        return b + c;
    } else if constexpr (config::alg_foo == config::AlgFoo::B) {
        return b - c;
    } else {
        return b * c;
    }
}

int alg(int b, int c) {
    int a = foo(b, c);
    return a;
}

Решает тоже, но средствами плюсов. А бинарный код вполне себе эффективный: https://godbolt.org/z/qYoKre5T9

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

У Вас 20 строчек, у меня 3. Сценарий использования этой штуки - на ходу поигрался с кодом и выкинул. Если для этого надо писать какие то енумы, вводить новые функции и т.д. и т.п. то это нафик не надо.

Да и само решение - я привел ОЧЕНЬ простой пример. В реальности в строчке используется десяток переменных, как их в функцию передавать? Да, можно передать весь контекст через лямбду - а если там не просто приcваивание? А если там вызов каких то методов с сайд-эффектами и еще черти чего? Например три варианта головы цикла?

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

у меня 3

Кто-то лукавит, ещё код скрипта и конфигов сборочной системы добавить надо. А тут всё уже готово :)

… А если там вызов каких то методов с сайд-эффектами и еще черти чего? …

Ну не будет отдельной функции, будет внутри одной большой функции портянка из if constexpr/else :)

А вот как использовать // @1 в такой ситуации?

Да и IDE вполне себе умеют рефракторинг по выносу куска кода в функцию.

Ну и это просто вариант. И явно на случай когда нужно что-то сохранить для истории.

З.Ы.: А так #if 1 рулит :)

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

Скрипты и пр пишутся один раз.

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

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

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

Не совсем очевидное поведение.

Но, например, в проекте PX4-Autopilot это используется: в параметрах, которые можно сконфигурировать через GUI (QGroundControl) через Doxygen-style комментарии добавляется мета-информация, типа: какие значения может принимать, в каком диапазоне и прочее. Используется потом в GUI что бы правильно выводить контроллы и контроллировать ввод. С другой стороны, это ещё и автоматически частью документации становится.

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

Скрипты и пр пишутся один раз.

… и потом отлаживается всю жизнь. По-настоящему write-only код может быть только если его написали под разовую задачу и больше не используют. Тем более кодогенератор, в котором баги или недостающие фичи в теории могут появляться с каждым новых входным файлом.

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

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

Кроме того это интересно:-)

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

Макросы - харам и говно.
Шаблонятина - тормоза при компиляции.

Единственный остающийся вариант - кодогенерация во время сборки.

Если сборочная среда позволяет такую роскошь (условный libclang-for-pip), то парсить си-шные объявления структур и генерировать всякие вспомогательные функции. Дополнительную инфу для вспомогательных функций можно разместить в комментариях у мемберов структур.

Если в сборочную среду парсер с++ не втащить, то структуры изначально описывать на каком-то промежуточном языке (например, по мотивам swagger), и из него генерировать и си-шные объявления структур, и вспомогательные функции. Но тут будет ваша ide с ума сходить, т.к. изначально у нее не будет объявлений тех структур, с которыми вы в коде работаете.

PS тред не читал

Manhunt ★★★★★
()
Последнее исправление: Manhunt (всего исправлений: 6)

Ну во-первых вы хотите какой-то бред. Но это не мое дело.

Во-вторых используя макросы можно аргумент макроса превратить в строку через #. Например `#define MY_MACRO(A) #A` будет заменять MY_MACRO(some) на «some». Из этого следует, что можно написать макрос, который создаст структуру и соответствующий код для run-time обхода. Похожая идея используется в макросах MySQL++ для сереализации / десериализации таблиц MySQL и структур в C++.

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

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

Когда мне понадобилось что-то подобное для C, я закодил питонячий скрипт, который на основании таблицы интересующих вариантов (нужны были еще и комбинации) и комментов с тегами в коде, тупо генерил в отдельных каталогах нужные модификации проекта, пытался их собрать и запустить

Если не прокатывало, к имени каталога добавлялся постфикс _FAILED 🙂

alx777 ★★
()