Cartesian Product using Iterators and Variadic Templates

Viewed 942

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:

  1. Make a tuple from args
  2. Dereference each iterator in the newly created tuple
  3. 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.

3 Answers

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