【问题标题】:template template parameter expansion for variadic templates可变参数模板的模板模板参数扩展
【发布时间】:2014-04-12 22:42:06
【问题描述】:

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

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

有没有办法做到这一点? 问题是您是否可以找出 Container 需要多少模板参数。

编辑: 如果您需要在大小为 N 的元组中传递附加参数,那将是可以的

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

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

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

编辑3: 此处原始问题的完整解决方案:https://stackoverflow.com/a/22302867/1366591

【问题讨论】:

  • 第一个?容易地。第二个?笏。它应该如何知道它应该传递给该类型的参数数量? (请注意,标准容器的模板参数比您想象的要多。
  • 我知道他们有更多只是为了简化。我的问题是是否有可能以任何方式确定模板参数的数量,然后挽救该信息以获得我想要的
  • 编译器无法读懂你的想法,抱歉。
  • @Griwes 这就是为什么他想知道如何告诉他...
  • 我可以看到有一种方法可以制作一个简单的贪婪版本并使用 SFINAE 忽略那些不起作用的版本,但我不知道从哪里开始。

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


【解决方案1】:

根据您的第一次尝试这是不可能的,但根据您的编辑是可能的,其中参数包含在 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>...>;
};

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

【讨论】:

  • ye 发现对于元组 ^^ 似乎你不能推断出模板模板 args 的数量,所以这和它一样好
【解决方案2】:

这是一个不需要将模板模板参数预先打包为元组的解决方案。此打包是自动完成的,您只需提供要打包到一个元组中的参数数量 (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 的版本也适用于 N 个模板模板参数^^。太糟糕了,您必须指定模板模板参数的数量。但谢谢你的想法
  • @ACB 哦,​​你是对的。但就我所见,您必须将它们预先打包为元组。
  • 你我知道。但它有效 ^^ 并且作为副作用,您有一些语法来判断哪些类型属于一起
  • 在模板和参数上写一个特征类,返回它将接受多少个参数?然后用它推导出N?可能必须做一定程度的间接。 @ACB 我认为这样的特征是可写的......
  • 因为我现在找到了一种方法来确定您需要的参数数量。您知道为什么 Visual Studio 编译器会因您的代码而崩溃吗?
【解决方案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>

【讨论】:

  • 专业化有点破坏了练习的重点^^对我来说看起来像是一个真正的疏忽,你不能 sizeof...(模板模板参数)
  • 你说得对,我自己更喜欢我的第一个解决方案,虽然它的使用有点麻烦。一般来说,具有特定数量参数的模板模板有很多弱点,所以我倾向于总是使用template &lt;typename...&gt; class。另请注意,由于没有检查,因此使用带有错误数量参数的模板可能会产生各种意想不到的结果。
【解决方案4】:

这是使用 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&lt;&gt; 派生的)MyMap 类型。 (如果有人能在这里解释原因,我很高兴知道)。

Live On Coliru

【讨论】:

    【解决方案5】:

    所以我实际上设法找到了解决我的问题的方法。尽管语法很好,而且它还允许使用模板重载,但我会将 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;
    

    【讨论】:

    • 非常好!随意选择这个答案而不是我的 :-) 这是唯一真正回答问题的答案,即如何自动查找模板参数的数量。现在,当我们偶尔为不同的原因定义 template&lt;typename...&gt; struct ... 时,这可能只会失败,因为它实际上只需要 1-2 个参数。不确定,但你可以检查一下。
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

      在摆弄了这个帖子的各种解决方案后,我决定采用这个解决方案:

      一个二维的“元组包”,即元组, tuple, ... >等

      这允许做几件事:

      1. 我不想使用容器,但没有“默认可变参数”之类的东西,因此元组系统允许二维元组包的默认参数只是不是可变参数类型包。
      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 > >
      

      然后那个“元组包”可以用来产生更多的类型定义,等等。 另外,如果我想要一个变体而不是元组,即:

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

      让我知道你们的想法。

      【讨论】:

        猜你喜欢
        • 2014-10-30
        • 2016-12-01
        • 2015-05-21
        • 1970-01-01
        • 2014-09-08
        • 2012-03-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多