Suppose you’ve written a complex meta-function on sequences of integers and you want the exact same operation to work on packs of types. Instead of writing it all over again, you can use
template <template <typename...> class Op, typename... Packs> struct types_to_int;
where Op
is the meta-function on sequences that you’ve already written. Packs...
is converted to sequences by replacing the types in the packs with 0,1,2,3,…, and then operation is carried out. Then the resulting sequence will be converted back to a pack of types from a map that was constructed during the conversion. I don’t know of any better way to do this, and was wondering if anyone had any better ideas.
The following compiles with GCC 7.2 and clang 6.0. I’ve tested it with a simple merging operation on sequences for illustration:
#include <tuple> #include <type_traits> #include <utility> namespace utilities { template <typename Pack> struct pack_size; template <template <typename...> class P, typename... Ts> struct pack_size<P<Ts...>> : std::integral_constant<std::size_t, sizeof...(Ts)> { }; template <typename Pack, typename T> struct append; template <template <typename...> class P, typename... Ts, typename T> struct append<P<Ts...>, T> { using type = P<Ts..., T>; }; template <typename PackOfPacks, typename T, template <typename...> class, typename... CheckedPacks> struct append_to_packs; template <template <typename...> class P, typename T, template <typename...> class Q> struct append_to_packs<P<>, T, Q> { // Starting with a pack of zero packs. The only instance that Q is used at all. using type = P<Q<T>>; }; template <template <typename...> class P, typename Last, typename T, template <typename...> class Q, typename... CheckedPacks> struct append_to_packs<P<Last>, T, Q, CheckedPacks...> { using type = P<CheckedPacks..., typename append<Last, T>::type>; }; template <template <typename...> class P, typename First, typename... Rest, typename T, template <typename...> class Q, typename... CheckedPacks> struct append_to_packs<P<First, Rest...>, T, Q, CheckedPacks...> : append_to_packs<P<Rest...>, T, Q, CheckedPacks..., First> { }; // We append T to last pack only. template <typename... Packs> struct get_first_template; template <template <typename...> class P, typename... Ts, typename... Packs> struct get_first_template<P<Ts...>, Packs...> { template <typename... Us> using template_type = P<Us...>; }; template <typename Pack> struct to_tuple; template <template <typename...> class P, typename... Ts> struct to_tuple<P<Ts...>> { using type = std::tuple<Ts...>; }; } template <typename...> struct map; // A database storage of std::pair<T, std::integral_constant<std::size_t, N>>'s that assigns to every type a unique integral value. template <typename...> struct args_pack; // For containing extra (if any) argument types other than packs that the operator Op accepts. It can only be placed at the end of the Packs... arguments in types_to_int. If it is not placed at all, then it will be assumed that no extra argument types exist on top of Packs... template <typename Map, typename T, typename OriginalMap = Map> struct look_up_and_construct_map; // Map was fully searched, and T was not found, so add std::pair<T, std::integral_constant<std::size_t, utilities::pack_size<OriginalMap>::value>> to Map. template <typename T, typename OriginalMap> struct look_up_and_construct_map<map<>, T, OriginalMap> { static constexpr std::size_t value = utilities::pack_size<OriginalMap>::value; using map = typename utilities::append<OriginalMap, std::pair<T, std::integral_constant<std::size_t, value>>>::type; }; // T has been found. There is no need to change OriginalMap. template <typename T, std::size_t N, typename... Pairs, typename OriginalMap> struct look_up_and_construct_map<map<std::pair<T, std::integral_constant<std::size_t, N>>, Pairs...>, T, OriginalMap> { static constexpr std::size_t value = N; using map = OriginalMap; }; // T not found, so search the next pair in the map. template <typename First, typename... Rest, typename T, typename OriginalMap> struct look_up_and_construct_map<map<First, Rest...>, T, OriginalMap> : look_up_and_construct_map<map<Rest...>, T, OriginalMap> { }; // create_types_to_int_map (not only does it create the map, but for efficiency and turns the Packs... into packs of std::integral_constant<std::size_t, N>'s // at the same time (to avoid the O(N^2) process of making that conversion using the map after the map is made). template <typename Map, typename TransformedPacks, typename... Packs> struct create_types_to_int_map_h; // All types in every pack has been visited. template <typename Map, typename TransformedPacks, template <typename...> class P> struct create_types_to_int_map_h<Map, TransformedPacks, P<>> { using map = Map; using transformed_packs = TransformedPacks; }; // The last type in the current pack has been visited. So visit the next pack, while adding a new (empty) pack to TransformedPacks. template <typename Map, typename TransformedPacks, template <typename...> class P, typename... MorePacks> struct create_types_to_int_map_h<Map, TransformedPacks, P<>, MorePacks...> : create_types_to_int_map_h<Map, typename utilities::append<TransformedPacks, P<>>::type, MorePacks...> { }; template <typename Map, typename TransformedPacks, template <typename...> class P, typename First, typename... Rest, typename... MorePacks> struct create_types_to_int_map_h<Map, TransformedPacks, P<First, Rest...>, MorePacks...> { using looked_up = look_up_and_construct_map<Map, First>; static constexpr std::size_t int_value = looked_up::value; using continuation = create_types_to_int_map_h<typename looked_up::map, typename utilities::append_to_packs<TransformedPacks, std::integral_constant<std::size_t, int_value>, P>::type, P<Rest...>, MorePacks...>; using map = typename continuation::map; using transformed_packs = typename continuation::transformed_packs; }; template <typename... Packs> struct create_types_to_int_map : create_types_to_int_map_h<map<>, std::tuple<>, Packs...> { }; // to_sequences template <typename Pack> struct pack_to_sequence; template <template <typename...> class P, std::size_t... Is> struct pack_to_sequence<P<std::integral_constant<std::size_t, Is>...>> { using type = std::index_sequence<Is...>; }; template <typename Pack> struct to_sequences; template <template <typename...> class P, typename... Packs> struct to_sequences<P<Packs...>> { using type = P<typename pack_to_sequence<Packs>::type...>; }; // execute_op template <template <typename...> class Op, typename Pack, typename Args = args_pack<>> struct execute_op; template <template <typename...> class Op, template <typename...> class P, typename... Sequences, typename... Args> struct execute_op<Op, P<Sequences...>, args_pack<Args...>> { using type = typename Op<Sequences..., Args...>::type; }; // look_up_map (looking up a map to find the type that corresponds to a std::size_t value). template <typename Map, std::size_t N> struct look_up_map { using type = typename std::tuple_element_t<N, typename utilities::to_tuple<Map>::type>::first_type; // Since all pairs in the map were constructed with integral values 0,1,2,3,... }; // to_types (converting a sequence back to a pack of types using a constructed map) template <typename Map, typename Sequence, template <typename...> class P> struct to_types; template <typename Map, std::size_t... Is, template <typename...> class P> struct to_types<Map, std::index_sequence<Is...>, P> { using type = P<typename look_up_map<Map, Is>::type...>; }; template <template <typename...> class Op, typename... Packs> struct types_to_int { using meta = create_types_to_int_map<Packs...>; // To do: Check if args_pack is the last type in Packs... or not. If so, remove the args_pack from Packs... and pass Args... from args_pack<Args...> to 'execute<Op, sequences, Args...>' below. using map = typename meta::map; using sequences = typename to_sequences<typename meta::transformed_packs>::type; using result = typename execute_op<Op, sequences>::type; using type = typename to_types<map, result, utilities::get_first_template<Packs...>::template template_type>::type; }; // Testing template <typename... Sequences> struct merge; template <std::size_t... Is, std::size_t... Js> struct merge<std::index_sequence<Is...>, std::index_sequence<Js...>> { using type = std::index_sequence<Is..., Js...>; }; template <typename First, typename... Rest> struct merge<First, Rest...> : merge<First, typename merge<Rest...>::type> { }; template <typename...> struct P; template <typename...> struct Q; template <typename...> struct R; int main() { static_assert(std::is_same< types_to_int<merge, P<int, char, float, bool>, Q<char, int, double, int, float>, R<int, bool>>::type, P<int, char, float, bool, char, int, double, int, float, int, bool> >::value); }
Incidentally, I’ve also written an adaptor to handle the reverse problem (i.e. you’ve written an algorithm on packs of types and you want it to perform on sequences). The key idea I used was to convert all the integral template values to std::integral_constant
types, which can then be used on the metafunction and then after the output is extracted, the std::integral_constant
types are converted pack to the suitable pack of integral values.
#include <type_traits> #include <utility> #include <tuple> namespace detail { template <typename Pack> struct pack_size; template <template <typename...> class P, typename... Ts> struct pack_size<P<Ts...>> : std::integral_constant<std::size_t, sizeof...(Ts)> {}; template <typename Pack> struct get_template; template <template <typename...> class P, typename... Ts> struct get_template<P<Ts...>> { template <typename... Us> using templ_type = P<Us...>; }; } namespace utilities { struct nonesuch { }; template <typename Default, typename Void, template <typename...> class Op, typename... Args> struct detector { using value_t = std::false_type; using type = Default; }; template <typename Default, template <typename...> class Op, typename... Args> struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> { using value_t = std::true_type; using type = Op<Args...>; }; template <template <typename...> class Op, typename... Args> using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t; } // execute_and_search (see execute_and_search.cpp on how it is used in its own right). template <typename Pack, template <typename> class Op, template <typename...> class Get, typename Output, typename... Args> struct execute_and_search_h; template <bool, typename Pack, template <typename> class Op, template <typename...> class Get, typename Output, typename... Args> struct output; template <template <typename...> class P, typename First, typename... Rest, template <typename> class Op, template <typename...> class Get, typename... Output, typename... Args> struct output<true, P<First, Rest...>, Op, Get, std::tuple<Output...>, Args...> { using type = P<Output..., typename Op<First>::type, typename Op<Rest>::type...>; using special = First; }; template <template <typename...> class P, typename Last, template <typename> class Op, template <typename...> class Get, typename... Output, typename... Args> struct output<false, P<Last>, Op, Get, std::tuple<Output...>, Args...> { using type = P<Output..., typename Op<Last>::type>; using special = utilities::nonesuch; }; template <template <typename...> class P, typename First, typename... Rest, template <typename> class Op, template <typename...> class Get, typename... Output, typename... Args> struct output<false, P<First, Rest...>, Op, Get, std::tuple<Output...>, Args...> : execute_and_search_h<P<Rest...>, Op, Get, std::tuple<Output..., typename Op<First>::type>, Args...> { }; template <template <typename...> class P, typename First, typename... Rest, template <typename> class Op, template <typename...> class Get, typename Output, typename... Args> struct execute_and_search_h<P<First, Rest...>, Op, Get, Output, Args...> : output<utilities::is_detected<Get, Op<First>, Args...>::value, P<First, Rest...>, Op, Get, Output, Args...> { }; template <typename Pack, template <typename> class Op, template <typename...> class Get, typename... Args> struct execute_and_search : execute_and_search_h<Pack, Op, Get, std::tuple<>, Args...> { }; template <typename T, T...> struct values; // Converting packs of integral values to packs of std::integral_constant<T, I>'s where the I's are the integral values. template <typename T> struct to_integral_constants { using type = T; // No change if T is not a pack of integral values (or is not a pack at all). }; template <template <auto...> class Z, auto I, auto... Is> struct to_integral_constants<Z<I, Is...>> { using type = std::tuple<std::integral_constant<decltype(I), I>, std::integral_constant<decltype(I), Is>...>; using container = Z<>; }; // For packs like std:integer_sequence or std::index_sequence. template <typename T, template <typename U, U...> class Z, T I, T... Is> struct to_integral_constants<Z<T, I, Is...>> { using type = std::tuple<std::integral_constant<T, I>, std::integral_constant<T, Is>...>; using container = Z<T>; }; template <typename T, T I, T... Is> struct to_integral_constants<values<T, I, Is...>> { using type = values<T, I, Is...>; // Do not apply the previous specialization if the pack is values<T, I, Is...> used specifically so that it can be passed into int_to_types (as opposed to being an integral template value as it was originally meant to be). }; // Converting a pack of std::integral_constant<T, I>'s to a pack of the I's. template <typename T, typename Container, T... Is> struct fork; template <typename T, template <T...> class Z, T... Is> struct fork<T, Z<>, Is...> { using type = Z<Is...>; }; template <typename T, template <typename U, U...> class Z, T... Is> struct fork<T, Z<T>, Is...> { using type = Z<T, Is...>; // For example, containers like std::integer_sequence<T, Is...>. }; template <typename Pack, typename Container> struct to_int; template <template <typename...> class P, typename T, T... Is, typename Container> struct to_int<P<std::integral_constant<T, Is>...>, Container> : fork<T, Container, Is...> { }; template <template <typename...> class P, typename... Ts, typename Container> struct to_int<P<Ts...>, Container> { using type = P<typename to_int<Ts, Container>::type...>; }; template <typename T> using get_container = typename T::container; template <template <typename...> class Op, template <typename> class F, typename Pack> struct operator_on_pack; template <template <typename...> class Op, template <typename> class F, template <typename...> class P, typename... Ts> struct operator_on_pack<Op, F, P<Ts...>> { using type = typename Op<typename F<Ts>::type...>::type; }; // Finally, int_to_types. template <template <typename...> class Op, typename... Parameters> struct int_to_types { using i = execute_and_search<std::tuple<Parameters...>, to_integral_constants, get_container>; using output = typename operator_on_pack<Op, to_integral_constants, typename i::type>::type; using type = typename to_int<output, typename to_integral_constants<typename i::special>::container>::type; }; // Tests: // Testing with merge (even though merging integral packs is simple enough to write it out again, we shall test it anyway). template <typename... Packs> struct merge; template <typename Pack> struct merge<Pack> { using type = Pack; }; template <template <typename...> class P, typename... Ts, template <typename...> class Q, typename... Us> struct merge<P<Ts...>, Q<Us...>> { using type = P<Ts..., Us...>; }; template <typename First, typename... Rest> struct merge<First, Rest...> : merge<First, typename merge<Rest...>::type> { }; // Testing with all_sections (taken from all_sections.cpp) template <typename Pack, std::size_t SectionSize, std::size_t PackNumber, typename Sequence, typename = void> struct section; // Default void template which will force std::enable_if to evaluation which of two cases is true. template <typename Pack, std::size_t SectionSize, typename Sequence, template <typename...> class> struct all_sections_h; template <template <typename...> class P, typename... Ts, std::size_t SectionSize, std::size_t PackNumber, std::size_t... Is> struct section<P<Ts...>, SectionSize, PackNumber, std::index_sequence<Is...>, std::enable_if_t<(sizeof...(Ts) - PackNumber * SectionSize >= sizeof...(Is))>> { using type = P<std::tuple_element_t<PackNumber * SectionSize + Is, std::tuple<Ts...>>...>; }; template <template <typename...> class P, typename... Ts, std::size_t SectionSize, std::size_t PackNumber, std::size_t... Is> struct section<P<Ts...>, SectionSize, PackNumber, std::index_sequence<Is...>, std::enable_if_t<(sizeof...(Ts) - PackNumber * SectionSize < sizeof...(Is))>> : section<P<Ts...>, SectionSize, PackNumber, std::make_index_sequence<sizeof...(Ts) - PackNumber * SectionSize>> {}; template <typename Pack, std::size_t SectionSize, std::size_t... PackNumber, template <typename...> class P> struct all_sections_h<Pack, SectionSize, std::index_sequence<PackNumber...>, P> { using type = P< typename section<Pack, SectionSize, PackNumber, std::make_index_sequence<SectionSize>>::type... >; }; template <typename Pack, std::size_t SectionSize, template <typename...> class P = detail::get_template<Pack>::template templ_type> struct all_sections : all_sections_h<Pack, SectionSize, std::make_index_sequence<(detail::pack_size<Pack>::value + SectionSize - 1) / SectionSize>, P> {}; template <typename Pack, typename SectionSize, typename PackTemplate = Pack> struct all_sections_integral; template <typename Pack, std::size_t SectionSize, typename PackTemplate> struct all_sections_integral<Pack, values<std::size_t, SectionSize>, PackTemplate> : all_sections<Pack, SectionSize, detail::get_template<PackTemplate>::template templ_type> { }; template <int...> class Z; template <typename...> struct P; int main() { static_assert(std::is_same< int_to_types<merge, Z<4,6,1>, Z<0,3,8>, Z<11,13,9>, Z<5,2,7>>::type, Z<4,6,1,0,3,8,11,13,9,5,2,7> >::value); static_assert(std::is_same< int_to_types<all_sections_integral, std::index_sequence<4,6,1,0,3,8,11,13,9,5,2,7>, values<std::size_t, 3>>::type, std::tuple<std::index_sequence<4,6,1>, std::index_sequence<0,3,8>, std::index_sequence<11,13,9>, std::index_sequence<5,2,7>> >::value); static_assert(std::is_same< int_to_types<all_sections_integral, Z<4,6,1,0,3,8,11,13,9,5,2,7>, values<std::size_t, 3>, P<>>::type, P<Z<4,6,1>, Z<0,3,8>, Z<11,13,9>, Z<5,2,7>> >::value); }