If you look at the member type of
template <typename Pack> struct flatten;
template <typename T, typename U, std::size_t A, std::size_t B, std::size_t C, typename V, std::size_t D, std::size_t E, typename W>
struct flatten<std::tuple<T,U, std::index_sequence<A,B,C>, V, std::index_sequence<D,E>, W>> {
template <typename, typename, std::size_t, std::size_t, std::size_t, typename, std::size_t, std::size_t, typename> struct S;
using type = S<T,U,A,B,C,V,D,E,W>;
};
is it possible to do this generically, thus flattening all types and non-types into a single flattened pack, as long as the non-types are wrapped within a type?
Here is my simple solution to the normal type of flattening, and I'm just wondering how to use this method to work the above generically.
template <typename T> struct identity { using type = T; };
template <typename...> struct merge;
template <typename Pack>
struct merge<Pack> : identity<Pack> {};
template <template <typename...> class P, typename... Ts, typename... Us>
struct merge<P<Ts...>, P<Us...>> : identity<P<Ts..., Us...>> {};
template <typename First, typename... Rest>
struct merge<First, Rest...> : merge<First, typename merge<Rest...>::type> {};
template <typename T, template <typename...> class P> struct flatten_h : identity<P<T>> {};
template <template <typename...> class P, typename... Ts>
struct flatten_h<P<Ts...>, P> : merge<typename flatten_h<Ts, P>::type...> {};
template <typename Pack> struct flatten;
template <template <typename...> class P, typename... Ts>
struct flatten<P<Ts...>> : flatten_h<P<Ts...>, P> {};
// Testing
#include <type_traits>
template <typename...> struct P;
int main() {
static_assert(std::is_same<
flatten<P<int, char, long, P<double, bool>, int>>::type,
P<int, char, long, double, bool, int>
>::value);
static_assert(std::is_same<
flatten<P<int, P<bool, bool>, long, P<double, P<char, P<long>>>, int>>::type,
P<int, bool, bool, long, double, char, long, int>
>::value);
}
I think C++20 should allow auto...
(or some other keyword) to indicate both types and non-type values for template arguments.
The task becomes feasible if each non-type parameter (unpacked from, say, an std::index_sequence
) can be wrapped in its own std::integral_constant
. Then there are only type template parameters at the level where the flattening occurs and one may use a simple type container like template<class...> struct Types {};
.
// upper version for shorter type names; lower version for showing types
template<auto v> struct Val : std::integral_constant<decltype(v), v> {};
//template<auto v> using Val = std::integral_constant<decltype(v), v>;
// NOTE: bug(?) in GCC 7.2 which maps size_t(0) to false and size_t(1) to true
template<class I, I... is>
auto flatten(Type< std::integer_sequence<I, is...> >) {
return Flattened<Val<is>...>{};
}
Below and in a live demo you find a full working example with that restriction (and the bug(?) mentioned in the comment).
I chose to specify all types which should be flattened. Alternatively, one may also blindly unpack "everything reasonable" using various template template arguments of the form template<template<auto, class...> class ToFlatten>
etc.
#include <iostream>
#include <tuple>
#include <utility>
// upper version for shorter type names; lower version for showing types
template<auto v> struct Val : std::integral_constant<decltype(v), v> {};
//template<auto v> using Val = std::integral_constant<decltype(v), v>;
// NOTE: bug(?) in GCC 7.2 which maps size_t(0) to false and size_t(1) to true
template<size_t... is, class F>
constexpr decltype(auto) indexer_impl(std::index_sequence<is...>, F f) {
return f(Val<is>{}...);
}
template<size_t size, class F>
constexpr decltype(auto) indexer(F f) {
return indexer_impl(std::make_index_sequence<size>{}, f);
}
////////////////////////////////////////////////////////////////////////////////
template<class T_>
struct Type {};
template<class... Ts>
struct Types {
static constexpr auto size = Val<sizeof...(Ts)>{};
using Tuple = std::tuple<Ts...>;
};
template<size_t i, class T>
using at_t = std::tuple_element_t<i, typename T::Tuple>;
////////////////////////////////////////////////////////////////////////////////
template<class...> struct Flattened;
template<class I, I... is> using int_seq = std::integer_sequence<I, is...>;
// specify which types are allowed in a flat type container
template<class T> struct is_flat : Val<true> {};
template<class I, I... is> struct is_flat< int_seq<I, is...> > : Val<false> {};
template<class... Ts> struct is_flat< Types<Ts...> > : Val<false> {};
template<class... Ts> struct is_flat< Flattened<Ts...> > : Val<false> {};
// check if a type is an instantiation of `template<class...> struct Flattened`
template<class T> struct is_flattened : Val<false> {};
template<class... Ts> struct is_flattened<Flattened<Ts...>> : Val<true> {};
// specific type container which guarantees to contain `is_flat` types only
template<class... Ts> struct Flattened : Types<Ts...> {
static_assert((... && is_flat<Ts>{}));
};
////////////////////////////////////////////////////////////////////////////////
namespace internal {
auto merge() {
return Flattened<>{};
}
template<class... Ts>
auto merge(Flattened<Ts...> done) {
return done;
}
template<class... Ts, class... Us>
auto merge(Flattened<Ts...>, Flattened<Us...>) {
return Flattened<Ts..., Us...>{};
}
// merge more than two args: attempt to avoid linear recursion: is it better?
template<class... Ts, class... Fs>
auto merge(Flattened<Ts...>, Fs...) {
static_assert((... && is_flattened<Fs>{}));
using T = Types<Flattened<Ts...>, Fs...>;
// group the Flattened args into two halves
constexpr size_t N = T::size;
constexpr size_t N0 = N/2u;
constexpr size_t N1 = N-N0;
auto h0 = indexer<N0>([] (auto... is) { return merge(at_t<is, T>{}...); });
auto h1 = indexer<N1>([] (auto... is) { return merge(at_t<N0+is, T>{}...); });
return merge(h0, h1);
}
template<class T>
auto flatten(Type<T>) {
static_assert(is_flat<T>{});
return Flattened<T>{};
}
template<class I, I... is>
auto flatten(Type< std::integer_sequence<I, is...> >) {
return Flattened<Val<is>...>{};
}
template<class... Ts>
auto flatten(Type< Types<Ts...> >) {
return merge(internal::flatten(Type<Ts>{})...);
}
}// internal
template<class... Ts>
auto flatten(Types<Ts...>) {
return internal::merge(internal::flatten(Type<Ts>{})...);
}
////////////////////////////////////////////////////////////////////////////////
template<class T>
void inspect() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
struct Custom {};
int main() {
auto foo = Types<
Types<int, char, long>,
Val<7>,
Types<double, Val<5>, float, Types<unsigned, Types<Custom, Types<char>, int>>, std::make_index_sequence<4u>>,
std::index_sequence<5u,19u,4u>,
Types<>,
Val<8>
>{};
auto bar = flatten(foo);
inspect<decltype(bar)>();
return 0;
}
Output:
void inspect() [with T = Flattened<int, char, long int, Val<7>, double, Val<5>, float, unsigned int, Custom, char, int, Val<false>, Val<true>, Val<2>, Val<3>, Val<5>, Val<19>, Val<4>, Val<8> >]
Output with longer type names:
void inspect() [with T = Flattened<int, char, long int, std::integral_constant<int, 7>, double, std::integral_constant<int, 5>, float, unsigned int, Custom, char, int, std::integral_constant<bool, false>, std::integral_constant<bool, true>, std::integral_constant<long unsigned int, 2>, std::integral_constant<long unsigned int, 3>, std::integral_constant<int, 5>, std::integral_constant<long unsigned int, 19>, std::integral_constant<long unsigned int, 4>, std::integral_constant<int, 8> >]