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

template template parameter expansion for variadic templates

本文关键字:参数 扩展 Template      更新时间:2023-10-16

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

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

我想要的是一个模板,它得到一个容器或其他模板类作为模板模板参数,然后扩展模板参数的其余部分,如果容器有N个模板参数,我给N * M个模板参数,我得到M个模板实例化与N个模板参数,例如:

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

有什么办法可以做到吗?问题是你是否能找出Container有多少个模板参数。

编辑:如果需要在大小为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

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

参见实例

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

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

这里有一个不需要将模板模板参数预先打包为元组的解决方案。这种打包是自动完成的,您只需要提供在一个元组中打包多少个参数(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{} );
}

所以我实际上设法找到一种方法来解决我的问题。我将把iavr的答案作为解决方案,因为语法很好,它也允许使用模板重载。因此,为了完整起见,为了证明它确实是可能的:

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;

我已经提出了另一个解决方案,根据您的第一个要求进行全自动包装。需要注意的是,实现并不是完全可变的:你必须为1、2、3个参数的模板专门化。然而,使用完全符合您最初的要求。

这可能与我没有仔细研究的dyp的解决方案相似。

同样,参见实际示例。

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

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>

开始使用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

这是使用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;
}

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

二维"元组包",即tuple, tuple<T2,T3>,…比;等。

  1. 我不想使用容器,但是没有"默认可变参数"这样的东西。所以元组系统允许2D元组包的默认参数,而不是可变类型包。
  2. 如果需要,每个子元组可以有不同数量的参数。这允许任何默认模板参数发挥作用-即在子元组小于引用的template-template (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;

_tytptransportss最终为:

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

然后那个"tuple pack"可用于产生进一步的类型等。另外,如果我想要一个变量而不是一个元组,例如:

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;

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

让我知道你们的想法