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

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

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

то, что я хочу, является шаблоном, который получает Container или какой-либо другой шаблонный шаблон в качестве параметра шаблона шаблона, а затем расширяет остальные аргументы шаблона таким образом, что если Container имеет N шаблонных аргументов и я даю шаблон N * M аргументы для args, я получаю экземпляры M-шаблонов с N шаблонами arg, например:

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: полное решение для исходного вопроса здесь: qaru.site/info/493242/...

Ответ 1

Это невозможно в соответствии с вашей первой попыткой, но это возможно в соответствии с вашим редактированием, где аргументы упакованы в пределах 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>...>;
};

В общем, размещение ... внутри ... очень сложно и может произойти только в ограниченных обстоятельствах (мне это удалось только однажды полезным способом).

Ответ 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{} );
}

Ответ 3

Так мне действительно удалось найти способ решить мой вопрос. Я оставлю 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;

Ответ 4

Я придумал другое решение, которое полностью автоматизирует упаковку в соответствии с вашим первым требованием. Предостережение заключается в том, что реализация не является полностью изменчивой: вам нужно специализироваться на шаблонных шаблонах из 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>

Ответ 5

Здесь можно использовать 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

Ответ 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;
}