Die Aufgabe wird durchführbar, wenn jeder Nicht-Typ-Parameter (entpackt z. B. von std::index_sequence
) in seine eigene std::integral_constant
verpackt werden kann. Dann gibt es nur Typvorlagenparameter auf der Ebene, auf der die Abflachung auftritt, und man kann einen einfachen Typcontainer wie template<class...> struct Types {};
verwenden.
// 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 und in a live demo Sie ein vollständiges Arbeitsbeispiel mit dieser Beschränkung finden (und der Bug (?) Im Kommentar erwähnt).
Ich wählte, um alle Typen anzugeben, die abgeflacht werden sollten. Alternativ kann man auch auspacken blind "alles in Ordnung" verschiedene Template-Template-Argumente der Form unter Verwendung von template<template<auto, class...> class ToFlatten>
usw.
#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;
}
Ausgang:
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> >]
Ausgabe mit mehr Typnamen:
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> >]