"constexpr"通过二进制操作减少"std::array"

`constexpr` reduction of `std::array` with binary operation

本文关键字:quot array std 二进制 constexpr 操作      更新时间:2023-10-16

我想写一个constexpr函数,它通过二进制操作减少给定的std::array。 即实现

template <typename T, std::size_t N>
reduce(std::array<T, N>, binary_function);

为了简单起见,我想从加法开始。

例如
sum(std::array<int, 5>{{1,2,3,4,5}});  // returns 15.

到目前为止我得到了什么。

我使用通常的索引技巧来索引数组元素。 即生成一个int序列,可用于参数列表扩展的索引。

template <int... Is>
struct seq {};
template <int I, int... Is>
struct gen_seq : gen_seq<I - 1, I - 1, Is...> {};
template <int... Is>
struct gen_seq<0, Is...> : seq<Is...> {};  // gen_seq<4> --> seq<0, 1, 2, 3>

然后通过可变参数模板递归定义sum函数。

// The edge-condition: array of one element.
template <typename T>
constexpr T sum(std::array<T, 1> arr, decltype(gen_seq<0>{})) {
return std::get<0>(arr);
}
// The recursion.
template <typename T, std::size_t N, int... Is>
constexpr auto sum(std::array<T, N> arr, seq<Is...>) -> decltype(T() + T()) {
return sum(std::array<T, N - 1>{ { std::get<Is>(arr)... } },
gen_seq<N - 2>()) +
std::get<N - 1>(arr);
}
// The interface - hides the indexing trick.
template <typename T, std::size_t N>
constexpr auto sum(std::array<T, N> arr)
-> decltype(sum(arr, gen_seq<N - 1>{})) {
return sum(arr, gen_seq<N - 1>{});
}

在这里,您可以看到它的实际效果。

问题

此实现有效。但是,现阶段我确实有几个问题。

  1. 有什么办法,我可以为这个功能添加完美转发吗?这甚至有意义吗?或者我应该声明这些数组 const-references?
  2. 到目前为止的假设是,减少的返回类型是decltype(T()+T())。 即当你添加两个元素时你得到什么。虽然在大多数情况下加法应该是这样,但对于一般减少来说可能不再如此。有没有办法获得a[0] + (a[1] + (a[2] + ... ) )的类型?我尝试了这样的事情,但我不知道如何生成<T, T, T, ...>的模板参数列表。

我的回答是基于我自己对此类工作人员的实现。

我更喜欢通用的reduce(或折叠或累加)函数直接对元素进行操作,作为它自己的函数参数,而不是像std::array这样的容器中。这样,不是在每次递归中构造一个新数组,而是将元素作为参数传递,我想整个操作更容易编译器内联。此外,它更灵活,例如可以直接使用或用于std::tuple的元素。一般代码在这里。我在这里重复主要功能:

template <typename F>
struct val_fold
{
// base case: one argument
template <typename A>
INLINE constexpr copy <A>
operator()(A&& a) const { return fwd<A>(a); }
// general recursion
template <typename A, typename... An>
INLINE constexpr copy <common <A, An...> >
operator()(A&& a, An&&... an) const
{
return F()(fwd<A>(a), operator()(fwd<An>(an)...));
}
};

很抱歉,这充满了我自己的定义,所以这里有一些帮助:F是定义二进制操作的函数对象。copy是我对数组和元组中递归std::decay的概括。fwd只是std::forward的捷径。同样,common只是std::common_type的快捷方式,但用于类似的泛化(通常,每个操作都可能产生一个用于惰性求值的表达式模板,这里我们强制求值)。

您如何使用上述内容定义sum?首先定义函数对象,

struct sum_fun
{
template <typename A, typename B>
INLINE constexpr copy <common <A, B> >
operator()(A&& a, B&& b) const { return fwd<A>(a) + fwd<B>(b); }
};

然后只是

using val_sum = val_fold<sum_fun>;

std::array开始时,你会怎么称呼它?好吧,一旦你有了你的Is...,你所需要的只是

val_sum()(std::get<Is>(arr)...);

您可以将其包装在自己的界面中。请注意,在 C++14 中,std::array::operator[]是 constexpr,所以这只是

val_sum()(arr[Is]...);

现在,针对您的问题:

1)转发:是的,std::get将数组元素转发到val_sum,后者递归地将所有内容转发给自身。所以剩下的就是你自己的接口来转发输入数组,例如

template <typename A, /* enable_if to only allow arrays here */>
constexpr auto sum(A&& a) -> /* return type here */
{
return sum(std::forward<A>(a), gen_seq_array<A>{});
}

等等,其中gen_seq_array会取A的原始类型(std::remove_refstd::remove_cv等),推导出N,并调用gen_seq<N>{}。如果数组元素具有移动语义,则转发是有意义的。它应该无处不在,例如上面的val_sum调用将是这样的

val_sum()(std::get<Is>(std::forward<A>(a))...);

2)返回类型:如您所见,我使用std::common_type作为返回类型,这应该适用于sum和最常见的算术运算。这已经是可变的了。如果你想要自己的类型函数,使用模板递归很容易从二进制类型函数中创建一个可变参数。

我自己的common版本在这里。它涉及更多,但它仍然是一个递归模板,其中包含一些执行实际工作decltype

在任何情况下,这比您需要的更通用,因为它是为任意数量的任何给定类型定义的。数组中只有一个类型T,所以你所拥有的应该足够了。