Расширение параметров шаблона шаблона для шаблонов с переменными параметрами

Недавно я узнал о существовании параметров шаблона шаблона и теперь задавался вопросом, возможно ли что-то подобное:

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

Есть какой-либо способ сделать это? Вопрос был бы в том, можно ли узнать, сколько шаблонных аргументов берет контейнер или нет.

Изменить: было бы хорошо, если бы вы должны были передать дополнительные аргументы в кортежах размера 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

Edit3: полное решение для исходного вопроса здесь: /questions/9026491/rasshirenie-parametrov-shablona-shablona-dlya-shablonov-s-peremennyimi-parametrami/9026514#9026514

7 ответов

Решение

Это невозможно в соответствии с вашей первой попыткой, но возможно в соответствии с вашим редактированием, когда аргументы упакованы в std::tuple"S. В этом случае шаблон 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 тип. (Если кто-нибудь может объяснить причину здесь, я был бы очень рад узнать).

Посмотри это в прямом эфире на Колиру

Вот еще один вариант с использованием 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<T1>, tuple<T2, T3>, ...> и т. Д.

Это позволяет сделать несколько вещей:

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

Дайте мне знать, что вы думаете.

Другие вопросы по тегам