Предисловие. Я пытаюсь получить более глубокое понимание метапрограммирования шаблонов C ++, и кажется, что я застрял … Я пишу библиотеку, которую мы будем использовать для сериализации двоичных данных [de]. Ожидаемая структура распаковываемых данных известна в определенной степени, и мне кажется разумным использовать эти знания для (1) проверки данных (2) пропуска ненужных частей и (3) распаковки данных непосредственно в структуры, известные во время компиляции — как для избежания ненужного копирования, так и для того, чтобы код клиента выглядел чище.
Так, например, я хочу реализовать функцию, которая будет распаковывать массив (массивы могут содержать разнородные данные, как в JSON). Для простоты предположим, что массив имеет фиксированный размер и не имеет вложенности.
Актуальная проблема Я хочу написать функцию, которая будет принимать входной буфер, содержащий сериализованные данные (или поток — это не имеет значения в нашем контексте) и std::tuple
, содержащий lvalues для вывода (пакет параметров — худшая альтернатива, потому что в конечном итоге мне придется иметь дело с вложением). Итак, сначала я должен проверить, подходят ли все типы в кортеже для распаковщика, и выдать соответствующее сообщение об ошибке, если это не так.
Так что код выглядит примерно так:
template<typename T>
struct is_integral_lvalue : std::integral_constant<bool,
std::is_lvalue_reference<T>::value &&
std::is_integral<T>::value &&
(sizeof(T) == 4 || sizeof(T) == 8)>
{
};
/* ... */
template<typename TInputBuffer, typename... TDest>
static TRet unpack_int_tuple(TInputBuffer src_buf, std::tuple<TDest...> &&dest) noexcept(is_noexcept)
{
static_assert(typelist::all_are<is_integral_lvalue, TDest...>::value,
"All types in a tuple must be integral lvalue-references");
/* do unpacking */
}
Состояние is_integral_constant
может быть несколько произвольным. Вот почему желательно, чтобы all_are
Шаблон может использовать любой унарный предикат. Вопрос: что я должен написать в typelist::all_are
(и, возможно, что я должен исправить в приведенном выше коде, чтобы можно было написать такой all_are
)?
Рабочий пример был бы идеальным, конечно, но я буду признателен за общие идеи / советы, если они будут полезны.
Ограничения Моя цель — не просто реализовать эту функцию, но понять, как она работает (решение «просто использовать boost :: mpl» или «boost :: hana» не подходит). Чем меньше несвязанных вещей мы используем, тем лучше. Предпочтительно код должен быть на C ++ 11 (мы еще не готовы использовать C ++ 1y / GCC 4.9 на производстве). Я также надеюсь, что можно избежать использования макросов препроцессора.
Некоторые вещи, которые я гуглил. Boost.MPL, конечно, можно использовать, но он большой, он использует медленные рекурсивные шаблоны (вместо вариационных) и трудно понять, что «под капотом». К сожалению, Boost :: hana основан на полиморфных лямбдах, которые не попали в C ++ 11. Я видел это https://github.com/Manu343726/Turbo библиотека, но кажется, что для ее использования требуется слишком много изменений в коде (чтобы обернуть почти каждый тип в свои адаптеры). Он также использует такие вещи, как ленивая оценка (при расширении шаблонов) — он здесь не нужен и сделает код более трудным для чтения.
Эта библиотека https://github.com/ldionne/mpl11 является почти Что мне нужно. Проблема, опять же, с обертками: and_
реализован как частный случай foldr
метафункция (которая развернута для лучшей производительности во время компиляции). И все они используют подъем метафункций, лень и так далее, что делает его действительно трудным для понимания (за исключением, может быть, опытных программистов на функциональном языке). Так что в принципе мне достаточно объяснить, как пропустить все эти очень обобщенные и сложные техники и написать то же самое and_
шаблон, но более простым способом (для более конкретного использования).
До C ++ 17 и сложить выражения давай, прямая реализация all_of
является:
// base case; actually only used for empty pack
template<bool... values>
struct all_of : std::true_type {};
// if first is true, check the rest
template<bool... values>
struct all_of<true, values...> : all_of<values...> {};
// if first is false, the whole thing is false
template<bool... values>
struct all_of<false, values...> : std::false_type {};
В этом случае использование становится
static_assert(all_of<is_integral_lvalue<TDest>::value...>::value,
"All types in a tuple must be integral lvalue-references");
Если вы хотите сохранить свой оригинальный синтаксис, это легко с псевдонимом:
template<template <class> class T, class... U>
using all_are = all_of<T<U>::value...>;
Кроме того, есть ошибка в вашем is_integral_lvalue
— ссылочный тип не является целочисленным. Ваш is_integral
проверка должна быть сделана на typename remove_reference<T>::type
а не просто T
,
редактироватьВот более простая реализация all_of
любезно предоставлено @Columbo:
template<bool...> struct bool_pack;
template<bool...values> struct all_of
: std::is_same<bool_pack<values..., true>, bool_pack<true, values...>>{};