使用二进制操作``constexpr`减少`std :: array`

时间:2014-02-17 22:28:44

标签: c++ arrays c++11

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

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

为了简单起见,我想从补充开始。 E.g。

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>

然后通过可变参数模板递归定义{{​​1}}函数。

sum

Here你可以看到它的实际效果。

问题

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

  1. 有什么办法,我可以为这个功能添加完美前进吗?这甚至有意义吗?或者我应该声明那些数组const-references?
  2. 到目前为止的假设是,减少的返回类型是// 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>{}); } 。即你添加两个元素时得到的结果。虽然在大多数情况下这应该适用于添加,但对于一般的减少可能不再适用。有没有办法获得decltype(T()+T())的类型?我尝试了类似this的内容,但我不知道如何生成a[0] + (a[1] + (a[2] + ... ) )的模板参数列表。

1 个答案:

答案 0 :(得分:2)

我的回答是基于我自己对这些员工的实施。

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

 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版本为here。它涉及的更多,但它仍然是一个递归模板,包含一些decltype来完成实际工作。

在任何情况下,这都比您需要的更通用,因为它是针对任意给定类型的任意数量定义的。

,你的数组中只有一种类型T,所以你拥有的就足够了。