How to Iterate Over a Packed Variadic Template Argument List

How can I iterate over a packed variadic template argument list?

If you want to wrap arguments to any, you can use the following setup. I also made the any class a bit more usable, although it isn't technically an any class.

#include <vector>
#include <iostream>

struct any {
enum type {Int, Float, String};
any(int e) { m_data.INT = e; m_type = Int;}
any(float e) { m_data.FLOAT = e; m_type = Float;}
any(char* e) { m_data.STRING = e; m_type = String;}
type get_type() const { return m_type; }
int get_int() const { return m_data.INT; }
float get_float() const { return m_data.FLOAT; }
char* get_string() const { return m_data.STRING; }
private:
type m_type;
union {
int INT;
float FLOAT;
char *STRING;
} m_data;
};

template <class ...Args>
void foo_imp(const Args&... args)
{
std::vector<any> vec = {args...};
for (unsigned i = 0; i < vec.size(); ++i) {
switch (vec[i].get_type()) {
case any::Int: std::cout << vec[i].get_int() << '\n'; break;
case any::Float: std::cout << vec[i].get_float() << '\n'; break;
case any::String: std::cout << vec[i].get_string() << '\n'; break;
}
}
}

template <class ...Args>
void foo(Args... args)
{
foo_imp(any(args)...); //pass each arg to any constructor, and call foo_imp with resulting any objects
}

int main()
{
char s[] = "Hello";
foo(1, 3.4f, s);
}

It is however possible to write functions to access the nth argument in a variadic template function and to apply a function to each argument, which might be a better way of doing whatever you want to achieve.

How to Iterate over a parameter pack

To use a std::tuple to iterate over a parameter pack, you would usually use a std::index_sequence to introduce a new pack of indices, and use a fold expression to do the actual iteration. Something like this:

template<typename F, typename F2, typename... Args, std::size_t... I>
void func_impl(F& f, F2& f2, std::tuple<Args...> params, std::index_sequence<I...>) {
// This would be a fold expression over a comma in C++17:
/*
(([&]{}(
f(std::get<I*2>(params));
f2(std::get<I*2+1>(params));
)), ...);
*/
// C++14 version
using consume = int[];
(void) consume{ 0, [&]{
f(std::get<I*2>(params));
f2(std::get<I*2+1>(params));
return 0;
}()... };
}

template<typename F, typename F2, typename... Args>
void func(F f, F2 f2, Args&&... args) {
static_assert(sizeof...(args) % 2 == 0, "Must pass a multiple of 2 args to func");
func_impl(
f, f2,
std::forward_as_tuple(std::forward<Args>(args)...),
std::make_index_sequence<sizeof...(args) / 2>{}
);
}

But you can also iterate with recursion, which might be easier in your case:

template<typename F, typename F2>
void func(F&& f, F2&& f2) {
// base case: do nothing
}

template<typename F, typename F2, typename Arg1, typename Arg2, typename... Args>
void func(F&& f, F2&& f2, Arg1&& arg1, Arg2&& arg2, Args&&... args) {
// recursive case
f(arg1);
f2(arg2);
func(std::forward<F>(f), std::forward<F2>(f2), std::forward<Args>(args)...);
}

Iterating over a parameter pack

In C++17, you need additional level of indirection to get a pack of indices to get a pack of elements at those indices:

template<typename... Args, std::size_t... Is>
auto get_tuple_impl(const std::vector<std::size_t>& indices,
std::index_sequence<Is...>,
const Args&... args) {
return std::make_tuple(args[indices[Is]]...);
}

template<typename... Args>
auto get_tuple(const std::vector<std::size_t>& indices, const Args&... args) {
return get_tuple_impl(indices, std::index_sequence_for<Args...>(), args...);
}

In C++20, we could you a lambda function with template parameters invoked in-place:

template<typename... Args>
auto get_tuple(const std::vector<std::size_t>& indices, const Args&... args) {
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
return std::make_tuple(args[indices[Is]]...);
}(std::index_sequence_for<Args...>());
}

You might also want to add an assertion assert(indices.size() == sizeof...(Args)); or use std::array<std::size_t, N> type instead.

How to iterate through variadic template parameters to create variable number of local variables?

First, don't take your names as a vector. Take them as a parameter pack. And, moreover, take them as a pack of what you actually want:

bool is_valid() { return true; }
template <class T, class... Ts>
bool is_valid(T& arg, Ts&... args) {
return arg.valid() && is_valid(args...);
}

template <class T, class... Args>
T* create(Args&&... args) {
if (is_valid(args...)) {
return new T{args...};
}
else {
return nullptr;
}
}

