We implement a function object that accepts a function, f, and a set of parameters. The function object will create the cartesian product of the parameter set, filter out the redundant parts, and call the f function with each of them:
- We only need to include the STL header that is needed for printing:
#include <iostream>
- Then, we define a simple helper function that prints a pair of values, and we begin implementing the main function:
static void print(int x, int y)
{
std::cout << "(" << x << ", " << y << ")n";
}
int main()
{
- The hard part starts now. We first implement a helper for the cartesian function that we are going to implement in the next step. This function accepts a parameter, f, which will be the print function when we use it later. The other parameters are x and the parameter pack rest. These contain the actual items of which we want to have the cartesian product. Look at the f(x, rest) expression: for x=1 and rest=2, 3, 4, this will result in calls such as f(1, 2); f(1, 3); f(1, 4);. The (x < rest) test is for removing redundancy in the generated pairs. We will look at this in more detail later:
constexpr auto call_cart (
[=](auto f, auto x, auto ...rest) constexpr {
(void)std::initializer_list<int>{
(((x < rest)
? (void)f(x, rest)
: (void)0)
,0)...
};
});
- The cartesian function is the most complex piece of code in this whole recipe. It accepts the parameter pack xs and returns a function object that captures it. The returned function object accepts a function object, f.
For a parameter pack, xs=1, 2, 3, the inner lambda expression will generate the following calls: call_cart(f, 1, 1, 2, 3); call_cart(f, 2, 1, 2, 3); call_cart(f, 3, 1, 2, 3);. From that range of calls, we can generate all the cartesian product pairs we need.
Note that we use the ... notation for expanding the xs parameter pack twice, which looks weird at first. The first occurrence of ... expands the entire xs parameter pack into the call_cart call. The second occurrence leads to multiple call_cart calls with a differing second parameter:
constexpr auto cartesian ([=](auto ...xs) constexpr {
return [=] (auto f) constexpr {
(void)std::initializer_list<int>{
((void)call_cart(f, xs, xs...), 0)...
};
};
});
- Now, let's generate the cartesian product of the numeric set 1, 2, 3 and print the pairs. Without the redundant pairs, this should result in the number pairs, (1, 2), (2, 3), and (1, 3). More combinations are not possible if we ignore the order and do not want the same number in one pair. This means that we do not want (1, 1), and consider (1, 2) and (2, 1) the same pair.
First, we let cartesian generate a function object that already contains all possible pairs and accepts our print function. Then, we use it to let our print function being called with all these pairs.
We declare the print_cart variable, constexpr, so we can guarantee that the function object it holds (and all the pairs it generates) is created at compile time:
constexpr auto print_cart (cartesian(1, 2, 3));
print_cart(print);
}
- Compiling and running yields the following output, just as expected. Play around with the code by removing the (x < xs) conditional in the call_cart function and see that we get the full cartesian product with redundant pairs and the same number pairs:
$ ./cartesian_product
(1, 2)
(1, 3)
(2, 3)