Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

template metaprogramming :why flat type is failure

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

like image 815
fe263 Avatar asked Jan 06 '14 08:01

fe263


2 Answers

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

like image 97
Vinzenz Avatar answered Nov 17 '22 12:11

Vinzenz


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.

like image 34
Yakk - Adam Nevraumont Avatar answered Nov 17 '22 13:11

Yakk - Adam Nevraumont