template template parameter expansion for variadic templates

丶灬走出姿态 提交于 2021-02-06 08:56:15

问题


I recently learned about the existence of template template parameters and was now wondering if something like this would be possible:

template<template<class... > class Container, typename... args>
struct ContainerTemplate
{
    using container = std::tuple<Container<args...>...>;
};

what i want is a template that gets a Container or some other template class as a template template parameter and then expands the rest of the template arguments in such a way that if Container has N template args and i give N * M template arguments for args i get M template instantiations with N template args eg:

ContainerTemplate<std::vector, int, short, char>
//assuming std::vector takes only 1 arg for simplicity    

should result in

container = std::tuple<std::vector<int>, std::vector<short>, std::vector<char>>

while

ContainerTemplate<std::map, int, int, short, short>
//assuming std::map takes only 2 args for simplicity    

should result in

container = std::tuple<std::map<int, int>, std::map<short, short>>

Is there any way to do this? The question would be wether you could find out how many template args Container takes or not.

Edit: it would be ok if you were required to pass the additional arguments in tuples of size N

ContainerTemplate<std::map, std::tuple<int, int>, std::tuple<short, short>>

Edit2: so i actually found a way to determine the number of template template arguments

template<typename... T>
struct TypeList
{
    static const size_t Size = sizeof...(T);
    template<typename T2>
    struct PushFront
    {
        typedef TypeList<T2, T...> type_list;
    };
};

template<template<class...> class Template, typename... Args>
struct SizeofTemplateTemplate
{
    static const size_t Size = 0;
    typedef TypeList<> type;
};

template<template<class...> class Template, typename Arg, typename... Args>
struct SizeofTemplateTemplate<Template, Arg, Args...>
{
    template<typename... Args>
    struct Test;

    typedef char yes[1];
    typedef char no[2];

    template<typename... Args>
    struct Test<TypeList<Args...>>
    {
        template<template<class...> class Template>
        static yes& TestTemplate(Template<Args...>* arg);

        template<template<class...> class Template>
        static no& TestTemplate(...);
    };


    typedef typename SizeofTemplateTemplate<Template, Args...>::type::PushFront<Arg>::type_list type;
    static const size_t Size = sizeof(Test<type>::TestTemplate<Template>(0)) == sizeof(yes) ? type::Size : SizeofTemplateTemplate<Template, Args...>::Size;
};

with this, the following code will print 2

std::cout << SizeofTemplateTemplate<std::vector, int, std::allocator<int>, int, int>::Size << std::endl;

only problem i have now is that dyp's solution crashes the visual studio compiler xD

Edit3: complete solution for the original question here: https://stackoverflow.com/a/22302867/1366591


回答1:


It is not possible according to your first attempt, but it is possible according to your edit, where arguments are packed within std::tuple's. In this case, template Embed below takes arguments in each tuple and embeds them in Container.

See live example.

template<template<class... > class Container, typename P>
struct Embed_t;

template<template<class... > class Container, typename... T>
struct Embed_t <Container, std::tuple <T...> >
{
    using type = Container <T...>;
};

template<template<class... > class Container, typename P>
using Embed = typename Embed_t <Container, P>::type;

template<template<class... > class Container, typename... P>
struct ContainerTemplate
{
    using container = std::tuple<Embed <Container, P>...>;
};

