I would like to write a function that computes a cartesian product of two tuples in C++17 (the tuples can be of type std::tuple
or std::pair
, or any other type that can be used with std::apply
). The tuples can be of different types and sizes, and the resulting std::tuple
has to contain all std::pair
s of elements, where the first
element of a pair is from the first tuple, and the second
element is from the second tuple. The function must be suitable for use in a constexpr
context.
My approach is to use std::apply
to effectively convert a tuple to a parameter pack, and then to recursively apply a generic lambda to it to build the result. Because a lambda cannot be explicitly recursive, I pass a reference to it to itself as an additional parameter self
.
#include <functional>
#include <tuple>
#include <utility>
template<class X, class Y>
constexpr auto cartesian_product(const X& tx, const Y& ty) {
if constexpr (std::tuple_size_v<X> == 0) return std::make_tuple();
else return std::apply([&](const auto& ... ys) {
return std::apply([&](const auto& ... xs) {
const auto recursive = [&](
const auto& self,
const auto& arg, const auto& ... args)
{
if constexpr (sizeof...(args) == 0)
return std::make_tuple(std::make_pair(arg, ys)...);
else return std::tuple_cat(
std::make_tuple(std::make_pair(arg, ys)...),
self(self, args...));
};
return recursive(recursive, xs...);
}, tx);
}, ty);
}
// Test
constexpr auto x = std::make_tuple('a', 2);
constexpr auto y = std::make_tuple(true, std::optional<long>{});
constexpr auto result = cartesian_product(x, y);
static_assert(result == std::make_tuple(
std::make_pair('a', true),
std::make_pair('a', std::optional<long>{}),
std::make_pair(2, true),
std::make_pair(2, std::optional<long>{})));
I would appreciate any comments or suggestions how to simplify or improve this code.
Update: A generalization to an arbitrary number of factors in the cartesian product (the components of the result are now tuple
s rather than pair
s):
#include <functional>
#include <tuple>
#include <utility>
constexpr std::tuple<std::tuple<>> cartesian_product() {
return {{}};
}
template<class X, class ... Y>
constexpr auto cartesian_product(const X& tx, const Y& ... ty) {
if constexpr (std::tuple_size_v<X> == 0) return std::make_tuple();
else return std::apply([&](const auto & ... tuples) {
return std::apply([&](const auto & ... xxs) {
const auto recursive = [&](
const auto& self,
const auto& x, const auto& ... xs)
{
auto tuple = std::make_tuple(
std::tuple_cat(std::make_tuple(x), tuples)...);
if constexpr (sizeof...(xs) == 0) return tuple;
else return std::tuple_cat(std::move(tuple), self(self, xs...));
};
return recursive(recursive, xxs...);
}, tx);
}, cartesian_product(ty...));
}
constexpr auto x = std::make_tuple('a', 2);
constexpr auto y = std::make_tuple(true, std::optional<long>{});
constexpr auto z = std::make_tuple(1L);
constexpr auto result = cartesian_product(x, y, z);
static_assert(result == std::make_tuple(
std::make_tuple('a', true, 1L),
std::make_tuple('a', std::optional<long>{}, 1L),
std::make_tuple(2, true, 1L),
std::make_tuple(2, std::optional<long>{}, 1L)));
I do not like std::make_tuple(x)
part, but do not see a better way to prepend an element to a tuple
.