Related
The existing tuple overloads of std::get are limited to return exactly 1 element by index, or type. Imagine having a tuple with multiple elements of the same type and you want to extract all of them into a new tuple.
How to achieve a version of std::get<T> that returns a std::tuple of all occurrences of given type(s) like this?
template<typename... Ts_out>
constexpr std::tuple<Ts_out...> extract_from_tuple(auto& tuple) {
// fails in case of multiple occurences of a type in tuple
return std::tuple<Ts_out...> {std::get<Ts_out>(tuple)...};
}
auto tuple = std::make_tuple(1, 2, 3, 'a', 'b', 'c', 1.2, 2.3, 4.5f);
auto extract = extract_from_tuple <float, double>(tuple);
// expecting extract == std::tuple<float, double, double>{4.5f, 1.2, 2.3}
Not sure if std::make_index_sequence for accessing each element by std::get<index> and std::is_same_v per element could work.
Only C++17 is needed here.
std::tuple_cat is one of my favorite tools.
Use a std::index_sequence to chew through the tuple
Use a specialization to pick up either a std::tuple<> or a std::tuple<T> out of the original tuple, for each indexed element.
Use std::tuple_cat to glue everything together.
The only tricky part is checking if each tuple element is wanted. To do that, put all the wanted types into its own std::tuple, and use a helper class for that part, too.
#include <utility>
#include <tuple>
#include <iostream>
// Answer one simple question: here's a type, and a tuple. Tell me
// if the type is one of the tuples types. If so, I want it.
template<typename wanted_type, typename T> struct is_wanted_type;
template<typename wanted_type, typename ...Types>
struct is_wanted_type<wanted_type, std::tuple<Types...>> {
static constexpr bool wanted=(std::is_same_v<wanted_type, Types>
|| ...);
};
// Ok, the ith index in the tuple, here's its std::tuple_element type.
// And wanted_element_t is a tuple of all types we want to extract.
//
// Based on which way the wind blows we'll produce either a std::tuple<>
// or a std::tuple<tuple_element_t>.
template<size_t i, typename tuple_element_t,
typename wanted_element_t,
bool wanted=is_wanted_type<tuple_element_t, wanted_element_t>::wanted>
struct extract_type {
template<typename tuple_type>
static auto do_extract_type(const tuple_type &t)
{
return std::tuple<>{};
}
};
template<size_t i, typename tuple_element_t, typename wanted_element_t>
struct extract_type<i, tuple_element_t, wanted_element_t, true> {
template<typename tuple_type>
static auto do_extract_type(const tuple_type &t)
{
return std::tuple<tuple_element_t>{std::get<i>(t)};
}
};
// And now, a simple fold expression to pull out all wanted types
// and tuple-cat them together.
template<typename wanted_element_t, typename tuple_type, size_t ...i>
auto get_type_t(const tuple_type &t, std::index_sequence<i...>)
{
return std::tuple_cat( extract_type<i,
typename std::tuple_element<i, tuple_type>::type,
wanted_element_t>::do_extract_type(t)... );
}
template<typename ...wanted_element_t, typename ...types>
auto get_type(const std::tuple<types...> &t)
{
return get_type_t<std::tuple<wanted_element_t...>>(
t, std::make_index_sequence<sizeof...(types)>());
}
int main()
{
std::tuple<int, const char *, double> t{1, "alpha", 2.5};
std::tuple<double, int> u=get_type<int, double>(t);
std::cout << std::get<0>(u) << " " << std::get<1>(u) << std::endl;
std::tuple<int, int, int, char, char, char, double, double, float> tt;
auto uu=get_type<float, double>(tt);
static_assert(std::is_same_v<decltype(uu),
std::tuple<double, double, float>>);
return 0;
}
With Boost.Mp11:
template <typename... Ts, typename Tuple>
auto extract_from_tuple(Tuple src) {
// the indices [0, 1, 2, ..., N-1]
using Indices = mp_iota<mp_size<Tuple>>;
// the predicate I -> Tuple[I]'s type is one of {Ts...}
using P = mp_bind<
mp_contains,
mp_list<Ts...>,
mp_bind<mp_at, Tuple, _1>>;
// the indices that satisfy P
using Chosen = mp_filter_q<P, Indices>;
// now gather all the appropriate elements
return [&]<class... I>(mp_list<I...>){
return std::tuple(std::get<I::value>(src)...);
}(Chosen{});
}
Demo.
If we want to use tuple_cat, a concise version of that:
template <typename... Ts, typename Tuple>
constexpr auto extract_from_tuple2(Tuple src) {
auto single_elem = []<class T>(T e){
if constexpr (mp_contains<mp_list<Ts...>, T>::value) {
return std::tuple<T>(e);
} else {
return std::tuple<>();
}
};
return std::apply([&](auto... e){
return std::tuple_cat(single_elem(e)...);
}, src);
}
Demo.
The idea of implementation is as follows (although boost.Mp11 may only need a few lines).
Take extract_from_tuple<float, double>, where tuple is tuple<int, char, char, double, double, float> as an example, for each type, we can first calculate its corresponding indices in the tuple, which is 5 for float and 3, 4 for double, then extract elements base on the indices and construct a tuple with same types, finally use tuple_cat to concatenate them together
#include <array>
#include <tuple>
#include <utility>
template<typename T, class Tuple>
constexpr auto
extract_tuple_of(const Tuple& t) {
constexpr auto N = std::tuple_size_v<Tuple>;
constexpr auto indices = []<std::size_t... Is>
(std::index_sequence<Is...>) {
std::array<bool, N> find{
std::is_same_v<std::tuple_element_t<Is, Tuple>, T>...
};
std::array<std::size_t, find.size()> indices{};
std::size_t size{};
for (std::size_t i = 0, j = 0; j < find.size(); j++) {
size += find[j];
if (find[j])
indices[i++] = j;
}
return std::pair{indices, size};
}(std::make_index_sequence<N>{});
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
return std::tuple(std::get<indices.first[Is]>(t)...);
}(std::make_index_sequence<indices.second>{});
};
template<typename... Ts_out, class Tuple>
constexpr auto
extract_from_tuple(const Tuple& t) {
return std::tuple_cat(extract_tuple_of<Ts_out>(t)...);
}
Demo
constexpr auto tuple = std::make_tuple(1, 2, 3, 'a', 'b', 'c', 1.2, 2.3, 4.5f);
constexpr auto extract1 = extract_from_tuple<float, double>(tuple);
constexpr auto extract2 = extract_from_tuple<int>(tuple);
constexpr auto extract3 = extract_from_tuple<long>(tuple);
static_assert(extract1 == std::tuple<float, double, double>{4.5f, 1.2, 2.3});
static_assert(extract2 == std::tuple<int, int, int>{1, 2, 3});
static_assert(extract3 == std::tuple<>{});
Yet another solution (the shortest?), which, after inspection, is basically Barry's but without the "mp_contains":
template<typename ... Ts>
constexpr auto extract_from_tuple(auto tuple)
{
auto get_element = [](auto el) {
if constexpr ((std::is_same_v<decltype(el), Ts> || ...)) {
return std::make_tuple(std::move(el));
}
else {
return std::make_tuple();
}
};
return std::apply([&](auto ... args){
return std::tuple_cat(get_element(std::move(args)) ...);}, std::move(tuple));
}
Here is the positive result -- note however that the ordering comes from the tuple and not the template argument list:
int main()
{
static_assert( extract_from_tuple <float, double>(std::make_tuple(1, 2, 3, 'a', 'b', 'c', 1.2, 2.3, 4.5f)) == std::tuple<double, double, float>{1.2, 2.3, 4.5f});
}
I know that I can use SFINAE to disable generation of templated functions based on a condition, but that doesn't really work in this case. I want to initialize an array at compile-time that should contain values that matches a condition. Something like this:
template <std::size_t i, class ... Types, class ... Group>
constexpr auto fetch_match(const std::tuple<Group...>& candidates)
{
if constexpr (is_match<std::tuple<Group...>, i, Types...>())
{
auto& group = std::get<i>(candidates);
return group.template get<Types...>();
}
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr std::array views {
(fetch_match<indices, Types...>(candidates), ...),
};
return views;
}
I know the code above is wrong and doesn't compile. If the condition isn't filled, then I want the fold expression to not generate that function call. How would I do that?
This question might be an XY-problem, so here's a the problem in more detail.
I have a Registry that contains Groups of heterogeneous data. I want to be able to query all groups that contains the specified sub list of types. For example, for (const auto& view : registry.get<char, short, int>()) should yield an array with views of the groups that contain char, short and int. I've created a mcve below. The problem with the current code is that I have to first create the array and then copy the views, which I'd like to avoid.
#include <tuple>
#include <array>
#include <utility>
#include <type_traits>
#include <iostream>
template <typename T, typename... Ts>
constexpr bool contains = (std::is_same<T, Ts>{} || ...);
template <typename Subset, typename Set>
constexpr bool is_subset_of = false;
template <typename... Ts, typename... Us>
constexpr bool is_subset_of<std::tuple<Ts...>, std::tuple<Us...>> = (contains<Ts, Us...> && ...);
template <typename ... T>
struct View
{
const char* name_of_group; // For debugging.
std::tuple<T...> data;
};
template <typename ... Ts>
struct Group
{
using type_set = std::tuple<Ts...>;
static const char* name; // For debugging.
std::tuple<Ts...> data;
explicit Group(Ts... values) : data(values...) {}
template <typename ... Us>
[[nodiscard]] View<Us...> get() const noexcept
{
return { this->name, std::make_tuple(std::get<Us>(this->data)...) };
}
};
template <class Groups, std::size_t i, class ... Types>
constexpr bool is_match()
{
using group_type = std::tuple_element_t<i, Groups>;
bool match = is_subset_of<std::tuple<Types...>, typename group_type::type_set>;
return match;
}
template <std::size_t i, class ... Types, class ... Group, class Array>
constexpr void add_matches(const std::tuple<Group...>& candidates, Array& matches, std::size_t& index)
{
if constexpr (is_match<std::tuple<Group...>, i, Types...>())
{
auto& group = std::get<i>(candidates);
matches[index++] = group.template get<Types...>();
}
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr std::size_t size = (is_match<std::tuple<Group...>, indices, Types...>() + ... + 0);
std::array<View<Types...>, size> views {};
std::size_t index = 0;
(add_matches<indices, Types...>(candidates, views, index), ...);
return views;
}
template <typename ... Group>
class Registry
{
public:
explicit Registry(Group... groups) : groups(groups...) {}
template <typename ... T>
auto get()
{
constexpr auto indices = std::index_sequence_for<Group...>{};
return get_matches<T...>(this->groups, indices);
}
private:
std::tuple<Group...> groups;
};
using A = Group<char>;
using B = Group<char, short>;
using C = Group<char, short, int>;
using D = Group<char, short, int, long long>;
// Giving the classes names for debugging purposes.
template<> const char* A::name = "A";
template<> const char* B::name = "B";
template<> const char* C::name = "C";
template<> const char* D::name = "D";
int main()
{
auto registry = Registry(A{0}, B{1,1}, C{2,2,2}, D{3,3,3,3});
// Should yield an array of size 2 with View<char, short, int>,
// one from group C and one from Group D.
for (const auto& view : registry.get<char, short, int>())
{
std::cout << "View of group: " << view.name_of_group << std::endl;
std::cout << "char: " << int(std::get<char>(view.data)) << std::endl;
std::cout << "short: " << std::get<short>(view.data) << std::endl;
std::cout << "int: " << std::get<int>(view.data) << std::endl;
}
}
Trying the suggestion in the comments, the following code is as far as I got.
template <class Groups, std::size_t i, class ... Types>
constexpr bool is_match()
{
using group_type = std::tuple_element_t<i, Groups>;
bool match = is_subset_of<std::tuple<Types...>, typename group_type::type_set>;
return match;
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto build_view_array(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
std::array views {
std::get<indices>(candidates).template get<Types...>()...
};
return views;
}
template <std::size_t i, class Groups, class TypeSet, std::size_t ... x>
constexpr auto get_matching_indices()
{
if constexpr (is_match<Groups, i, TypeSet>())
return std::index_sequence<x..., i>{};
else
return std::index_sequence<x...>{};
}
template <std::size_t i, std::size_t j, std::size_t ... rest, class Groups, class TypeSet, std::size_t ... x>
constexpr auto get_matching_indices()
{
if constexpr (is_match<Groups, i, TypeSet>())
return get_matching_indices<j, rest..., Groups, TypeSet, i, x...>();
else
return get_matching_indices<j, rest..., Groups, TypeSet, x...>();
}
template <class ... Types, class ... Group, std::size_t ... indices>
constexpr auto get_matches(const std::tuple<Group...>& candidates, std::index_sequence<indices...>)
{
constexpr auto matching_indices = get_matching_indices<indices..., std::tuple<Group...>, std::tuple<Types...>>();
constexpr auto views = build_view_array<Types...>(candidates, matching_indices);
return views;
}
It feels like it should work, but it won't compile due to the following error:
/Users/tedkleinbergman/Programming/ECS/temp.cpp:76:39: error: no matching function for call to 'get_matching_indices'
constexpr auto matching_indices = get_matching_indices<indices..., std::tuple<Group...>, std::tuple<Types...>>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/tedkleinbergman/Programming/ECS/temp.cpp:92:16: note: in instantiation of function template specialization 'get_matches<char, short, int, Group<char>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> , 0, 1, 2, 3>' requested here
return get_matches<T...>(this->groups, indices);
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:118:38: note: in instantiation of function template specialization 'Registry<Group<char>, Group<char, short>, Group<char, short, int>, Group<char, short, int, long long> >::get<char, short, int>' requested here
for (const auto& view : registry.get<char, short, int>())
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:57:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'Groups'
constexpr auto get_matching_indices()
^
/Users/tedkleinbergman/Programming/ECS/temp.cpp:65:16: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'rest'
constexpr auto get_matching_indices()
^
1 error generated.
First, start with an index_sequence filter:
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index = {};
template<std::size_t...Is, std::size_t...Js>
constexpr std::index_sequence<Is...,Js...> concatenate( std::index_sequence<Is...>, std::index_sequence<Js...> ) {
return {};
}
template <class Test>
constexpr auto filter_sequence(std::index_sequence<> sequence, Test test) {
return sequence;
}
template<std::size_t I0, std::size_t...Is, class Test>
constexpr auto filter_sequence( std::index_sequence<I0, Is...>, Test test )
{
constexpr auto tail = filter_sequence( std::index_sequence<Is...>{}, test );
if constexpr ( test(index<I0>) ) {
return concatenate( std::index_sequence<I0>{}, tail );
} else {
return tail;
}
}
we then use these primitives.
template <class Group, class ... Types>
constexpr auto get_match_indexes()
{
constexpr auto test = [](auto I){ return is_match<Group, I, Types...>(); };
constexpr auto indexes = std::make_index_sequence< std::tuple_size_v<Group> >{};
constexpr auto retval = filter_sequence( indexes, test );
return retval;
}
template<class ... Types, class Group, std::size_t...Is>
std::array<sizeof...Is, View<Types...>> get_matches(const Group& candidates, std::index_sequence<Is...> ) {
return {{
std::get<Is>(candidates).template get<Types...>(), ...
}};
}
template<class ... Types, class Group>
std::array<sizeof...Is, View<Types...>> get_matches(const Group& candidates ) {
return get_matches<Types...>( candidates, get_match_indexes<Group, Types...>() );
}
or something like that.
Note that some compilers may need to replace is_match<Group, I, Types...>() with is_match<Group, decltype(I)::value, Types...>().
There may be typos. This uses c++17 at the least.
filter_sequence uses O(n^2) template symbol length and O(n) recursive template instantiation depth. It can be improved to O(n lg n) length and O(lg n) depth with a tricky code; basically, you need to split Is... into As... and Bs... down the middle and recurse that way.
Here is a log-depth split of an index sequence:
template<class A, class B>
struct two_things {
A a;
B b;
};
template<class A, class B>
two_things(A,B)->two_things<A,B>;
template<class Seq>
constexpr auto split_sequence( index_t<0>, Seq seq ) {
return two_things{ std::index_sequence<>{}, seq };
}
template<std::size_t I0, std::size_t...Is>
constexpr auto split_sequence( index_t<1>, std::index_sequence<I0, Is...> seq ) {
return two_things{ std::index_sequence<I0>{}, std::index_sequence<Is...>{} };
}
template<std::size_t N, class Seq>
constexpr auto split_sequence( index_t<N>, Seq seq ) {
constexpr auto step1 = split_sequence( constexpr_index<N/2>, seq );
constexpr auto step2 = split_sequence( constexpr_index<N-N/2>, step1.b );
return two_things{ concatenate(step1.a, step2.a), step2.b };
}
template<std::size_t...Is>
constexpr auto halve_sequence( std::index_sequence<Is...> seq ) {
return split( index< (sizeof...(Is)) / 2u >, seq );
}
(two_things exists as a many-many-many times lighter tuple or pair than the std one).
That in turn lets you improve filter sequence.
template<std::size_t I, class Test>
constexpr auto filter_sequence( std::index_sequence<I> seq, Test test )
{
if constexpr ( test(constexpr_index<I>) ) {
return seq;
} else {
return std::index_sequence<>{};
}
}
template<std::size_t...Is, class Test>
constexpr auto filter_sequence( std::index_sequence<Is...> seq, Test test )
{
constexpr auto split = halve_sequence( seq );
constexpr auto head = filter_sequence( split.a, test );
constexpr auto tail = filter_sequence( split.b, test );
return concatenate(head, tail);
}
this version should compile faster and use less memory, especially for large numbers of elements. But you should start with the simpler one above, because (as I noted) there are probably plenty of tpyos.
Live example.
I have the following function:
template<class T>
T Check(int index);
How can I write a function, CheckTuple, which, given a tuple type, populates a tuple with calls to Check?
For example:
CheckTuple< std::tuple<int, float, std::string> >()
would return the following tuple:
std::make_tuple( Check<int>(1), Check<float>(2), Check<std::string>(3) )
The other questions I see involve unpacking a given tuple, not building one up this way.
Implementing what you're looking for becomes pretty simple using C++14's integer_sequence. If you don't have that available, here's a C++11 implementation written by Jonathan Wakely.
template<typename Tuple, int... I>
Tuple CallCheck(std::integer_sequence<int, I...>)
{
return std::make_tuple(Check<typename std::tuple_element<I, Tuple>::type>(I)...);
}
template<typename Tuple>
Tuple CheckTuple()
{
return CallCheck<Tuple>(std::make_integer_sequence<int, std::tuple_size<Tuple>::value>());
}
// Use it as
auto tup = CheckTuple<std::tuple<int, float, std::string>>();
Live demo
Here is my working test implementation. (Perhaps someone has an idea of how to improve upon it in terms of conciseness. Can I get rid of TupleInfo somehow?)
#include <typeinfo>
#include <tuple>
#include <iostream>
template<class T>
T Check(int i) {
std::cout << "getting a " << typeid(T).name() << " at position " << i << std::endl;
return T();
}
template<typename Signature>
struct TupleInfo;
template<class T, class... Args>
struct TupleInfo< std::tuple<T, Args...> > {
using Head = T;
using Tail = std::tuple<Args...>;
};
template<int N, class Tuple>
struct TupleChecker {
static Tuple CheckTuple() {
auto t = std::make_tuple(Check<typename TupleInfo<Tuple>::Head>(N));
return std::tuple_cat(t, TupleChecker<N+1, typename TupleInfo<Tuple>::Tail >::CheckTuple());
}
};
template<int N>
struct TupleChecker<N, std::tuple<> > {
static std::tuple<> CheckTuple() {
return std::tuple<>();
}
};
template<class Tuple>
Tuple CheckTuple() {
return TupleChecker<1, Tuple>::CheckTuple();
}
int main() {
std::tuple<> t0 = CheckTuple<std::tuple<> >();
std::tuple<int> t1 = CheckTuple<std::tuple<int> >();
std::tuple<int, float, std::string> t2 = CheckTuple<std::tuple<int, float, std::string> >();
return 0;
}
I recently ran across this puzzle, was finally able to struggle out a hacky answer (using index arrays), and wanted to share it (answer below). I am sure there are answers that use template recursion and answers that use boost; if you're interested, please share other ways to do this. I think having these all in one place may benefit others and be useful for learning some of the cool C++11 template metaprogramming tricks.
Problem:
Given two tuples of equal length:
auto tup1 = std::make_tuple(1, 'b', -10);
auto tup2 = std::make_tuple(2.5, 2, std::string("even strings?!"));
How do you create a function that will "zip" the two tuples into a heterogeneous tuple of pairs?
std::tuple<
std::pair<int, double>,
std::pair<char, int>,
std::pair<int, std::string> > result =
tuple_zip( tup1, tup2 );
Where
std::get<0>(result) == std::make_pair(1, 2.5);
std::get<1>(result) == std::make_pair('b', 2);
std::get<2>(result) == std::make_pair(-10, std::string("even strings?!"));
First, a quick overview of index arrays:
template<std::size_t ...S>
struct seq { };
// And now an example of how index arrays are used to print a tuple:
template <typename ...T, std::size_t ...S>
void print_helper(std::tuple<T...> tup, seq<S...> s) {
// this trick is exceptionally useful:
// ((std::cout << std::get<S>(tup) << " "), 0) executes the cout
// and returns 0.
// { 0... } expands (because the expression has an S in it),
// returning an array of length sizeof...(S) full of zeros.
// The array isn't used, but it's a great hack to do one operation
// for each std::size_t in S.
int garbage[] = { ((std::cout << std::get<S>(tup) << " "), 0)... };
std::cout << std::endl;
}
And now to use our print_helper function:
int main() {
print_helper(std::make_tuple(10, 0.66, 'h'), seq<0,1,2>() );
return 0;
}
Typing seq<0,1,2> can be a bit of a pain, though. So we can use template recursion to create a class to generate seqs, so that gens<3>::type is the same as seq<0,1,2>:
template<std::size_t N, std::size_t ...S>
struct gens : gens<N-1, N-1, S...> { };
template<std::size_t ...S>
struct gens<0, S...> {
typedef seq<S...> type;
};
int main() {
print_helper(std::make_tuple(10, 0.66, 'h'), gens<3>::type() );
return 0;
}
Since the N in gens<N>::type will always be the number of elements in the tuple, you can wrap print_helper to make it easier:
template <typename ...T>
void print(std::tuple<T...> tup) {
print_helper(tup, typename gens<sizeof...(T)>::type() );
}
int main() {
print(std::make_tuple(10, 0.66, 'h'));
return 0;
}
Note that the template arguments can be deduced automatically (typing all of that out would be a pain wouldn't it?).
Now, the tuple_zip function:
As before, start with the helper function:
template <template <typename ...> class Tup1,
template <typename ...> class Tup2,
typename ...A, typename ...B,
std::size_t ...S>
auto tuple_zip_helper(Tup1<A...> t1, Tup2<B...> t2, seq<S...> s) ->
decltype(std::make_tuple(std::make_pair(std::get<S>(t1),std::get<S>(t2))...)) {
return std::make_tuple( std::make_pair( std::get<S>(t1), std::get<S>(t2) )...);
}
The code is a little tricky, particularly the trailing return type (the return type is declared as auto and provided with -> after the parameters are defined). This lets us avoid the problem of even defining what the return type will be, by simply declaring it returns the expression used in the function body (if x and y are ints, delctype(x+y) is resolved at compile time as int).
Now wrap it in a function that provides the appropriate seq<0, 1...N> using gens<N>::type:
template <template <typename ...> class Tup1,
template <typename ...> class Tup2,
typename ...A, typename ...B>
auto tuple_zip(Tup1<A...> t1, Tup2<B...> t2) ->
decltype(tuple_zip_helper(t1, t2, typename gens<sizeof...(A)>::type() )) {
static_assert(sizeof...(A) == sizeof...(B), "The tuple sizes must be the same");
return tuple_zip_helper( t1, t2, typename gens<sizeof...(A)>::type() );
}
Now you can use it as specified in the question:
int main() {
auto tup1 = std::make_tuple(1, 'b', -10);
auto tup2 = std::make_tuple(2.5, 2, std::string("even strings?!"));
std::tuple<
std::pair<int, double>,
std::pair<char, int>,
std::pair<int, std::string> > x = tuple_zip( tup1, tup2 );
// this is also equivalent:
// auto x = tuple_zip( tup1, tup2 );
return 0;
}
And finally, if you provide a << operator for std::pair you can use the print function we defined above to print the zipped result:
template <typename A, typename B>
std::ostream & operator << (std::ostream & os, const std::pair<A, B> & pair) {
os << "pair("<< pair.first << "," << pair.second << ")";
return os;
}
int main() {
auto tup1 = std::make_tuple(1, 'b', -10);
auto tup2 = std::make_tuple(2.5, 2, std::string("even strings?!"));
auto x = tuple_zip( tup1, tup2 );
std::cout << "zipping: ";
print(tup1);
std::cout << "with : ";
print(tup2);
std::cout << "yields : ";
print(x);
return 0;
}
The output is:
zipping: 1 b 10
with : 2.5 2 even strings?!
yields : pair(1,2.5) pair(b,2) pair(10,even strings?!)
Like std::array, std::tuple is defined at compile time, and so it can be used to generate more optimizable code (more information is known at compile time compared to containers like std::vector and std::list). So even though it's sometimes a bit of work, you can sometimes use it to make fast and clever code. Happy hacking!
Edit:
As requested, allowing tuples of different sizes and padding with null pointers:
template <typename T, std::size_t N, std::size_t ...S>
auto array_to_tuple_helper(const std::array<T, N> & arr, seq<S...> s) -> decltype(std::make_tuple(arr[S]...)) {
return std::make_tuple(arr[S]...);
}
template <typename T, std::size_t N>
auto array_to_tuple(const std::array<T, N> & arr) -> decltype( array_to_tuple_helper(arr, typename gens<N>::type()) ) {
return array_to_tuple_helper(arr, typename gens<N>::type());
}
template <std::size_t N, template <typename ...> class Tup, typename ...A>
auto pad(Tup<A...> tup) -> decltype(tuple_cat(tup, array_to_tuple(std::array<std::nullptr_t, N>()) )) {
return tuple_cat(tup, array_to_tuple(std::array<std::nullptr_t, N>()) );
}
#define EXTENSION_TO_FIRST(first,second) ((first)>(second) ? (first)-(second) : 0)
template <template <typename ...> class Tup1, template <typename ...> class Tup2, typename ...A, typename ...B>
auto pad_first(Tup1<A...> t1, Tup2<B...> t2) -> decltype( pad<EXTENSION_TO_FIRST(sizeof...(B), sizeof...(A)), Tup1, A...>(t1) ) {
return pad<EXTENSION_TO_FIRST(sizeof...(B), sizeof...(A)), Tup1, A...>(t1);
}
template <template <typename ...> class Tup1, template <typename ...> class Tup2, typename ...A, typename ...B>
auto diff_size_tuple_zip(Tup1<A...> t1, Tup2<B...> t2) ->
decltype( tuple_zip( pad_first(t1, t2), pad_first(t2, t1) ) ) {
return tuple_zip( pad_first(t1, t2), pad_first(t2, t1) );
}
And BTW, you're going to need this now to use our handy print function:
std::ostream & operator << (std::ostream & os, std::nullptr_t) {
os << "null_ptr";
return os;
}
It isn't extremely difficult to do this for an arbitrary amount of tuples.
One way is to make a function that collects all elements at a specific index from N tuples into a new tuple. Then have another function which collects those tuples into a new tuple for each index in the original tuples.
All of that can be done relatively simply by expanding expressions with parameter packs, without any recursive functions.
#include <cstddef>
#include <tuple>
namespace detail {
// Describe the type of a tuple with element I from each input tuple.
// Needed to preserve the exact types from the input tuples.
template<std::size_t I, typename... Tuples>
using zip_tuple_at_index_t = std::tuple<std::tuple_element_t<I, std::decay_t<Tuples>>...>;
// Collect all elements at index I from all input tuples as a new tuple.
template<std::size_t I, typename... Tuples>
zip_tuple_at_index_t<I, Tuples...> zip_tuple_at_index(Tuples && ...tuples) {
return {std::get<I>(std::forward<Tuples>(tuples))...};
}
// Create a tuple with the result of zip_tuple_at_index for each index.
// The explicit return type prevents flattening into a single tuple
// when sizeof...(Tuples) == 1 or sizeof...(I) == 1 .
template<typename... Tuples, std::size_t... I>
std::tuple<zip_tuple_at_index_t<I, Tuples...>...> tuple_zip_impl(Tuples && ...tuples, std::index_sequence<I...>) {
return {zip_tuple_at_index<I>(std::forward<Tuples>(tuples)...)...};
}
}
// Zip a number of tuples together into a tuple of tuples.
// Take the first tuple separately so we can easily get its size.
template<typename Head, typename... Tail>
auto tuple_zip(Head && head, Tail && ...tail) {
constexpr std::size_t size = std::tuple_size_v<std::decay_t<Head>>;
static_assert(
((std::tuple_size_v<std::decay_t<Tail>> == size) && ...),
"Tuple size mismatch, can not zip."
);
return detail::tuple_zip_impl<Head, Tail...>(
std::forward<Head>(head),
std::forward<Tail>(tail)...,
std::make_index_sequence<size>()
);
}
See it in action here: https://wandbox.org/permlink/EQhvLPyRfDrtjDMw
I used some C++14/17 features, but nothing essential. The most difficult part to replace would be the fold expression for checking the tuple sizes. That would probably have to become a recursive check.
If I have std::tuple<double, double, double> (where the type is homogeneous), is there a stock function or constructor to convert to std::array<double>?
Edit:: I was able to get it working with recursive template code (my draft answer posted below). Is this the best way to handle this? It seems like there would be a stock function for this... Or if you have improvements to my answer, I'd appreciate it. I'll leave the question unanswered (after all, I want a good way, not just a workable way), and would prefer to select someone else's [hopefully better] answer.
Thanks for your advice.
Converting a tuple to an array without making use of recursion, including use of perfect-forwarding (useful for move-only types):
#include <iostream>
#include <tuple>
#include <array>
template<int... Indices>
struct indices {
using next = indices<Indices..., sizeof...(Indices)>;
};
template<int Size>
struct build_indices {
using type = typename build_indices<Size - 1>::type::next;
};
template<>
struct build_indices<0> {
using type = indices<>;
};
template<typename T>
using Bare = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template<typename Tuple>
constexpr
typename build_indices<std::tuple_size<Bare<Tuple>>::value>::type
make_indices()
{ return {}; }
template<typename Tuple, int... Indices>
std::array<
typename std::tuple_element<0, Bare<Tuple>>::type,
std::tuple_size<Bare<Tuple>>::value
>
to_array(Tuple&& tuple, indices<Indices...>)
{
using std::get;
return {{ get<Indices>(std::forward<Tuple>(tuple))... }};
}
template<typename Tuple>
auto to_array(Tuple&& tuple)
-> decltype( to_array(std::declval<Tuple>(), make_indices<Tuple>()) )
{
return to_array(std::forward<Tuple>(tuple), make_indices<Tuple>());
}
int main() {
std::tuple<double, double, double> tup(1.5, 2.5, 4.5);
auto arr = to_array(tup);
for (double x : arr)
std::cout << x << " ";
std::cout << std::endl;
return 0;
}
The C++17 solution is a short one:
template<typename tuple_t>
constexpr auto get_array_from_tuple(tuple_t&& tuple)
{
constexpr auto get_array = [](auto&& ... x){ return std::array{std::forward<decltype(x)>(x) ... }; };
return std::apply(get_array, std::forward<tuple_t>(tuple));
}
Use it as
auto tup = std::make_tuple(1.0,2.0,3.0);
auto arr = get_array_from_tuple(tup);
EDIT: forgot to sprinkle constexpr anywhere :-)
You can do it non-recursively:
#include <array>
#include <tuple>
#include <redi/index_tuple.h> // see below
template<typename T, typename... U>
using Array = std::array<T, 1+sizeof...(U)>;
template<typename T, typename... U, unsigned... I>
inline Array<T, U...>
tuple_to_array2(const std::tuple<T, U...>& t, redi::index_tuple<I...>)
{
return Array<T, U...>{ std::get<I>(t)... };
}
template<typename T, typename... U>
inline Array<T, U...>
tuple_to_array(const std::tuple<T, U...>& t)
{
using IndexTuple = typename redi::make_index_tuple<1+sizeof...(U)>::type;
return tuple_to_array2(t, IndexTuple());
}
See https://gitlab.com/redistd/redistd/blob/master/include/redi/index_tuple.h for my implementation of index_tuple, something like that is useful for working with tuples and similar variadic templates. A similar utility was standardised as std::index_sequence in C++14 (see index_seq.h for a standalone C++11 implementation).
I would return the array instead of populating it by reference, so that auto can be used to make the callsite cleaner:
template<typename First, typename... Rem>
std::array<First, 1+sizeof...(Rem)>
fill_array_from_tuple(const std::tuple<First, Rem...>& t) {
std::array<First, 1+sizeof...(Rem)> arr;
ArrayFiller<First, decltype(t), 1+sizeof...(Rem)>::fill_array_from_tuple(t, arr);
return arr;
}
// ...
std::tuple<double, double, double> tup(0.1, 0.2, 0.3);
auto arr = fill_array_from_tuple(tup);
Realistically, NRVO will eliminate most performance concerns.
#include <iostream>
#include <tuple>
#include <array>
template<class First, class Tuple, std::size_t N, std::size_t K = N>
struct ArrayFiller {
static void fill_array_from_tuple(const Tuple& t, std::array<First, N> & arr) {
ArrayFiller<First, Tuple, N, K-1>::fill_array_from_tuple(t, arr);
arr[K-1] = std::get<K-1>(t);
}
};
template<class First, class Tuple, std::size_t N>
struct ArrayFiller<First, Tuple, N, 1> {
static void fill_array_from_tuple( const Tuple& t, std::array<First, N> & arr) {
arr[0] = std::get<0>(t);
}
};
template<typename First, typename... Rem>
void fill_array_from_tuple(const std::tuple<First, Rem...>& t, std::array<First, 1+sizeof...(Rem)> & arr) {
ArrayFiller<First, decltype(t), 1+sizeof...(Rem)>::fill_array_from_tuple(t, arr);
}
int main() {
std::tuple<double, double, double> tup(0.1, 0.2, 0.3);
std::array<double, 3> arr;
fill_array_from_tuple(tup, arr);
for (double x : arr)
std::cout << x << " ";
return 0;
}
Even if title says C++11 I think C++14 solution is worth sharing (since everyone searching for problem will come up here anyway). This one can be used in compile time (constexpr proper) and much shorter than other solutions.
#include <array>
#include <tuple>
#include <utility>
#include <iostream>
// Convert tuple into a array implementation
template<typename T, std::size_t N, typename Tuple, std::size_t... I>
constexpr decltype(auto) t2a_impl(const Tuple& a, std::index_sequence<I...>)
{
return std::array<T,N>{std::get<I>(a)...};
}
// Convert tuple into a array
template<typename Head, typename... T>
constexpr decltype(auto) t2a(const std::tuple<Head, T...>& a)
{
using Tuple = std::tuple<Head, T...>;
constexpr auto N = sizeof...(T) + 1;
return t2a_impl<Head, N, Tuple>(a, std::make_index_sequence<N>());
}
int main()
{
constexpr auto tuple = std::make_tuple(-1.3,2.1,3.5);
constexpr auto array = t2a(tuple);
static_assert(array.size() == 3, "err");
for(auto k : array)
std::cout << k << ' ';
return 0;
}
Example
In C++14 you can do this to generate an array:
auto arr = apply([](auto... n){return std::array<double, sizeof...(n)>{n...};}, tup);
Full code:
#include<experimental/tuple> // apply
#include<cassert>
//using std::experimental::apply; c++14 + experimental
using std::apply; // c++17
template<class T, class Tuple>
auto to_array(Tuple&& t){
return apply([](auto... n){return std::array<T, sizeof...(n)>{n...};}, t); // c++14 + exp
}
int main(){
std::tuple<int, int, int> t{2, 3, 4};
auto a = apply([](auto... n){return std::array<int, 3>{n...};}, t); // c++14 + exp
assert( a[1] == 3 );
auto a2 = apply([](auto... n){return std::array<int, sizeof...(n)>{n...};}, t); // c++14 + exp
assert( a2[1] == 3 );
auto a3 = apply([](auto... n){return std::array{n...};}, t); // c++17
assert( a3[1] == 3 );
auto a4 = to_array<int>(t);
assert( a4[1] == 3 );
}
Note that the subtle problem is what to do when all types in the source tuple are not the same, should it fail to compile? use implicit conversion rules? use explicit conversion rules?