In general, placing ... within ... is very tricky and can happen only in limited circumstances (I've only managed this once in a useful way).




回答2:


Here's a solution that doesn't require pre-packing the template template-arguments as tuples. This packing is done automatically, you only have to provide how many arguments are to be packed in one tuple (N).

#include <tuple>

template<template<class...> class Container, int N>
struct join_n_impl
{
    template<class ArgTuple, int I = 0, class Joined = std::tuple<>>
    struct helper;

    template<class Arg, class... Rest, int I, class... Joined>
    struct helper<std::tuple<Arg, Rest...>, I, std::tuple<Joined...>>
    : helper<std::tuple<Rest...>, I+1, std::tuple<Joined..., Arg>>
    {};

    template<class Arg, class... Rest, class... Joined>
    struct helper<std::tuple<Arg, Rest...>, N, std::tuple<Joined...>>
    {
        using type = Container<Joined...>;
        using rest = std::tuple<Arg, Rest...>;
    };

    template<class... Joined>
    struct helper<std::tuple<>, N, std::tuple<Joined...>>
    {
        using type = Container<Joined...>;
        using rest = std::tuple<>;
    };
};

template<template<class...> class Container, int N, class ArgTuple>
using join_n = typename join_n_impl<Container, N>::template helper<ArgTuple>;

template<template<class...> class Container, int N, class Args,
         class Collected = std::tuple<>>
struct pack_n;

template<template<class...> class Container, int N, class... Args,
         class... Collected>
struct pack_n<Container, N, std::tuple<Args...>, std::tuple<Collected...>>
{
    static_assert(sizeof...(Args) % N == 0,
                  "Number of arguments is not divisible by N.");

    using joiner = join_n<Container, N, std::tuple<Args...>>;
    using joined = typename joiner::type;
    using rest = typename joiner::rest;

    using type = typename pack_n<Container, N, rest,
                                 std::tuple<Collected..., joined>>::type;
};

template<template<class...> class Container, int N, class... Collected>
struct pack_n<Container, N, std::tuple<>, std::tuple<Collected...>>
{
    using type = std::tuple<Collected...>;
};

Usage example:

template<class, class>
struct test {};

#include <iostream>
template<class T>
void print_type(T) { std::cout << __PRETTY_FUNCTION__ << "\n"; }

int main()
{
    using to_pack = std::tuple<int, double, int, char, int, bool>;
    print_type( pack_n<test, 2, to_pack>::type{} );
}



回答3:


So i actually managed to find a way to solve my Question. I will leave iavr's answer as soloution though since the syntax is nice and it also allows the use of template overloads. So just for completeness sake and to prove that it is indeed possible:

template<typename... T>
struct TypeList
{
    static const size_t Size = sizeof...(T);
    template<typename T2>
    struct PushFront
    {
        typedef TypeList<T2, T...> type_list;
    };
};

template<template<class...> class Template, typename... Args>
struct SizeofTemplateTemplate
{
    static const size_t Size = 0;
    typedef TypeList<> type;
};

template<template<class...> class Template, typename Arg, typename... Args>
struct SizeofTemplateTemplate<Template, Arg, Args...>
{
    typedef char yes[1];
    typedef char no[2];

    template<typename...>
    struct Test;

    template<typename... args>
    struct Test<TypeList<args...>>
    {
        template<template<class...> class Testee>
        static yes& TestTemplate(Testee<args...>* arg);

        template<template<class...> class Testee>
        static no& TestTemplate(...);
    };


    typedef typename SizeofTemplateTemplate<Template, Args...>::type::PushFront<Arg>::type_list type;
    static const size_t Size = sizeof(Test<type>::TestTemplate<Template>(0)) == sizeof(yes) ? type::Size : SizeofTemplateTemplate<Template, Args...>::Size;
};

template<template<class...> class Template, size_t N, typename... Args>
struct GenerateNTuple;

template<template<class...> class Template, typename... Args>
struct GenerateNTuple<Template, 0, Args...>
{
    using type = TypeList<>;
    using rest = TypeList<Args...>;
};

template<template<class...> class Template, size_t N, typename Head, typename... Args>
struct GenerateNTuple<Template, N, Head, Args...>
{
    using type = typename GenerateNTuple<Template, N - 1, Args...>::type::template PushFront<Head>::type_list;
    using rest = typename GenerateNTuple<Template, N - 1, Args...>::rest;
};


template<template<class...> class Container, typename... args>
struct DeduceType;

template<template<class...> class Container, typename... args>
struct DeduceType<Container, TypeList<args...>>
{
    using type = Container<args...>;
};

template<template<class...> class Template, typename... Args>
struct ContainerTemplate;

template<template<class...> class Template, typename... Args>
struct ContainerTemplate<Template, TypeList<Args...>>
{
    using packed = GenerateNTuple<Template, SizeofTemplateTemplate<Template, Args...>::Size, Args...>;
    using type = typename ContainerTemplate<Template, typename packed::rest>::type::template PushFront<typename DeduceType<Template, typename packed::type>::type>::type_list;
};

template<template<class...> class Template>
struct ContainerTemplate<Template, TypeList<>>
{
    using type = TypeList<>;
};

template<template<class...> class Template, typename... Args>
using ContainerTypeList = typename ContainerTemplate<Template, TypeList<Args...>>::type;

usage is like this:

template<typename T>
using vec = std::vector<T>;
std::cout << typeid(ContainerTypeList<vec, int, short>).name() << std::endl;



回答4:


I've come up with another solution that does fully automatic packing according to your first requirement. The caveat is that implementation is not fully variadic: you have to specialize for template templates of 1, 2, 3 arguments etc. However, usage is exactly as you required initially.

This is probably similar to dyp's solution which I did not study very carefully.

Again, see live example.

In short, pack template templates into plain templates like that:

template<template<class> class>
struct Temp1;

template<template<class, class> class>
struct Temp2;

Then, the main definition of ContainerTemplate e.g. for 2 arguments is

template<
    template<class, class> class Container,
    typename T1, typename T2, typename... T
>
struct ContainerTemplate <Temp2<Container>, T1, T2, T...>
{
    using container = Join <
        std::tuple<Container<T1, T2> >,
        typename ContainerTemplate<Temp2<Container>, T...>::container
    >;
};

template<template<class, class> class Container>
struct ContainerTemplate<Temp2<Container> >
{
    using container = std::tuple<>;
};

where Join is concatenation (see live example for definition).

Finally, given e.g.

template<class> class Vector { };
template<class, class> class Map { };

usage is pretty nice:

ContainerTemplate<Temp1<Vector>, int, short, char>
ContainerTemplate<Temp2<Map>, int, int, short, short>



回答5:


Here's a start using Boost Mpl.

I opted to solve the map case by first 'pairing' the input into a vector of mpl::pair.

#include <boost/mpl/transform.hpp>
#include <boost/mpl/push_front.hpp>
#include <boost/mpl/pair.hpp>
#include <boost/mpl/vector.hpp>
#include <vector>
#include <map>

namespace mpl = boost::mpl;

namespace detail
{
    using namespace mpl;

    template <template <typename...> class Container, typename... T>
        using unary = typename transform<vector<T...>, Container<_1> >::type;

    namespace binary_impl
    {
        template <typename MplVector> struct pairs;

        template <> struct pairs<mpl::vector<> >
        {
            using type = mpl::vector<>;
        };

        template <typename A, typename B, typename... T>
            struct pairs<mpl::vector<A, B, T...> >
        {
            using type = typename mpl::push_front<
                    typename pairs<mpl::vector<T...> >::type,
                    mpl::pair<A, B>
                >::type;
        };
    }

    template <template <typename...> class Container, typename... T>
        using binary = typename transform<
            typename binary_impl::pairs<vector<T...> >::type, 
            Container<apply_wrap1<first<>, _1>, apply_wrap1<second<>, _1> >
            >
            ::type;
}

template <typename K, typename V, typename stuff = std::less<K> >
struct MyMap : std::map<K,V,stuff> { using std::map<K, V>::map; };

template <typename... T> using make_vectors = detail::unary<std::vector, T...>;
template <typename... T> using make_pairs   = detail::binary<std::pair,  T...>;
template <typename... T> using make_mymaps  = detail::binary<MyMap,      T...>;

#include <iostream>
#include <string>

int main()
{
    auto vectors = make_vectors<int, char, double> { };
    auto pairs   = make_pairs  <int, char, int, std::string, int, double> { };
    auto mymaps  = make_mymaps <int, char, int, std::string, int, double> { };
}

For some reason, it will not work with actual std::map but it will with my std::pair or my own (std::map<> derived) MyMap type. (If anyone can explain the reason here, I'd be very happy to know).

See it Live On Coliru




回答6:


Here is another variation using std::tuple. I used some code from @ACB for the template parameter count calculation.

#include <tuple>

template<template<typename...> class Template, typename... Args>
struct TemplateArgCount
{
   static const int value = 0;
};

template<template<typename...> class Template, typename Arg, typename... Args>
struct TemplateArgCount<Template, Arg, Args...>
{
   typedef char small[1];
   typedef char big[2];

   template<typename... A>
   struct Test
   {
      template<template<typename...> class T>
      static small& test(T<A...>*);

      template<template<typename...> class T>
      static big& test(...);
   };

   static const int value = sizeof(Test<Arg, Args...>::template test<Template>(0)) == sizeof(small)
                            ? sizeof...(Args)+1
                            : TemplateArgCount<Template, Args...>::value;
}; 

template<typename GlobalResult, typename LocalResult, template<typename...> class Template, int Count, int Pos, typename... Args>
struct TemplateTuplesImpl;

template<typename... GlobalResult, typename... LocalResult, template<typename...> class Template, int Count, typename Arg, typename... Args>
struct TemplateTuplesImpl<std::tuple<GlobalResult...>, std::tuple<LocalResult...>, Template, Count, Count, Arg, Args...>
: TemplateTuplesImpl<std::tuple<GlobalResult..., Template<LocalResult...>>, std::tuple<>, Template, Count, 0, Arg, Args...>
{
};

template<typename GlobalResult, typename... LocalResult, template<typename...> class Template, int Count, int Pos, typename Arg, typename... Args>
struct TemplateTuplesImpl<GlobalResult, std::tuple<LocalResult...>, Template, Count, Pos, Arg, Args...>
: TemplateTuplesImpl<GlobalResult, std::tuple<LocalResult..., Arg>, Template, Count, Pos+1, Args...>
{
};

template<typename... GlobalResult, typename ...LocalResult, template<typename...> class Template, int Count>
struct TemplateTuplesImpl<std::tuple<GlobalResult...>, std::tuple<LocalResult...>, Template, Count, Count>
{
   using type = std::tuple<GlobalResult..., Template<LocalResult...>>;
};

template<template<class... Params> class Container, typename... Args>
struct TemplateTuples
{
   static const int ParamSize = TemplateArgCount<Container, Args...>::value;
   static const int ArgSize = sizeof...(Args);
   static_assert(ParamSize > 0, "Arguments list does not match template class param list!");
   static_assert(ArgSize%ParamSize == 0, "Argument list not in multiples of template class param count!");
   using type = typename TemplateTuplesImpl<std::tuple<>, std::tuple<>, Container, ParamSize, 0, Args...>::type;
};

Usage is like this:

#include <type_traits>
#include <utility>

int main()
{
   static_assert(std::is_same<TemplateTuples<std::pair, int, short, float, double>::type, 
                              std::tuple<std::pair<int, short>, std::pair<float, double>>
                             >::value, "Does not match :-(");
   return 0;
}



回答7:


After fiddling around with various solutions from this thread I decided on this solution:

A two-dimensional "tuple pack", i.e. tuple< tuple< T1 >, tuple<T2,T3>, ... > etc.

This allows a couple of things:

  1. I'd like to not use a container, but there is no such thing as "default variadic parameters" so the tuple system allows default parameters for a 2D tuple pack merely by not being a variadic type-pack.
  2. Each sub-tuple can have a different number of arguments if desired. This allows any default template parameters to come into play - i.e. in cases where the sub-tuple is less specified than the referenced template-template (t_tempMPlex).
  3. Also I'd like to place the result in any holder that might be capable of holding the result - i.e. tuple, variant, or any other variadic holder that a user might want to fill with types (t_tempVarHolder).
// Multiplex templates with 2-dimensional tuple-types and contain them in some type of variant/tuple/etc container.
template < template<class... > class t_tempMPlex, class t_TyTpTPack >
struct  _MPlexTPack2DHelp_2;
template < template<class... > class t_tempMPlex, class ... t_TysExtractTPack >
struct  _MPlexTPack2DHelp_2< t_tempMPlex, tuple< t_TysExtractTPack ... > >
{
  typedef t_tempMPlex< t_TysExtractTPack ... > type;
};
template< template<class... > class t_tempMPlex, class t_TyTp2DTPack, 
          template < class ... > class t_tempVarHolder >
struct _MPlexTPack2DHelp;
template< template<class... > class t_tempMPlex, class ... t_TyTpsExtractTPack, 
          template < class ... > class t_tempVarHolder >
struct _MPlexTPack2DHelp<t_tempMPlex, tuple< t_TyTpsExtractTPack ... >, t_tempVarHolder >
{
  using type = t_tempVarHolder< typename _MPlexTPack2DHelp_2< t_tempMPlex, t_TyTpsExtractTPack >::type ... >;
};
template< template<class... > class t_tempMPlex, class t_TyTp2DTPack, 
          template < class ... > class t_tempVarHolder = tuple >
struct MPlexTPack2D
{
    using type = typename _MPlexTPack2DHelp< t_tempMPlex, t_TyTp2DTPack, t_tempVarHolder >::type;
};
template< template<class... > class t_tempMPlex, class t_TyTp2DTPack, 
          template < class ... > class t_tempVarHolder = tuple >
using MPlexTPack2D_t = typename MPlexTPack2D< t_tempMPlex, t_TyTp2DTPack, t_tempVarHolder >::type;

Usage: Here's my usage scenario: I am writing an XML parser that works natively in any character type. I also want to support switching the endian nature of the file in the scenarios that matter - i.e. for UTF32BE, and UTF16BE - of course when I am on a little endian machine.

So I have these transport types:

template < class t_TyChar, class t_TyBoolSwitchEndian = false_type >
class _l_transport_file;
template < class t_TyChar, class t_TyBoolSwitchEndian = false_type >
class _l_transport_fixedmem;
template < class t_TyChar, class t_TyBoolSwitchEndian = false_type >
class _l_transport_mapped;

I want to provide a default argument for my variant parser that implements all character types and endian-switching possibilities, but I also want the user to be able to specify that they want less than that.

Here is the declaration of my xml_parser_var:

template <  template < class ... > t_tempTyTransport, 
            class t_TyTp2DCharPack >
class xml_parser_var;

Where t_tempTyTransport is one of the above _l_transport_* templates.

I will provide a default argument of this for t_TyTp2DCharPack:

tuple< tuple< char32_t, true_type >, 
       tuple< char32_t, false_type >, 
       tuple< char16_t, true_type >, 
       tuple< char16_t, false_type >, 
       tuple< char8_t, false_type > >

But I'd like the user to be able to specify less - i.e. perhaps the user programmer doesn't care about UTF32 files, only UTF16 and UTF8. A significant amount of binary space will be saved in the variant if the UTF32 character types are removed.

Anyway, short story long, this is what I came up with. I like it. It allows default arguments to come into play, i.e. this is the same as the above given the default arguments:

tuple< tuple< char32_t, true_type >, 
       tuple< char32_t >, 
       tuple< char16_t, true_type >, 
       tuple< char16_t >, 
       tuple< char8_t > >

Here is my usage sequence:

typedef MPlexTPack2D_t< t_tempTyTransport, t_TyTp2DCharPack > _TyTpTransports;

_TyTpTransports ends up being:

tuple< t_tempTyTransport< char32_t, true_type >, t_tempTyTransport< char32_t >
       t_tempTyTransport< char16_t, true_type >, t_tempTyTransport< char16_t >,
       t_tempTyTransport< char8_t > >

Then that "tuple pack" can be use to produce further typedefs, etc. Also, if I want a variant instead of a tuple, i.e.:

variant< t_tempTyTransport< char32_t, true_type >, t_tempTyTransport< char32_t >
         t_tempTyTransport< char16_t, true_type >, t_tempTyTransport< char16_t >,
         t_tempTyTransport< char8_t > >

Then I use this instead:

typedef MPlexTPack2D_t< t_tempTyTransport, t_TyTp2DCharPack, variant > _TyTpTransports;

Synposis:

typedef MPlexTPack2D_t< variant, 
                        tuple< tuple< char32_t, true_type >, 
                               tuple< char32_t >,  
                               tuple< char16_t, true_type >, 
                               tuple< char16_t >, 
                               tuple< char8_t, false_type > > > _TyTuple2D;
static_assert( is_same_v< _TyTuple2D, 
                          tuple< variant< char32_t, true_type >, 
                                 variant< char32_t >,  
                                 variant< char16_t, true_type >, 
                                 variant< char16_t >, 
                                 variant< char8_t, false_type > > > );

Let me know what y'all think.



来源:https://stackoverflow.com/questions/22289827/template-template-parameter-expansion-for-variadic-templates

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!