Reputation: 118
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:
tuple
from args
tuple
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.
Upvotes: 6
Views: 1468
Reputation: 23497
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
Upvotes: 1
Reputation: 5387
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.
Upvotes: 3
Reputation: 303186
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; });
Upvotes: 4