How to Have Multiple Parameter Packs in a Variadic Template

Multiple parameter packs in a single function?

Let's first code a variable template which determines whether a type derives from First or not:

template <int N>
constexpr std::true_type is_first(First<N> const &) { return {}; }
template <int N>
constexpr std::false_type is_first(Second<N> const &) { return {}; }

template <class T>
constexpr bool is_first_v = decltype( is_first(std::declval<T>()) )::value;

And a struct Split which collects the indices of the First and Second types:

template <class, class, class, std::size_t I = 0> struct Split;

template <
std::size_t... FirstInts,
std::size_t... SecondInts,
std::size_t N
>
struct Split<
std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts...>,
std::tuple<>,
N
> {
using firsts = std::index_sequence<FirstInts...>;
using seconds = std::index_sequence<SecondInts...>;
};

template <
std::size_t... FirstInts,
std::size_t... SecondInts,
std::size_t I,
typename T,
typename... Tail
>
struct Split<
std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts...>,
std::tuple<T, Tail...>,
I
> : std::conditional_t<
is_first_v<T>,
Split<std::index_sequence<FirstInts..., I>,
std::index_sequence<SecondInts...>,
std::tuple<Tail...>,
I + 1
>,
Split<std::index_sequence<FirstInts...>,
std::index_sequence<SecondInts..., I>,
std::tuple<Tail...>,
I + 1
>
> {};

And like I told you in the comments, adding a member value to First and Second (or inheriting from std:integral_constant), this allows us to write the following:

template <std::size_t... FirstIdx, std::size_t... SecondIdx, typename Tuple>
void function_impl(float f, std::index_sequence<FirstIdx...>, std::index_sequence<SecondIdx...>, Tuple const & tup) {
((std::cout << "firstInts: ") << ... << std::get<FirstIdx>(tup).value) << '\n';
((std::cout << "secondInts: ") << ... << std::get<SecondIdx>(tup).value) << '\n';
// your implementation
}

template <class... Args>
void function(float f, Args&&... args) {
using split = Split<std::index_sequence<>,std::index_sequence<>, std::tuple<std::decay_t<Args>...>>;
function_impl(f, typename split::firsts{}, typename split::seconds{}, std::forward_as_tuple(args...));
}

Demo

C++ - Multiple parameter packs for variadic function

And what exactly does the error mean?

The error mean that your call

iocContainer_.RegisterSingletonClassFactory<boost::asio::io_context, std::tuple<>>(30);

doesn't matches the declaration of your template function

template <class T,
template<typename ...TDependencies> typename TUnused, typename... TDependencies,
typename ...TArgs>
void RegisterSingletonClassFactory(TArgs...args)

because your template functions waits for

  1. a type parameter (T),

  2. a template-template parameter (TUnused)

and other template parameters when you pass

  1. a type paramer (boost::asio::io_context)

  2. another type template parameter (std::tuple<>)

If you want to pass std::tuple as template-template parameter, you have to pass it without template parameters (std::tuple, not std::tuple<>).

Given that your TUnused parameter is... well, unused,... I suppose that your intention was to use it as type container.

But there is no needs for it.

Not sure but seems to me that your looking for something similar

template <typename T, typename... TDependencies, typename ...TArgs>
void foo (TArgs...args)

So you can explicit T and a (maybe empty) TDependecies... list of types.

The TArgs... list is deduced from the arguments

The following is a silly, but compiling, C++17 example

#include <iostream>

template <typename T, typename... TDependencies, typename ...TArgs>
void foo (TArgs...args)
{
std::cout << "T:" << typeid(T).name() << std::endl;

std::cout << "TDependecies list:" << std::endl;

((std::cout << "- " << typeid(TDependencies).name() << std::endl), ...);

std::cout << "TArgs list:" << std::endl;

((std::cout << "- " << typeid(TArgs).name() << std::endl), ...);
}

int main()
{
foo<std::string, short, int, long, long long>(0, 1l, 2ll);
}

where T is std::string (the first explicit template parameters), TDependencies... is short, int, long, long long (the following explicit template parameters) and TArgs... is int, long, long long (deduced from the 0, 1l, 2ll arguments).

Observe that you don't need the TUnused template parameter.

Multiple Variadic Parameter Pack for Template Class

In the discussion comments you expressed a willingness to consider some kind of indirection, or "a wrapper of some kind for the attribute list".

A lightweight std::tuple-based wrapper, together with specialization, might work here:

template <typename attribute_tuple, APITypes APIType,
typename policy_tuple> class IShader;

template <AttributeType... Attributes, APITypes APIType,
class... Policies>
class IShader<std::tuple<Attributes...>, APIType,
std::tuple<Policies...>> : public Policies... {

// ...

};

The goal here is to use a template instance along the lines of:

IShared<std::tuple<Attribute1, Attribute2>, APITypeFoo,
std::tuple<Policy1, Policy2>> ishared_instance;

And cross your fingers that this is going to match the specialized template declaration, at which point both parameter packs are available for the template specialization to use, individually.

Expansion of multiple parameter packs of types and integer values

It's not possible to generate specializations, but you don't actually need those.

It's not possible to have more than one template parameter pack per class template, so we'll have to work with a single one, with a helper struct that combines both a type and its index into a single type.

#include <cstddef>
#include <iostream>
#include <type_traits>

template <typename T, int I>
struct Type
{
using type = T;
static constexpr int value = I;
};

