std::tuple 的笛卡尔积

Cartesian product of std::tuple

为了对严重依赖模板的 C++17 框架进行单元测试,我尝试编写帮助程序 template 生成两个数据类型的 笛卡尔积 由两个元组给出:

**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>>

我知道 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;

同样,在尝试以类似方式实例化模板列表 类 时 (try it here),我必须做出相同的假设:我无法将其应用于 类它有一个 protected/private 构造函数(没有 friend 声明)并且不可默认构造。

是否可以解除默认可构造性的限制而不转向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

提前致谢!

解决方法之一是省略函数定义并直接使用 decltype 推断 return 类型:

template<typename T1, typename T2>
class CartesianProduct {
  template<typename T, typename... Ts>
  static auto innerHelper(T&&, std::tuple<Ts...>&&) 
  -> decltype(
       std::make_tuple(
         std::make_tuple(std::declval<T>(), std::declval<Ts>())...));

  template <typename... Ts, typename T>
  static auto outerHelper(std::tuple<Ts...>&&, T&&) 
  -> decltype(
       std::tuple_cat(innerHelper(std::declval<Ts>(), std::declval<T>())...));

 public:
  using type = decltype(outerHelper(std::declval<T1>(), std::declval<T2>()));
};

class Example {
  Example() = delete;
  Example(const Example&) = delete;
};

using T1 = std::tuple<Example>;
using T2 = std::tuple<int, double>;
static_assert(
  std::is_same_v<
    CartesianProduct_t<T1, T2>, 
    std::tuple<std::tuple<Example, int>, std::tuple<Example, double>>>);

Demo.

也许是这样的:

template <typename TupleA, typename TupleB>
struct CartesianProduct {
  static constexpr size_t SizeA = std::tuple_size_v<TupleA>;
  static constexpr size_t SizeB = std::tuple_size_v<TupleB>;
  template <size_t I>
  static constexpr size_t Col = I / SizeB;
  template <size_t I>
  static constexpr size_t Row = I % SizeB;

  template <size_t ... Is>
  static
  std::tuple<
    std::tuple<
      std::tuple_element_t<Col<Is>, TupleA>,
      std::tuple_element_t<Row<Is>, TupleB>
    >...>
  Helper(std::index_sequence<Is...>);

  using type = decltype(Helper(std::make_index_sequence<SizeA*SizeB>{}));
};

Demo

因为它更短。添加模板类型应该是显而易见的。还省略了完美转发样板文件。

auto cartesian(auto ts0, auto ts1){
  return std::apply([&](auto...t0s){
    return std::tuple_cat([&](auto t0s){ // glue (xk)x(ys...) together
      return std::apply([&](auto...t1s){
        return std::make_tuple(
           std::make_tuple(t0s, t1s)...
        ); // ((xk,y0),(xk,y1),...)
      }, ts1); // turning ts1 into t1s...
    }(t0s)...); // "laundering" the t0s... into t0s non-pack, then gluing
  }, ts0); // turning ts0 into t0s...
}

或类似的东西。

第一个应用将第一个元组拆分为参数。

对于这些参数中的每一个,我们构建元组 ((x,y0),(x,y1),(x,y2)...),然后将它们连接在一起。

稍微手动控制哪些...扩展发生在哪些元素上;我们通过 lambda“清洗”第一个元组的参数,因此当第二个元组展开时它们是单个值而不是一个包。

那就干

template<class T0, class T1>
using cart_t = decltype( cartesian( std::declval<T0>(), std::declval<T1>() ) );

这确实要求类型为 copy/movable。

有两种明显的解决方法。首先是把types提升到tags,做产品,然后pull down。

第二个是对类型列表而不是元组进行操作。您可能正在使用元组作为类型列表,因此我们甚至不必将其提升回元组。