I want to flatten a tree type to flat type. Example:
typedef std::tuple<int,std::tuple<int,long>,int> tup;
Flat<tup>::type=>std::tuple<int,int,long,int>
I use:
template<typename T>
struct Flat
{
using type=T;
};
template <template <typename ...> class C,typename...ARGS>
struct Flat<C<ARGS...> >
{
using type=C<ARGS...>;
};
template <template <typename ...> class C,typename ...ARGS0,typename...ARGS1,typename ...ARGS2>
struct Flat<C<ARGS0...,C<ARGS1...>,ARGS2...> >
:Flat<C<ARGS0...,ARGS1...,ARGS2...> >
{
};
void test(){
typedef std::tuple<int,std::tuple<int,long>,int> tup;
static_assert(std::is_same<typename Flat<tup>::type,std::tuple<int,int,long,int> >::value,"");
}
but I get std::tuple<int,std::tuple<int,long>,int>
still... I use gcc 4.8.1
First the explanation of how this works:
This is a forward declaration so that we can use flatten
with a single type.
template<class T>
struct flatten;
Then we specialize flatten
to accept any template type.
C
is a template template parameter so we can get the type of the surrounding template.
E.g. if you would use flatten
with the parameter std::tuple<int, double>
then C
would be std::tuple
.
FArgs is used to retrieve the parameter list of the template passed. In the case of the example I just referred to, it would be int, double
template< template< typename ... > class C, typename ...FArgs>
struct flatten<C<FArgs...>>
The rest of the flatten
implementation now has always access to C
the wrapper type and FArgs
the list of arguments.
Forward declaration for append
which appends the parameter B
to the list of items in parameter Target
template< typename Target, typename B >
struct append;
The specialization of append
which retrieves the list of types from Target
(see above).
Note that C
here still is std::tuple
.
So we're adding one more item to the list and creating a new type by applying the previous list of arguments Args1
and then adding T
template< typename ...Args1, typename T >
struct append<C<Args1...>, T> {
using type = C<Args1..., T>;
};
Forward declaration of inner
and inner2
inner
is used to iterate through the list of arguments provided and applies inner2
on each of them, which in turn again applies inner
on the type, if the type is another template with parameters. This template must be the matched template C
template< typename Target, typename ...Args >
struct inner;
template< typename Target, typename T >
struct inner2;
This inner2
handles all C
template types and recursively iterates through its parameter list with the help of inner
template< typename Target, typename ...Args>
struct inner2<Target, C<Args...>> {
using type = typename inner<Target, Args...>::type;
};
If this inner2
specialization is used, the type will be appended to the end result.
template< typename Target, typename T >
struct inner2 {
using type = typename append<Target, T>::type;
};
Recurses through the template parameters by inheriting and applying on each parameter inner2
template< typename Target, typename T, typename ...Args>
struct inner<Target, T, Args...>
: inner<typename inner2<Target, T>::type, Args...>
{};
End condition for the recursion.
template< typename Target, typename T >
struct inner<Target, T>
{
using type = typename inner2<Target, T>::type;
};
Here the whole thing from above gets triggered, C<>
specifies the Target
and FArgs
are the arguments extracted by the flatten
specialization.
using type = typename inner<C<>, FArgs...>::type;
};
And here the whole code:
#include <tuple>
#include <string>
template<class T>
struct flatten;
template< template< typename ... > class C, typename ...FArgs>
struct flatten<C<FArgs...>>
{
template< typename Target, typename ...B >
struct append;
template< typename ...Args1, typename T >
struct append<C<Args1...>, T> {
using type = C<Args1..., T>;
};
template< typename Target, typename ...Args >
struct inner;
template< typename Target, typename T >
struct inner2;
template< typename Target, typename ...Args>
struct inner2<Target, C<Args...>> {
using type = typename inner<Target, Args...>::type;
};
template< typename Target, typename T >
struct inner2 {
using type = typename append<Target, T>::type;
};
template< typename Target, typename T, typename ...Args>
struct inner<Target, T, Args...>
: inner<typename inner2<Target, T>::type, Args...>
{};
template< typename Target, typename T >
struct inner<Target, T>
{
using type = typename inner2<Target, T>::type;
};
using type = typename inner<C<>, FArgs...>::type;
};
int main() {
typedef flatten<std::tuple<int, float, double>>::type first;
static_assert(std::is_same<first, std::tuple<int, float, double>>::value, "First not the same");
typedef flatten<std::tuple<int, std::tuple<float, double>>>::type second;
static_assert(std::is_same<second, std::tuple<int, float, double>>::value, "Second not the same");
typedef flatten<std::tuple<int, std::tuple<char const *>, std::tuple<std::tuple<float, int>, double>>>::type third;
static_assert(std::is_same<third, std::tuple<int, char const *, float, int, double>>::value, "Third not the same");
typedef flatten<std::tuple<int, std::tuple<std::tuple<std::tuple<std::tuple<char const *>>>>, std::tuple<std::tuple<float, int>, double>>>::type fourth;
static_assert(std::is_same<fourth, std::tuple<int, char const *, float, int, double>>::value, "Fourth not the same");
typedef flatten<std::tuple<int, std::tuple<std::tuple<std::tuple<std::tuple<std::string>>>>, std::tuple<std::tuple<float, int>, double>>>::type fifth;
static_assert(std::is_same<fifth, std::tuple<int, std::string, float, int, double>>::value, "Fifth not the same");
}
Edit: I have rewritten the implementation to make it more readable and shorter (inspired by @DyP) Edit2: Explained the code
Here is my stab at this. I tried to document what is going on to make it clear:
We start with Flatten. It takes a type. We will specialize it below:
template<typename T>
struct Flatten;
Here is our workhorse. Takes Src, and flattens its contents and appends it to Dest:
template<typename Dest, typename Src>
struct Flatten_append;
An empty right hand side pack means return left hand side:
template<template<typename...>class Pack, typename... LHS>
struct Flatten_append< Pack<LHS...>, Pack<> > {
typedef Pack<LHS...> type;
};
A right hand side whose first argument is a Pack<...> should be flattened before processing:
template<template<typename...>class Pack, typename... LHS, typename... RHS0, typename... RHSrest>
struct Flatten_append< Pack<LHS...>, Pack<Pack<RHS0...>, RHSrest... > >:
Flatten_append< Pack<LHS...>, Pack< RHS0..., RHSrest... > >
{};
Otherwise, a non-empty right hand side pack should have its first element moved over to the left hand side: (this will match weaker than the above, as it is less specialized)
template<template<typename...>class Pack, typename... LHS, typename RHS0, typename... RHSrest>
struct Flatten_append< Pack<LHS...>, Pack<RHS0, RHSrest... > >:
Flatten_append< Pack<LHS..., RHS0>, Pack< RHSrest... > >
{};
Implement Flatten in terms of Flatten_append to an empty Pack:
template<template<typename...>class Pack, typename... Ts>
struct Flatten< Pack<Ts...> >:Flatten_append< Pack<>, Pack<Ts...> > {};
The goal was to make it as clear what is going on as possible.
Now, you'll note that a downside to this design is that it will flatten any template
that only contains types. We probably want to pass in the pack that we want to flatten.
template<template<typename...>class Pack, typename T>
struct Flatten;
template<template<typename...>class Pack, typename Dest, typename Src>
struct Flatten_append;
template<template<typename...>class Pack, typename... Ts>
struct Flatten< Pack<Ts...> > : Flatten_append< Pack, Pack<>, Pack<Ts...> > {};
and then change each specialization of Flatten_append< blah, blah, blah >
to Flatten_append< Pack, blah, blah, blah >
.
This means you pass in the template
you want to flatten explicitly, and the code only flattens that template
.
In practice, this may not be needed, as the Pack
type gets deduced from the left hand side type passed in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With