template <typename ...P>
struct Foo
{
int index = 0;

template <typename T, std::enable_if_t<(std::is_same_v<T, typename P::type> || ...), std::nullptr_t> = nullptr>
Foo &operator=(const T &)
{
(void)((std::is_same_v<T, typename P::type> ? (index = P::value, true) : false) || ...);
return *this;
}
};

int main()
{
Foo<Type<int, 10>, Type<float, 20>> x;
x = 42;
std::cout << x.index << '\n'; // 10
x = 42.f;
std::cout << x.index << '\n'; // 20
// x = 42L; // Error.
}

(X || ...) is a fold expression. It repeats X multiple times, once per element of P. Every use of P in X is replaced with its i-th element.

We use it as a poor man's loop. When the types match and ? : returns true, the loop stops.

The cast to (void) silences the "unused result" warning.

How to explicitly specify template arguments for multiple parameter packs

Regarding the second part of the question (second example).

Template argument deduction deduces parameter packs Ts as [int, char], and Us as [ float, double ] the same way it deduces T if you have the following:

template <class> struct B {};
template <class T> void h(B<T>)

.. and you call h(B<int>()), template argument deduction is applied as follows:

P = B<T>, A = B<int> -> [ T = int ] not [ T = B<int> ]

The same is applied in the second example:

P1 = S<Ts...>, A1 = S<int, char> --> [ Ts = int, char ];
P2 = S<Us...>, A2 = S<float, double> --> [ Us = float, double ];

Inheriting class with multi parameter pack variadic template

What about

template <typename... OutputTopics, typename... InputTopics>
class TheChild<TopicsList<OutputTopics...>, TopicsList<InputTopics...>>
: public TheParent<TopicsList<OutputTopics...>, TopicsList<InputTopics...>>, public ::testing::Test
// ....................^^^^^^^^^^^...............^^^^^^^^^^^^^^..............^
{
};

?

I mean... if your TheParent is declared only receiving a couple of TopicsList, you have to maintain the TopicsList wrapper passing the parameters from TheChild to TheParent.

How can i combine multiple variadic templates or split parameter pack?

In hope I got your wishes...


template< typename PACK1, typename PACK2 > struct Combined;

template < typename ... PACK1, typename ... PACK2 >
struct Combined< std::function< uint64_t( uint64_t, PACK1... )>, std::function< uint64_t(uint64_t, PACK2...) > >
{
using OP_TYPE1 = std::function< uint64_t( uint64_t, PACK1... )>;
using OP_TYPE2 = std::function< uint64_t( uint64_t, PACK2... )>;

OP_TYPE1 op1;
OP_TYPE2 op2;
Combined( OP_TYPE1 op1_, OP_TYPE2 op2_ ): op1{ op1_}, op2{ op2_}{}

auto operator( )(uint64_t p1, PACK1... args1, PACK2... args2)
{
return op2( op1( p1, args1...), args2...);
}
};

template < typename OP_TYPE1, typename OP_TYPE2> auto operator*(OP_TYPE1, OP_TYPE2);

template < typename ... PACK1, typename ... PACK2 >
auto operator* ( std::function< uint64_t( uint64_t, PACK1... )> op1, std::function< uint64_t(uint64_t, PACK2...) > op2 )
{
return Combined< std::function< uint64_t( uint64_t, PACK1... )>, std::function< uint64_t(uint64_t, PACK2...)>>{ op1, op2 };
}

// Example funcs
auto f(uint64_t num, int i, int j) -> uint64_t{
return num + ((i - j)^i);
}

uint64_t g(uint64_t num, double x){
return num - int(num / x);
}

int main()
{
std::function fobject = f;
std::function gobject = g;

auto fg = fobject*gobject;

std::cout << fg( 1, 2, 3, 6.66 ) << std::endl;
}

The exmaple misses all what can be optimized with forwarding arguments, moving and so on. It is only for you to catch signatures and taking params from template arguments and so on.

Multiple concept-constrained parameter packs in variadic function in C++20 does not accept arguments in the first parameter pack

Deduction for function parameter packs only occurs for the last pack in the argument list. All other packs are considered a non-deduced context:

The non-deduced contexts are:

...

A function parameter pack that does not occur at the end of the parameter-declaration-list.

Concepts doesn't affect this. You can't use concepts as a way to make the first pack deducible.

In any case, it's much easier to just have a concept that could be an arithmetic type or a pointer to an arithmetic type, a fold expression, and a single function to distinguish which from which:

#include <type_traits>

template <typename T>
concept number = std::is_arithmetic_v<T>; //Pointers aren't arithmetic types.

template <typename T>
concept ptr_to_num =
std::is_pointer_v<T> &&
number<std::remove_pointer_t<T>>;

template<typename T>
concept ptr_to_num_or_num =
number<T> || ptr_to_num<T>;

template<ptr_to_num_or_num T>
double dereference(T p)
{
if constexpr(ptr_to_num<T>)
return *p;
else
return p;
}

template<ptr_to_num_or_num ...Args>
double foo(Args ...args)
{
return (0.0 + ... + dereference(args));
}

int main()
{
float f = 3.;
unsigned u = 4;

foo (); // Compiles
foo (1); // Compiles
foo (&f); // Compiles
foo (1, &f); // Compiles
foo (1, &f, &u); // Compiles
foo (&f, &u); // Compiles

foo (1, 2.); // Error!
foo (1, 2., &f); // Error!
foo (1, 2., &f, &u); // Error!
}

Yes, you will be able to pass pointers before numbers. But isn't that a better interface for whatever you're trying to do?



Related Topics



Leave a reply



Submit