How to Expand a Tuple into Variadic Template Function'S Arguments

Expand a tuple TYPE into a variadic template?

You can expand a tuple easily by introducing a layer of indirection, which could be a lambda (C++20) or template function (C++11). E.g.

std::tuple<int, float, char> t;
[]<typename... Ts>(std::tuple<Ts...>)
{
// use `Ts...` here
}(t);

In your case:

template <typename T>
struct type_wrapper { using type = T; };

template<typename... Args>
std::vector<type_info> get_type_vector(type_wrapper<std::tuple<Args...>>)
{
return { type_info<Args>()... };
}

get_type_vector(type_wrapper<std::tuple<int, float, char>>{});

The type_wrapper class prevents useless instantiations of tuples at run-time. You can use std::type_identity in C++20.

How do I strip a tuple back into a variadic template list of types?

No, this is not possible. Argument packs are the result of type deduction, and they can't be produced in other contexts.

You could do something similar to what you're asking for this way:

template<template<typename...> class T, typename>
struct instantiate_with_arg_pack { };

template<template<typename...> class T, typename... Ts>
struct instantiate_with_arg_pack<T, std::tuple<Ts...>>
{
using type = T<Ts...>;
};

template<typename... Ts>
struct vct { };

int main()
{
using U = std::tuple<int,char,std::string>;
using X = vct<int,char,std::string>;
using Y = instantiate_with_arg_pack<vct, U>::type;
}

Actually, you don't need to hold the argument pack in a tuple: any variadic class template is OK:

template<template<typename...> class T, typename>
struct instantiate_with_arg_pack { };

template<
template<typename...> class T,
template<typename...> class U, // <===
typename... Ts
>
struct instantiate_with_arg_pack<T, U<Ts...>>
// ^^^^^^^^
{
using type = T<Ts...>;
};

template<typename... Ts>
struct vct { };

int main()
{
using U = std::tuple<int,char,std::string>;
using X = vct<int,char,std::string>;
using Y = instantiate_with_arg_pack<vct, X>::type;
// ^

// Won't fire
static_assert(
std::is_same<Y, vct<int,char,std::string>>::value,
"Error!");
}

And here is a live example.

Build a specific tuple from a function's variadic argument

As usual std::index_sequence to the rescue (C++14, but can be implemented in C++11):

// C++14 alias
template <typename T>
using decay_t = typename std::decay<T>::type;

template <std::size_t I, typename T>
using tuple_element_t = typename std::tuple_element<I, T>::type;

template <std::size_t...Is, typename Tuple>
Map<decay_t<tuple_element_t<2 * Is + 1, Tuple>>...>
MakeMapImpl(std::index_sequence<Is...>, Tuple&& t)
{
return std::make_tuple(std::make_pair(std::get<2 * Is>(t),
std::get<2 * Is + 1>(t))...);
}

template <typename... Args>
auto MakeMap(Args&&... args)
-> decltype(MakeMapImpl(std::make_index_sequence<sizeof...(Args) / 2>(), std::make_tuple(args...)))
{
static_assert(sizeof...(Args) % 2 == 0, "!");

return MakeMapImpl(std::make_index_sequence<sizeof...(Args) / 2>(), std::make_tuple(args...));
}

Demo

How do I create an std::tuple from a variadic template parameter?

You need to expand both:

  • template parameters pack (Args...)

and

  • function parameters pack (data...):

so it should be:

    vec.push_back(std::tuple<Args...>(data...));

Or shorter form, use make_tuple:

    vec.push_back(std::make_tuple(data...));

syntax to unpack tuple on parameter pack and variadic template

std::apply let's you call a function with the tuple elements as parameters. You can use that with a lambda.

template<typename ... Args>
constexpr bool all_values_equal(std::tuple<Args...> tuple) {
auto cmp = [](auto&& first, auto&&... args) {
return ((first == args) && ...);
};

return std::apply(cmp, tuple);
}

To make the all_types_equal check we can use partial specialization. Something like this.

template <typename First, typename ... Rest>
constexpr bool all_types_equal_impl = (std::is_same_v<First, Rest> && ...);;

template <typename First, typename ... Rest>
constexpr bool all_types_equal_impl<std::tuple<First, Rest...>> = (std::is_same_v<First, Rest> && ...);

template <typename... Args>
constexpr bool all_types_equal() {
return all_types_equal_impl<Args...>;
}

We can refer to the template variable directly, so wrapping it in a function is not really required if we don't want to.

How to: Extend C++14 template function to variadic template, arguments

Variadic templates have a mechanism not too dissimilar to Python's ability to pass a function positional arguments and to then expand those positional arguments into a sequence of values. C++'s mechanism is a bit more powerful and more pattern based.

So let's take it from the top. You want to take an arbitrary series of ranges (containers is too limiting):

template <typename ...Ranges>
auto pyzip(Ranges&& ...ranges)

The use of ... here specifies the declaration of a pack. This particular function declaration declares two "packs": a pack of types named Ranges, and a parameter pack named ranges.

