c++c++20variadic-templatesparameter-pack

How to generate parameter packs via meta-programming?


Suppose

template<T,typename... Types> void bar(Types..., int,Types...,float,T);

I am trying to write a template struct GimmePack<double,k> so that

bar<char, GimmePack<double,3>::type >(x,y,z,42,dx,dy,dz,3.14159,'c');

is the same as

bar<char, double,double,double >(x,y,z,42,dx,dy,dz,3.14159,'c');

I.e., I would like to find a way to repeat a Template type k times outside of a tuple type, i.e. directly injecting it into a function.

I had left out the arguments priorly because their exact nature is of no relevance to the question. But as has turned out from the below answers (some of which become inapplicable from that fact), the fact of the mere existance of a nontrivial signature based on Types... is a critical detail.

Question

Is this even possible?

Below is my attempt.

#include<iostream>

template<typename,size_t,typename...> struct Packstack;

template<typename Tfloat, size_t i, typename ...Types>
    requires(i>0)
struct Packstack<Tfloat,i,Types...>{
    using pack = typename Packstack<Tfloat,i-1,Tfloat,Types...>::pack;
};

template<typename Tfloat, typename ...Types>
struct Packstack<Tfloat,0,Types...>{
    using pack... = Types...;
};

template<typename Tfloat, size_t i>
struct GimmePack{
    using pack = typename Packstack<Tfloat,i>::pack;
};

Remarks

I am aware that I could weave my Packstack metaprogramming code into the function definition of bar. But in my actual source code that I am developing on, bar is not a simple function but a template function of a complicated template class, and readability would suffer.

I am also aware that the information double,double,double is equivalent to the information double,3 in my context of identical types, hence a template specialization for bar could be constructed if bar was a struct (because template specializations for functions are invalid c++). So my question is rather on syntax and not on concept.

What I tried

In my actual code I have already tried rearranging the order of the templates so that the compiler could figure them out, but my signature is

template<typename other1, typename other2, typename ...Values> bar(Values... values1, other1 o1, Values... values2, other2 o2);

and it just seems like whenever Values... appears twice in a signature then the compiler wants to have it written explicitly. While it looks like vectors are a good choice here, the data elements within each values1..., values2... stem from uncorrelated contexts.

I tried putting triple-dots in various places around the using statements.


Solution

  • How about using an explicit template parameter list on a generic lambda expression?

    template <class...>
    struct mp_list {};
    
    template <class, std::size_t>
    struct mp_pack {};
    
    namespace detail {
    
    template <class L, class...>
    struct mp_expand { using type = L; };
    
    template <class... Ts, class U, class... Us>
    struct mp_expand<mp_list<Ts...>, U, Us...>
        : mp_expand<mp_list<Ts..., U>, Us...> {};
    
    template <class... Ts, class U, std::size_t I, class... Us>
    struct mp_expand<mp_list<Ts...>, mp_pack<U, I>, Us...>
        : mp_expand<mp_list<Ts..., U>, mp_pack<U, I - 1>, Us...> {};
    
    template <class... Ts, class U, class... Us>
    struct mp_expand<mp_list<Ts...>, mp_pack<U, 0>, Us...>
        : mp_expand<mp_list<Ts...>, Us...> {};
    
    }  // namespace detail
    
    template <class... Ts>
    using mp_expand = typename detail::mp_expand<mp_list<>, Ts...>::type;
    
    template <class... Ts>
    auto GimmePack(auto f) {
      return f(mp_expand<Ts...>{});
    }
    

    and then:

    template <class T, class... Types>
    void bar(Types..., int, Types..., float, T);
    
    // bar<char, double, double, double>(x, y, z, 42, dx, dy, dz, 3.14159, 'c');
    GimmePack<char, mp_pack<double, 3>>([&]<class... Ts>(mp_list<Ts...>) {
      return bar<Ts...>(x, y, z, 42, dx, dy, dz, 3.14159, 'c');
    });
    

    Try it on godbolt