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