I'm trying to create a function to generate the Cartesian product of a variable number of input ranges, using the style of the STL. My basic format is that the function accepts a fixed range and the start of an output range, then a variadic number of bidirectional input iterators.
template <
typename BidirectionalIterator,
typename OutputIterator,
typename... Args
>
void cartesian_product(
BidirectionalIterator first,
BidirectionalIterator last,
OutputIterator result,
Args&&... args
);
My idea for the args is that I make a tuple out of it, then I iterate through that tuple to extract the elements. This would require me to follow a few basic steps:
Make a tuple from args
Dereference each iterator in the newly created tuple
Increment each iterator in the tuple in sequence, so that we get all possible combinations of the values in the ranges.
To elaborate on step 3: if we had two sets A = {0, 1} and B = {2, 3}, the Cartesian product A x B = {(0, 2), (0, 3), (1, 2), (1, 3)}.
I can do the first step like:
auto arg_tuple = std::make_tuple(std::forward<Args>(args)...);
The second step, I'm not too sure about. I think I will have somehow push_back elements to a temporary tuple, then set *result equal to that temporary tuple. I was a little inspired by the way that ostream accomplishes this, so I think this could come in handy:
template <typename Tuple, typename T>
auto operator<<(const Tuple &lhs, const T &rhs)
-> decltype(std::tuple_cat(lhs, std::make_tuple(rhs)))
{
return std::tuple_cat(lhs, std::make_tuple(rhs));
}
The third step is probably pretty trivial. I could combine something like this:
template <typename T>
auto pre_increment(T &x) -> decltype(++x) {
return ++x;
}
with one of the 3,000 implementations of for_each for a tuple that are on here.
Odds are that I'm not correctly leveraging C++14 for this. My education has been entirely on the less-difficult parts of C++11 so far.
If you're tempted to recommend I use boost::fusion for this, thanks, but I would prefer to not use it.
In C++17, we get std::apply(). A possible C++14 implementation is found on that link. We can then implement fmap for a tuple as:
template <class Tuple, class F>
auto fmap(Tuple&& tuple, F f) {
return apply([=](auto&&... args){
return std::forward_as_tuple(f(std::forward<decltype(args)>(args))...);
}, std::forward<Tuple>(tuple));
}
With that:
auto deref_all = fmap(iterators, [](auto it) -> decltype(auto) { return *it; });
auto incr_all = fmap(iterators, [](auto it) { return ++it; });
Here's what I've come up with:
#include <iostream>
#include <tuple>
#include <vector>
template <typename T, typename B>
bool increment(const B& begins, std::pair<T,T>& r) {
++r.first;
if (r.first == r.second) return true;
return false;
}
template <typename T, typename... TT, typename B>
bool increment(const B& begins, std::pair<T,T>& r, std::pair<TT,TT>&... rr) {
++r.first;
if (r.first == r.second) {
r.first = std::get<std::tuple_size<B>::value-sizeof...(rr)-1>(begins);
return increment(begins,rr...);
}
return false;
}
template <typename OutputIterator, typename... Iter>
void cartesian_product(
OutputIterator out,
std::pair<Iter,Iter>... ranges
) {
const auto begins = std::make_tuple(ranges.first...);
for (;;) {
out = { *ranges.first... };
if (increment(begins, ranges...)) break;
}
}
struct foo {
int i;
char c;
float f;
};
int main(int argc, char* argv[]) {
std::vector<int> ints { 1, 2, 3 };
std::vector<char> chars { 'a', 'b', 'c' };
std::vector<float> floats { 1.1, 2.2, 3.3 };
std::vector<foo> product;
cartesian_product(
std::back_inserter(product),
std::make_pair(ints.begin(), ints.end()),
std::make_pair(chars.begin(), chars.end()),
std::make_pair(floats.begin(), floats.end())
);
for (const auto& x : product)
std::cout << x.i << ' ' << x.c << ' ' << x.f << std::endl;
}
The cartesian_product function has a slightly different signature than yours, but it should be straightforward to write a wrapper.
Since the ranges you pass in may potentially have different extents, I'd suggest you pass both begin and end, as in my example.
I recently came up with the solution that allows to invoke a callable object (e.g., lambda) for any combination of the Cartesian product of the input ranges defined by iterators. The lambda makes elements of input ranges accessible by values or by references. Exemplary usage:
std::vector<int> vector = { 1, 2, 3 };
std::set<double> set = { -1.0, -2.0 };
std::string string = "abcd";
bool array[] = { true, false };
std::cout << std::boolalpha;
cartesian_product([](const auto& v1, const auto& v2, const auto& v3, const auto& v4){
std::cout << "(" << v1 << ", " << v2 << ", " << v3 << ", " << v4 << ")\n";
},
std::begin(vector), std::end(vector),
std::begin(set), std::end(set),
std::begin(string), std::end(string),
std::begin(array), std::end(array)
);
I haven't found a solution with such a (natural) syntax (the style of the STL you ask for). The cartesian_product function is in my case built upon C++17 std::apply as follows:
template <typename F, typename... Ts>
void cartesian_product_helper(F&& f, std::tuple<Ts...> t) { std::apply(f, t); }
template <typename F, typename... Ts, typename Iter, typename... TailIters>
void cartesian_product_helper(
F&& f, std::tuple<Ts...> t, Iter b, Iter e, TailIters... tail_iters)
{
for (auto iter = b; iter != e; ++iter)
cartesian_product_helper(
std::forward<F>(f), std::tuple_cat(t, std::tie(*iter)), tail_iters...);
}
template <typename F, typename... Iters>
void cartesian_product(F&& f, Iters... iters) {
cartesian_product_helper(std::forward<F>(f), std::make_tuple(), iters...);
}
It's relatively simple - it iterates over all ranges recursively and in each iteration, it appends the reference to the corresponding dereferenced iterator (i.e., range item) to the tuple. When the tuple is complete (has references to items from all levels), then the callable object is invoked and those references from the tuple are used as arguments.
Just I'm not sure whether this is the most efficient way, so any suggestions for improvement would be helpful.
Live demo is here: https://wandbox.org/permlink/lgPlpKXRkPuTtqo8
Related
I have a collection of equally-sized vectors and want to provide an interface for the user to obtain an iterator range over a subset of these vectors.
The following example shows the problematic line inside getRange: its idea is to receive a bunch of types (specifying the types of vectors) and equally many indices (specifying the locations of the vectors). The code compiles, but the problem is that i++ never gets executed as intended, i.e., the call is always with just i (which equals 0). This will also lead to runtime errors via boost::get if the user tries to get distinct types.
This is probably a well-known issue. What's a neat solution to it?
#include <vector>
#include <boost/variant.hpp>
#include <boost/range/combine.hpp>
template <typename... T>
struct VectorHolder
{
template<typename X>
using Iterator = typename std::vector<X>::const_iterator;
std::vector<boost::variant<std::vector<T>...> > vecs_;
template <typename X>
auto begin(int idx) const {
return boost::get<std::vector<X> >(vecs_.at(idx)).cbegin();
}
template <typename X>
auto end(int idx) const {
return boost::get<std::vector<X> >(vecs_.at(idx)).cend();
}
};
template <typename... T, typename VectorHolder>
auto getRange(const VectorHolder& vh, const std::vector<int>& idx)
{
assert(sizeof...(T) == idx.size());
// Fetch a boost::iterator_range over the specified indices
std::size_t i = 0;
std::size_t j = 0;
// PROBLEM: i and j not incremented as intended
return boost::combine(
boost::iterator_range<VectorHolder::Iterator<T>>(
vh.begin<T>(idx[i++]), vh.end<T>(idx[j++]))...);
}
int main()
{
VectorHolder<bool, int, double> vh;
vh.vecs_.push_back(std::vector<int>(5, 5));
vh.vecs_.push_back(std::vector<bool>(5));
vh.vecs_.push_back(std::vector<double>(5, 2.2));
vh.vecs_.push_back(std::vector<int>(5, 1));
const std::vector<int> idx = { 0, 3 };
for (auto t : getRange<int, int>(vh, idx))
{
std::cout << t.get<0>() << " " << t.get<1>() << "\n";
}
}
std::index_sequence helps:
template <typename... Ts, typename VectorHolder, std::size_t ... Is>
auto getRange(const VectorHolder& vh, const std::vector<int>& idx, std::index_sequence<Is...>)
{
assert(sizeof...(Ts) == idx.size());
return boost::combine(
boost::iterator_range<typename VectorHolder::template Iterator<Ts>>(
vh.template begin<Ts>(idx[Is]), vh.template end<Ts>(idx[Is]))...);
}
template <typename... Ts, typename VectorHolder>
auto getRange(const VectorHolder& vh, const std::vector<int>& idx)
{
return getRange<Ts...>(vh, idx, std::index_sequence_for<Ts...>());
}
Demo
I'd like to write a helper function like:
template <typename F, typename Range1, typename Range2>
auto helper(const Range1& left, const Range2& right, F&& pred)
{
using namespace std; // for cbegin/cend and ADL
return pred(cbegin(left), cend(left), cbegin(right), cend(right));
}
It works well for containers:
std::vector<int> v1 = {1,2,3,4,5,6};
std::vector<int> v2 = {5,4,3,2,1,6};
std::cout << helper(v1, v2, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;
but it fails to deduce initializer_list-s (example):
std::cout << helper({1,2,3,4,5,6}, {5,4,3,2,1,6}, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;
Is there an idiomatic way to rewrite helper so that it deduces both containers and initializer_list-s?
I can't come up with anything better than overloads for all combinations of container and initializer_list.
I think the fundamental problem here is that a braced-init-list like { 1, 2, 3 } is just an initializer and not an object of type std::initializer_list<T>. It can potentially be used to initialize an object of some given type. But it's not an object of any type itself. And there doesn't seem to be anything in the rules for function template argument deduction that would allow you to get an std::initializer_list<T> from a braced-init-list argument unless your function parameter was already declared to be some sort of std::initializer_list<T> to begin with.
So I'm afraid writing those overloads will be the simplest solution…
Here is the best I can do:
template<class X>
struct Range {
X* container;
Range(X& x):container(std::addressof(x)) {}
Range(X&& x):container(std::addressof(x)) {} // dangerous, but hey
auto begin() const { using std::begin; return begin(*container); }
auto end() const { using std::end; return end(*container); }
auto cbegin() const { using std::cbegin; return cbegin(*container); }
auto cend() const { using std::cend; return cend(*container); }
};
template<class T>
struct Range<std::initializer_list<T>> {
using X=std::initializer_list<T>;
X container;
Range(X x):container(x) {}
auto begin() const { using std::begin; return begin(container); }
auto end() const { using std::end; return end(container); }
auto cbegin() const { using std::cbegin; return cbegin(container); }
auto cend() const { using std::cend; return cend(container); }
};
template<class T>
Range( std::initializer_list<T> ) -> Range<std::initializer_list< T >>;
template<class C1, class C2>
void foo( Range<C1> r1, Range<C2> c2 ) {}
test code:
Range r = {{'a', 'b', 'c'}};
(void)r;
std::vector v = {1,2,3};
foo( Range{{'a','b','c'}}, Range{v} );
you have to cast the arguments to Range manually for this to work at the call site, because class template arguement deduction doesn't work on function arguments.
We might be able to attack it differently.
template <typename F, typename Range1, typename Range2>
auto helper(const Range1& left, const Range2& right, F&& pred)
change the above syntax to a chained-call.
helper(v1)({1,2,3})[pred];
that reduces the 2^n explosion into 2. Not much of a help with 2 overloads, but still...
template<class...Ts>
struct helper_t {
std::tuple<Ts&&...> containers;
template<class T>
helper_t<T, Ts...> operator()(T&& t)&& {
return { /* append-move containers and t into one tuple */ };
}
template<class T>
helper_t<std::initializer_list<T>, Ts...> operator()(std::initializer_list<T> t)&& {
return { /* append-move containers and t into one tuple */ };
}
template<class F>
decltype(auto) operator[](F&& f)&& {
return std::move(*this).apply_impl(
std::make_index_sequence<sizeof...(Ts)>{},
std::forward<F>(f)
);
}
private:
template<std::size_t...Is, class F>
decltype(auto) apply_impl( std::index_sequence<Is...>, F&& f ) && {
using std::cbegin; using std::cend;
using std::get;
return std::forward<F>(f)(
cbegin( get<Is>(std::move(containers)) ), cend( get<Is>(std::move(containers)) )
);
}
};
static constexpr const helper_t<> helper;
I left appending the tuples as an exercise.
helper( container1 )( {1,2,3} )( container2 )[ some_lambda ];
is the syntax.
Please explicitly specify that the arguments you are passing is the initializer_list like this:-
std::cout << helper(v1, std::initializer_list<int>{5,4,3,2,1,6}, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;
std::cout << helper(std::initializer_list<int>{1,2,3,4,5,6}, std::initializer_list<int>{5,4,3,2,1,6}, [](const auto&... args){ return std::is_permutation(args...);}) << std::endl;
This is the way you can pass the initializer_list else you need to overload for all combinations of container and initializer_list
As explained by Michael Kenzel, the problem is that a braced-init-list isn't an std::intializer_list.
So I'm agree with Michael (+1): I don't see a way to write a single template function that can deduce both STL containers and (as std::initilizer_list<T>) braced-init-lists.
But if you can accept to add an helper() helper function for your helper() function, you can use the fact that a braced-init-list can be deduced as a C-style array.
So you can write a helper() specific version for C-style arrays that can convert the C-style arrays in std::initializer_list (or std::array, or... see you) or, in my example, you can simply call your original helper() function passing the arrays but explicating the types.
I mean: you can add this helper() helper function
template <typename F, typename R1, std::size_t S1,
typename R2, std::size_t S2>
auto helper (R1 const (&r1)[S1], R2 const (&r2)[S2], F && pred)
{ return helper<F, R1[S1], R2[S2]>(r1, r2, std::forward<F>(pred)); }
Drawback: this works if both ranges are STL containers or if both ranges are C-style arrays (or braced-init-lists); if you can have a STL container and a C-style array, you have to write other two helper() helper function: one for (only) the first range and one for (only) the second.
The following is a full working example
#include <vector>
#include <iostream>
#include <algorithm>
template <typename F, typename Range1, typename Range2>
auto helper (Range1 const & left, Range2 const & right, F && pred)
{ return pred(std::cbegin(left), std::cend(left),
std::cbegin(right), std::cend(right)); }
template <typename F, typename R1, std::size_t S1,
typename R2, std::size_t S2>
auto helper (R1 const (&r1)[S1], R2 const (&r2)[S2], F && pred)
{ return helper<F, R1[S1], R2[S2]>(r1, r2, std::forward<F>(pred)); }
int main ()
{
std::vector<int> v1 = {1,2,3,4,5,6};
std::vector<int> v2 = {5,4,3,2,1,6};
std::cout << helper(v1, v2,
[](auto const & ... args){ return std::is_permutation(args...);})
<< std::endl;
std::cout << helper({1, 2, 3, 4, 5, 6}, {5, 4, 3, 2, 1, 6},
[](auto const &... args){ return std::is_permutation(args...);})
<< std::endl;
}
If I have the following program:
#include <vector>
#include <set>
template<class T, class U>
void AddToContainer(T& container, U value)
{
container.push_back(value);
}
int main(char**, int)
{
std::vector<int> v;
AddToContainer(v, 1);
std::set<int> s;
AddToContainer(s, 1);
return 0;
}
How can I make the adding to the container generic? Since std::set hasn't got a push_back but only insert, this will fail to compile.
You could use expression SFINAE with a dummy parameter to check if push_back() works:
template <class C, class V>
auto append(C& container, V&& value, int)
-> decltype(container.push_back(std::forward<V>(value)), void())
{
container.push_back(std::forward<V>(value));
}
template <class C, class V>
void append(C& container, V&& value, ...)
{
container.insert(std::forward<V>(value));
}
which your function will just forward to:
template <class C, class V>
void AddToContainer(C& container, V&& value) {
append(container, std::forward<V>(value), 0);
}
If push_back() is a valid expression, the first overload will be preferred since int is a better match for 0 than ... If push_back() isn't a valid expression, then there's only one viable overload.
Whether this is actually a good idea or not is a separate question.
I believe that all* C++ containers (though not the container adapters like priority_queue) have a version of insert that looks like this:
iterator insert(iterator location, T&& value)
For the sequence collections, the location is the actual location; for associative collections (like map and unordered_map), the iterator is a "hint" parameter (for instance, to help a map insert an element quickly if you already know precisely where it belongs in sorted order). However, providing an invalid hint doesn't cause any invalid behavior, so a valid generic insert for C++ collections would be:
template<C, T>
void insert(C& collection, T&& value) {
collection.insert(collection.end(), std::forward<T>(value));
}
* It looks like forward_list is the only one that doesn't have this method, which makes sense.
C++20 style:
template<typename C, typename V>
requires requires (C& c, V&& v) { c.push_back(std::forward<V>(v)); }
auto AddToContainer(C& container, V&& value) {
return container.push_back(std::forward<V>(value));
}
template<typename C, typename V>
requires (requires (C& c, V&& v) { c.insert(c.end(), std::forward<V>(v)); } &&
!requires(C& c, V&& v) { c.push_back(std::forward<V>(v)); })
auto AddToContainer(C& container, V&& value) {
return container.insert(container.end(), std::forward<V>(value));
}
or more succinct but with worse diagnostics:
template<typename C, typename V>
auto AddToContainer(C& container, V&& value)
{
if constexpr (requires (C& c, V&& v) { c.push_back(std::forward<V>(v)); })
return container.push_back(std::forward<V>(value));
else
return container.insert(container.end(), std::forward<V>(value));
}
If you are not worried about the order of elements inserted in the set,
template<typename Container, typename value>
void addelement(Container& C, value v)
{
std::fill_n(std::inserter(C,C.end()), 1,v);
}
int main()
{
std::vector<int> v;
addelement(v, 2);
addelement(v, 4);
addelement(v, 6);
std::set<int> s;
addelement(s, 8);
addelement(s, 6);
addelement(s, 4);
addelement(s, 8);
std::cout << "Vector elements :: " << std::endl;
for (auto item : v)
std::cout << item << std::endl;
std::cout << "Set elements :: " << std::endl;
for (auto item : s)
std::cout << item << std::endl;
return 0;
}
I would like to ask if it's possible to define for each algorithm (like in STL) that would take multiple functions as input arguments and evaluate them in left to right order?
template <typename Iterator, typename ... Args>
void for_each(Iterator begin, Iterator end, Args ... args) {
// apply functions passed in Args... to range [begin,end)
}
How would I access those functions passed by Args? Is it possible only with some template recursion?
You can use something like this:
#include <iostream>
#include <utility>
#include <algorithm>
template <typename Iterator, typename F1>
void for_each(Iterator begin, Iterator end, F1 f1)
{
std::for_each(begin, end, f1);
}
template <typename Iterator, typename F1, typename... Fun>
void for_each(Iterator begin, Iterator end, F1 f1, Fun... fs)
{
std::for_each(begin, end, f1);
for_each(begin, end, fs...);
}
int main()
{
std::array<int, 5> a = {1,2,3,4,5};
auto f1 = [](int i){std::cout << "f1: " << i << " ";};
auto f2 = [](int i){std::cout << "f2: " << i << " ";};
for_each(a.begin(), a.end(), f1, f2);
}
output:
f1: 1 f1: 2 f1: 3 f1: 4 f1: 5 f2: 1 f2: 2 f2: 3 f2: 4 f2: 5
live example
You don't have to do some special template trickery for this, just define a recursion like below:
template <typename Iterator, typename F>
void recurse(Iterator first, Iterator last, F f) {
if(first != last) {
f(*(first++));
}
}
template <typename Iterator, typename F, typename ...Args>
void recurse(Iterator first, Iterator last, F f, Args ...args) {
if(first != last) {
f(*(first++));
recurse(first, last, args...);
}
}
template <typename Iterator, typename ...Args>
void variadic_for_each(Iterator first, Iterator last, Args ...args) {
recurse(first, last, args...);
}
LIVE DEMO
I wrote something more general – some pseudocode showing what can it do:
auto funcs = mk<TupleOfFunctions>(f, g, h); // f, g, h -- callables
// mk is my helper function, I defined it in # Usage # section
funcs(arg) == h(g(f(arg))); // similiar to pipe in shell
// echo arg | f | g | h
In plain english, it's template of class. It's constructor that takes any amount of callables. Calling it's instance will return argument transformed by every function which constructor received.
And, because it's callable, you should be able to pass it to for_each.
Code
#include <utility>
// 1 //
template <typename T, typename U>
struct PairOfFunctions: std::pair<T, U> {
using std::pair<T, U>::pair;
template <typename... Args>
auto operator() (Args&&... args) {
return std::pair<T, U>::second(std::pair<T, U>::first(args...));
}
};
template<typename...>
struct TupleOfFunctions;
// 2 //
template<typename T, typename... U>
struct TupleOfFunctions<T, U...>: PairOfFunctions<T, TupleOfFunctions<U...> >{
using PairOfFunctions<T, TupleOfFunctions<U...> >::PairOfFunctions;
TupleOfFunctions(T t, U... u):
PairOfFunctions<T, TupleOfFunctions<U...> >(
t,
TupleOfFunctions<U...>(u...)
)
{}
};
// 3 //
template<>
struct TupleOfFunctions<>{
template <typename T>
T operator() (T t) { // probably not optimal, too lazy to overload
return t;
}
};
Some explanation
PairOfFunctions – subclass of pair:
mk<PairOfFunctions>(f, g)(arg) == g(f(arg));
TupleOfFunctions – Generalization of PairOfFunctions, takes one or more callables.
TupleOfFunctions<>: special case – takes no functions, returns copy of argument.
Usage
My example is dumb, feel free to replace it.
// My universal helper
template<template <typename...> class T, typename... Args>
auto mk(Args... args){
return T<Args...>{args...};
}
int main()
{
auto a = mk<TupleOfFunctions>(
[](int a) {return a*2;},
[](int a) {return a + 10;},
[](int a) {return a / 2;}
);
std::cout << a(4) << '\n'; // (4 * 2 + 10) / 2
}
See it working online
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.