And now just pass the right thing in:

create<T>(A{"nameA"}, B{"nameB"}, C{"nameC"});
create<T>(D{"nameD"}, E{"nameE"});

If, for some reason, you really want to separate the types and the names, you can do that too:

template <class T, class... Cs, class... As>
T* create_classes(As&&... args) {
return create<T>(Cs{std::forward<As>(args)}...);
}

How to iterate over Variadic template types (not arguments)?

In your example, in C++17, you might do:

template<typename... Ts>
class Subscriber
{
Subscriber()
{
auto f = [](auto data){ /* do something with data*/ };
(PubSub.Subscribe<Ts>(f), ...);
}
}

In C++11/14, you might to use more verbose way, such as:

(C++14 currently with your generic lambda)

template<typename... Ts>
class Subscriber
{
Subscriber()
{
auto f = [](auto data){ /* do something with data*/ };
int dummy[] = {0, (PubSub.Subscribe<Ts>(f), void(), 0)...};
static_cast<void>(dummy); // Avoid warning for unused variable.
}
}

How to iterate over the size of a parameter pack with a compile-time index parameter

You can define a helper function to use index_sequence to expand the elements of Tuple and assign values through fold expression.

template<class Tuple, class Message, std::size_t... Is>
void assign_tuple(Tuple& tuple, const Message& message, std::index_sequence<Is...>) {
((std::get<Is>(tuple) = message.getArg<std::tuple_element_t<Is, Tuple>(Is)), ...);
};

template <typename ...Args>
auto addCallback(void* handle, std::string address, std::function<void(Args...)> callback) {
return addMessageCallback(std::move(address), [callback](const ci::osc::Message& message) {
std::tuple<Args...> args;
assign_tuple(args, message, std::make_index_sequence<sizeof...(Args)>{});
std::apply(callback, std::move(args));
});
}

In C++20 you can just define template lambda inside the function to do this.

iterating over variadic template's type parameters

What Xeo said. To create a context for pack expansion I used the argument list of a function that does nothing (dummy):

#include <iostream>
#include <initializer_list>

template<class...A>
void dummy(A&&...)
{
}

template <class ...A>
void do_something()
{
dummy( (A::var = 1)... ); // set each var to 1

// alternatively, we can use a lambda:

[](...){ }((A::var = 1)...);

// or std::initializer list, with guaranteed left-to-right
// order of evaluation and associated side effects

auto list = {(A::var = 1)...};
}

struct S1 { static int var; }; int S1::var = 0;
struct S2 { static int var; }; int S2::var = 0;
struct S3 { static int var; }; int S3::var = 0;

int main()
{
do_something<S1,S2,S3>();
std::cout << S1::var << S2::var << S3::var;
}

This program prints 111.

How to iterate over variadic template pack during (pseudo?) runtime?

template<class=void,  std::size_t...Is >
auto indexer( std::index_sequence<Is...> ){
return [](auto&&f)->decltype(auto){
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto indexer(){
return indexer(std::make_index_sequence<N>{} );
}
template<std::size_t N>
void for_each( F&& f ) {
indexer<N>()( [&](auto...Is){
using discard=int[];
(void)discard{0,(void(
f(Is)
),0)...};
});
}

indexer gives you unpacked indexes.

for_each calls f with a compile time i value for each i up to N.

That will let you iterate over integers at compile time. To map integers at runtime to compile time:

template<std::size_t N, class F>
void pick( std::size_t I, F&& f ){
for_each<N>( [&](auto i){
if (I==i) f(i);
} );
}

This invokes f with a compile time version of I so long as it is less than N.

template<class...Args>
void read( std::string pattern, Args&...args ){
auto tied=std::tie(args...);
for (i = 0; i < length; i = format.find(i, opening_bracket))
pick<sizeof...(args)>( i, [&](auto i){
std::cin>>std::get<i>(tied);
} );
}
}

Now there is an implicit chain of ifs written by the above; you can replace with a jump table using a different technique.

Code not compiled; design is sound, but there are probably tyops. Indexer can be found with google (I have written it on SO before). I have written it as for_each directly, but I find the single pack version too useful. Here I needed the separate pack version. Pick just uses it.

Here is a jump table version of pick:

template<std::size_t N, class F>
void pick( std::size_t I, F&& f ){
indexer<N>()([&](auto...Is){
using table_f=void(*)(&f);
const table_f table[]={
+[](F&f){ f(decltype(Is){}); }...
};
table[I](f);
});
}

Bounds checking not included. This version does not require for_each, but some compilers break when asked o have a lambda with a parameter pack unexpanded inside a statement.



Related Topics



Leave a reply



Submit