четверг, 24 марта 2016 г.

Сериализация в C++ 3. Шаг в сторону

Если приглядеться внимательно к тому, что я писал в предыдущих двух статьях про сериализацию (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, унеся принятие части решений на этап исполнения, но получив более качественный код. Если дойдут руки, то я попрофилирую оба решения и посмотрю стоит ли игра с уродливыми рекурсивными шаблонами свеч.

Продолжение следует...

Комментариев нет: