Возможно ли то, что написанно в заголовке, реализовать в нашем реальном мире? Ведь все работающие с C++ знают, что программист должен страдать и вручную описывать сохранение и загрузку каждого поля структуры данных и делать эту чисто механическую работу, которую идеально мог бы сделать тупой автомат, дважды. Но нет, всё же есть способы, позволяющие сильно упростить сию рутину. И я говорю не о системах вида protobuf или apache thrift, которые, несомненно, хороши, а о чистом C++.
Давайте определимся, чего же хочется?! А хочется для каких-то типов писать код сериализации руками, учитывая нюансы хранимых в них данных. Например, сохраняя std::tm в JSON вы можете хотеть сохранить её в виде какого-то конкретного строкового представления даты, и это будет определяться конкретикой задачи, а не внутренним представлением этой самой весьма развесистой структуры. Но в большинстве случаев всё же хочется сказать компилятору: "От каждого поля структуры возьми его имя и запользуй оное в качестве JSON ключа, а значение читай и пиши в соответствии с тем, какого типа это поле".
К несчастью, SG7 весь выданный им бамбук выкурило, а ничего кроме std::experimental::source_location не придумало. Поэтому static reflection мы получим в лучшем варианте в виде TS в середине жизненного цикла 17 плюсов, если не позже. Придётся, как обычно, изворачиваться.
Для начала вспомним, что параметрами шаблона могут быть не только типы, но и значения, если последние вычислимы на этапи компиляции и различимы компилятором. И соотнесём эту информацию с тем, что указатель на поле класса являеся таким значением. Иными словами, мы можем написать такой код:
template<typename C, typename T> using member_ptr = T C::*; template<typename C, typename T, member_ptr<C, T> M> struct member_info { static const char* const name; static const T& get(const C& c) {return c.*M;} template<typename U> static void set(C& c, U&& val) {c.*M = std::forward<U>(val);} }; template<typename C> struct type_info; template<typename C> using members = type_info<C>::members; struct Point { int x; int y; }; template<> member_info<Point, int, &Point::x>::name = "x"; template<> member_info<Point, int, &Point::y>::name = "y"; template<> struct type_info<Point> { using members = std::tuple< member_info<Point, int, &Point::x>, member_info<Point, int, &Point::y> >; };
Теперь на этапе компиляции у нас есть тип, в котором хранятся как имена полей, так и функции доступа к этим полям. Обращаю внимание: до этапа исполнения доживают только строковые константы с именами полей (которые при любых раскладах до него доживут). Информация о структуре полностью внутри компилятора в виде типа, экземпляры которого мы вряд ли когда-нибудь захотим создавать. Таким образом, мы очень сильно приблизились к идее static reflection. Можно пометить member_info::name как constexpr, но это увеличит размер описания метоинформации о типе (хоть оно и закроется макросами, ибо сейчас оно занимает больше места, чем сам описание типа) и убъёт поддержку MSVS2013, на которую я вынужден целиться, при этом реальных бонусов в задаче о сериализации это не даст.
Следующий важный момент: код выше строчки "struct Point {" самодостаточен и может быть использован в коде сериализации, не требуя forward-деклараций, правильной последовательности включения заголовочных файлов или какой бы то ни было другой информации о тех структурах, которые будут размеченны вышеуказанными средствами. Единственное, что осталось под ковром, это джентельменское соглашение при специализации шаблона type_info всегда заводить в нём тип с именем members, который при этом будет std::tuple.
Ещё один важный момент. Я сознательно не стал уносить метаинформацию в саму структуру, удлинив при этом портянку кода на 3 строчки, ибо часто хочется размечать сторонние типы, а потом читать и писать их так же, как и свои. В этом варианте отделённая от класса метаинформация - это единственны правильный путь.
Позже я напишу
- как размечать типы, написанные людьми, больными "ООП головного мозга" (вместо структуры с открытыми полями портянка геттеров и сеттеров)
- как обобщить эту идею на добавление более сложной метоинформации, чем просто строковое имя поля
- как получить имя поля структуры без макросов и рукописного кода
- как правильно завернуть описание метоинформации в макросню
- как эту метаинформацию использовать в функции сериализации
Комментариев нет:
Отправить комментарий