So, the first thing you need to do is get a series of begin and end iterators. Since these iterators can be of arbitrary types, an array won't do; it has to be stored in a tuple. Each element of the tuple needs to be initialized by taking ranges, getting that element, and calling begin on it. Here's how you do that:

auto begin_its = std::make_tuple(begin(std::forward<Ranges>(ranges))...);

This use of ... is called a pack expansion. The expression to its left contains one or more packs. And the ... takes that expression and turns it into a comma-separated sequence of values, substituting each corresponding member of the pack where the pack is listed. The expression being expanded is begin(std::forward<Ranges>(ranges)). And we use both ranges and Ranges here, so both packs are expanded together (and must be of the same size).

We expand this pack into the arguments for make_tuple, so that function gets one parameter for each element in the packs.

Of course, the same thing works for end too.

Next, you want to store copies(?) of the elements from each range in a vector<tuple>. Well, this requires that we first figure out what the value type of the range is. That's easy enough using another pack expansion over the typedef you used in your example:

using vector_elem = std::tuple<std::decay_t<decltype(*begin(std::forward<Ranges>(ranges)))>...>;
std::vector<vector_elem> result;

Note that in this case, the ... doesn't apply to an "expression", but it does the same thing: repeating the std::decay_t part for each element of ranges.

Next, we need to compute the size of our eventual list. That's... actually surprisingly difficult. One might think that you could just use begin_its and end_its, and just iterate over them, or use some pack expansion shenanigans with them. But no, C++ doesn't let you do either of those. Those tuples aren't packs, and you can't (easily) treat them as such.

It's actually easier to just recompute the begin/end iterators and take the difference, all in one expression.

auto size = std::min({std::distance(begin(std::forward<Ranges>(ranges)), end(std::forward<Ranges>(ranges)))...});
result.reserve(std::size_t(size));

Well, "easier" in terms of lines of code, not so much readability;

std::min here takes an initializer-list of values to compute the minimum of.

For our loop, rather than going until the iterators have reached an end state, it's easier to just loop over a count.

But that's really just pushing off the final problem. Namely, we have this tuple of iterators, and we need to perform 2 operations on their members: dereferencing, and incrementing. And it doesn't really matter which one we're doing; it's equally hard in C++.

Oh, it's perfectly doable. You just need a new function.

See, you cannot access an element of a tuple with a runtime index. And you cannot loop over compile-time values. So you need some way to get a pack containing, not parameters or types, but integer indices. This pack of indices can be unpacked into the get<Index> call for the tuple to access its contents.

C++17 gave us a convenient std::apply function for doing exactly this kind of thing. Unfortunately, this is C++14, so we have to write one:

namespace detail {
template <class F, class Tuple, std::size_t... I>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<I...>)
{
return f(std::get<I>(std::forward<Tuple>(t))...);
}
} // namespace detail

template <class F, class Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t)
{
return detail::apply_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple>>::value>{});
}

apply here takes a function and a tuple, and unpacks the tuple into the function's arguments, returning whatever the function returns.

So, back in our function, we can use apply to do what we need: indirection, then incrementing of each element. And generic lambdas allow us to deal effectively with inserting the values into the vector through emplace_back:

for (decltype(size) ix = 0; ix < size; ++ix)
{
apply([&result](auto&& ...its) mutable
{
result.emplace_back(*its...); //No need for redundant `make_tuple`+copy.
}, begin_its);

apply([](auto& ...its)
{
int unused[] = {0, (++its, 0)...};
}, begin_its);
}

unused and its initializer is a confused way for C++ to just execute an expression on every item in a pack, discarding the results. Unfortunately, it's the most straightforward way to do that in C++14.

Here's the whole working example.

C++11: I can go from multiple args to tuple, but can I go from tuple to multiple args? [duplicate]

Try something like this:

// implementation details, users never invoke these directly
namespace detail
{
template <typename F, typename Tuple, bool Done, int Total, int... N>
struct call_impl
{
static void call(F f, Tuple && t)
{
call_impl<F, Tuple, Total == 1 + sizeof...(N), Total, N..., sizeof...(N)>::call(f, std::forward<Tuple>(t));
}
};

template <typename F, typename Tuple, int Total, int... N>
struct call_impl<F, Tuple, true, Total, N...>
{
static void call(F f, Tuple && t)
{
f(std::get<N>(std::forward<Tuple>(t))...);
}
};
}

// user invokes this
template <typename F, typename Tuple>
void call(F f, Tuple && t)
{
typedef typename std::decay<Tuple>::type ttype;
detail::call_impl<F, Tuple, 0 == std::tuple_size<ttype>::value, std::tuple_size<ttype>::value>::call(f, std::forward<Tuple>(t));
}

Example:

#include <cstdio>
int main()
{
auto t = std::make_tuple("%d, %d, %d\n", 1,2,3);
call(std::printf, t);
}

With some extra magic and using std::result_of, you can probably also make the entire thing return the correct return value.



Related Topics



Leave a reply



Submit