For unit testing a C++17 framework that relies heavily on templates I tried to write helper template
classes which generate a Cartesian product of two sets of data types given by two tuples:
**Input**: std::tuple <A, B> std::tuple<C,D,E> **Expected output**: Cartesian product of the two tuples: std::tuple<std::tuple<A,C>, std::tuple<A,D>, std::tuple<A,E>, std::tuple<B,C>, std::tuple<B,D>, std::tuple<B,E>>
I am aware that Boost MP11 offers such features but I would not like to include a dependency on yet another library just for testing purposes. So far I came up with a pretty straight forward solution which though requires the class to be default-constructible (Try it here!):
template <typename T1, typename T2,
typename std::enable_if_t<is_tuple_v<T1>>* = nullptr,
typename std::enable_if_t<is_tuple_v<T2>>* = nullptr>
class CartesianProduct {
protected:
CartesianProduct() = delete;
CartesianProduct(CartesianProduct const&) = delete;
CartesianProduct(CartesianProduct&&) = delete;
CartesianProduct& operator=(CartesianProduct const&) = delete;
CartesianProduct& operator=(CartesianProduct&&) = delete;
template <typename T, typename... Ts,
typename std::enable_if_t<std::is_default_constructible_v<T>>* = nullptr,
typename std::enable_if_t<(std::is_default_constructible_v<Ts> && ...)>* = nullptr>
static constexpr auto innerHelper(T, std::tuple<Ts...>) noexcept {
return std::make_tuple(std::make_tuple(T{}, Ts{}) ...);
}
template <typename... Ts, typename T,
typename std::enable_if_t<std::is_default_constructible_v<T>>* = nullptr,
typename std::enable_if_t<(std::is_default_constructible_v<Ts> && ...)>* = nullptr>
static constexpr auto outerHelper(std::tuple<Ts...>, T) noexcept {
return std::tuple_cat(innerHelper(Ts{}, T{}) ...);
}
public:
using type = std::decay_t<decltype(outerHelper(std::declval<T1>(), std::declval<T2>()))>;
};
template <typename T1, typename T2>
using CartesianProduct_t = typename CartesianProduct<T1, T2>::type;
Also when trying to instantiate a list of template classes in a similar way (try it here) I have to make the same assumption: I can't apply it to classes which have a protected
/private
constructor (without a friend
declaration) and are not-default-constructible.
Is it possible to lift the restriction of default constructability without turning to an std::integer_sequence
and an additional helper class? From what I understand it is not possible to use std::declval<T>()
directly in the methods innerHelper
and outerHelper
(which would solve my issue), as it seems to not be an unevaluated expression anymore. At least GCC complains then about static assertion failed: declval() must not be used!
while it seems to compile fine with Clang.
Thank you in advance!