可变参数模板的模板模板参数扩展

时间:2014-03-09 23:48:12

标签: c++ templates c++11 variadic

我最近了解到模板模板参数的存在,现在想知道这样的事情是否可行:

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

我想要的是一个模板,它获取一个Container或一些其他模板类作为模板模板参数,然后扩展其余的模板参数,如果Container有N个模板参数,我给N * M模板args的参数我得到了N个模板实例的M模板实例,例如:

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

应该导致

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

,而

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

应该导致

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

有没有办法做到这一点? 问题是,你可以找出容器有多少模板args。

编辑: 如果你被要求在大小为N的元组中传递额外的参数,那就没问题。

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

EDIT2: 所以我实际上找到了一种方法来确定模板模板参数的数量

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;
};

有了这个,下面的代码将打印2

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

我现在唯一的问题是dyp的解决方案崩溃了visual studio编译器xD

EDIT3: 原始问题的完整解决方案:https://stackoverflow.com/a/22302867/1366591

7 个答案:

答案 0 :(得分:7)

根据您的第一次尝试,这是不可能的,但根据您的编辑,可以将参数打包在std::tuple中。在这种情况下,下面的模板Embed会在每个tuple中获取参数,并将其嵌入Container

请参阅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>...>;
};

一般情况下,将...放在...内是非常棘手的,并且只能在有限的情况下发生(我只是以有用的方式管理过一次)。

答案 1 :(得分:7)

这是一个不需要将模板模板参数预先打包为元组的解决方案。这种打包是自动完成的,你只需要在一个元组(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...>;
};

用法示例:

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{} );
}

答案 2 :(得分:5)

所以我实际上设法找到了解决问题的方法。我会留下iavr的答案作为soloution,因为语法很好,它也允许使用模板重载。所以只是为了完整起见并证明它确实可行:

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;

用法是这样的:

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

答案 3 :(得分:3)

我已经提出了另一种根据您的第一个要求进行全自动包装的解决方案。需要注意的是,实现并不是完全可变的:您必须专注于1,2,3个参数的模板模板等。但是,使用情况与您最初需要的完全相同。

这可能类似于dyp的解决方案,我没有仔细研究过。

再次,请参阅live example

简而言之,将模板模板打包成普通模板:

template<template<class> class>
struct Temp1;

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

然后,ContainerTemplate的主要定义,例如2个参数是

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<>;
};

其中Join是连接(请参阅实例以获取定义)。

最后,例如。

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

使用非常好:

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

答案 4 :(得分:3)

这是使用Boost Mpl的开始。

我选择首先将输入“配对”到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> { };
}

出于某种原因,它不适用于实际的std::map,但会使用我的std::pair或我自己的(std::map<>派生的)MyMap类型。 (如果有人能在这里解释原因,我会很高兴知道)。

查看 Live On Coliru

答案 5 :(得分:0)

这是使用std :: tuple的另一个变体。我使用了来自@ACB的一些代码进行模板参数计算。

#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;
};

用法是这样的:

#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;
}

答案 6 :(得分:0)

在从这个线程中摆弄各种解决方案后,我决定了这个解决方案:

二维“元组包”,即元组<元组、元组、...>等

这允许一些事情:

  1. 我不想使用容器,但没有“默认可变参数”这样的东西,因此元组系统允许 2D 元组包的默认参数只是因为不是可变类型包。
  2. 如果需要,每个子元组可以有不同数量的参数。这允许任何默认模板参数发挥作用 - 即在子元组比引用的模板模板 (t_tempMPlex) 指定更少的情况下。
  3. 此外,我想将结果放在 任何 可能能够保存结果的持有者中 - 即元组、变体或用户可能想要填充类型的任何其他可变参数持有者(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;

用法:这是我的使用场景:我正在编写一个 XML 解析器,它可以在任何字符类型中本地工作。我还希望支持在重要场景中切换文件的字节序性质 - 即对于 UTF32BE 和 UTF16BE - 当然,当我使用的是小字节序机器时。

所以我有这些传输类型:

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;

我想为实现所有字符类型和字节序切换可能性的变体解析器提供一个默认参数,但我也希望用户能够指定他们想要的更少。

这是我的 xml_parser_var 的声明:

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

其中 t_tempTyTransport 是上述 _l_transport_* 模板之一。

我将为 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 > >

但我希望用户能够指定更少 - 即用户程序员可能不关心 UTF32 文件,只关心 UTF16 和 UTF8。如果删除 UTF32 字符类型,将在变体中节省大量二进制空间。

无论如何,长篇小说,这就是我想出来的。我喜欢。它允许使用默认参数,即这与上面给出的默认参数相同:

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

这是我的使用顺序:

typedef MPlexTPack2D_t< t_tempTyTransport, t_TyTp2DCharPack > _TyTpTransports;

_TyTpTransports 最终是:

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 > >

然后可以使用“元组包”来生成进一步的 typedef 等。 另外,如果我想要一个变体而不是元组,即:

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 > >

然后我改用这个:

typedef MPlexTPack2D_t< t_tempTyTransport, t_TyTp2DCharPack, variant > _TyTpTransports;

概要:

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 > > > );

让我知道你们的想法。