Если приглядеться внимательно к тому, что я писал в предыдущих двух статьях про сериализацию (1, 2), то несложно заметить, что можно через такой же механизм привязать к методу или полю класса информацию любого типа. Например, можно привязать к метду класса максимальное допустимое время работы этого самого метода. Представьте себе threadpool у которого есть отдельная специализация метода run со следующей сигнатурой:
template<typename C, typename R, typename... A> future<R> run(C&& c, member_ptr<C, R(A...)> method, A&&... a);и строгой гарантией, что если к переданному методу прикреплена описанная выше метаинформация, то по истечению таймаута future будет содержать исключение какого-нибудь типа timeout_error если метод всё ещё не завершился.
Для этого заведём класс аналогичный member_info из первой статьи, но параметризованный дополнительным типом, который мы назовём MetaTag. Задача этого типа определять семантику прикреплённой метаинфорамции. Ведь просто время может значить всё что угодно, а вот тэг timeout очень чётко говорит, что это за время мы прицепили к методу. Единственное требование которое мы собираемся предъявлять тэгам это наличие в нём типа type, которое задаёт тип присоеденяемой метаинформации.
template<typename MetaTag, typename TM, Member<TM> M> struct member_metainf { static const typename MetaTag::type value; static constexpr Member<TM> member = M; };
Где Member<T> это эмуляция концепта посредством ранее описанного механизма, требующего в нужных нам местах указатель на поле кокого-нибудь класса или структуры. А вот назначение поля member станет ясно чуть ниже.
Теперь, наверно, стоит задуматься, как бы эту метаинформацию запрашивать. И это уже не совсем так тривиально как может сходу показаться. Ведь поле, информацию о котором мы хотим получать, почти во всех реальных сценариях использования будет браться из аргумента функции, который не может быть константным выражением и его нельзя просто запользовать в качестве шаблонного параметра. Несколько итераций с экcпериментами привели меня к реализации, которая мне самому не очень нравится, но которая максимально близка к идее static reflection. Мы заставляем компилятор генерить рутинный код за нас, не унося на этап исполнения ничего лишнего.
Итак, идея вносится в студию. То, что мы на самом деле хотим, это обойти список разных типов и для каждого из них проверить, является ли он требуемой метоинформацией для аргумента нашей функции и, если является, вернуть найденное значение. Оператор for, к несчастью, нам тут не поможет, ведь мы хотим итерироваться не то чтобы по значениям разных типов, мы хотим итерироваться просто по разным типам, чего C++ в отличии от D не умеет. Всё что у нас есть для решения данной задачи это рекурсивные шаблоны и молитвы всевышнему о том чтобы компилятор понял, что тут простой обход и заинлайнил всё как положено. Итак, смотрим на код (из которого становится ясно назначение дополнительного поля member в классе member_metainf):
namespace detail { // recursion termination template< typename MetaTag, typename M, typename MembersTuple, size_t Idx > typename std::enable_if< Idx >= std::tuple_size<MembersTuple>::value, const typename MetaTag::type* >::type get_metainf(Member<M> m) { return nullptr; } template< typename MetaTag, typename M, typename MembersTuple, size_t Id x> typename std::enable_if< Idx < std::tuple_size<MembersTuple>::value, const typename MetaTag::type* >::type get_metainf(Member<M> m) { if (m == std::tuple_element<Idx, MembersTuple>::type::member) return &std::tuple_element<Idx, MembersTuple>::type::value; return get_metainf<MetaTag, M, MembersTuple, Idx + 1>(m); } } // namespace detail template<typename MetaTag, typename M> const typename MetaTag::type* get_metainf(M m) { static_assert( is_member<M>::value, "M must be a pointer to member of some class"); using class_type = typename member_trait<M>::class_type; using members_tuple = MembersMetainf<MetaTag, class_type> return detail::get_metainf<MetaTag, M, members_tuple, 0>(m); }
Посмотрев на сей пример не очень красивого кода можно ненадолго задуматься, стоит ли решать задачу с таймаутами таким гнусным образом, а затем вернуться к проблеме сериализации.
Причиной, которая на самом деле побудила меня сделать этот шаг в сторону и написать код добавления произвольных метаданных к полю класса или структуры, является задача, с которой я однажды столкнулся. Мне нужно было распарсить XML выхлоп Redmine REST API и разложить информацию об отдельных задачах по полям структур. Решая её очень хотелось проаннотировать каждое поле моей структуры XPath выражением указыввающим на элемент документа откуда надо брать значение данного поля, а потом написать парсер который бы использовал их. Описанный в начале данной стати подход (сама структура member_metainf) позволяет реализовать эту идею, но для примера я взял таймауты, так как хотелось использовать метаинформацию с типом отличным от строк.
Другой важный момент, который можно найти в этой статье это код по поиску в списке полей класса, когда оный список является не списком значений, а списком типов. Данная задача возникает при написании функции десериализации с использованием информации о пропертях и полях из предыдущих двух статей. Чуть позже я хочу вернуться к этой проблеме и решить её через type-erasure, унеся принятие части решений на этап исполнения, но получив более качественный код. Если дойдут руки, то я попрофилирую оба решения и посмотрю стоит ли игра с уродливыми рекурсивными шаблонами свеч.
Комментариев нет:
Отправить комментарий