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

Lem*_*ing 6 c++ arrays c++11

我想写一个constexpr函数,它减少std::array了二进制操作的给定.即实现的功能

template <typename T, std::size_t N>
reduce(std::array<T, N>, binary_function);
Run Code Online (Sandbox Code Playgroud)

为了简单起见,我想从补充开始.例如

sum(std::array<int, 5>{{1,2,3,4,5}});  // returns 15.
Run Code Online (Sandbox Code Playgroud)

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

我使用通常的索引技巧来索引数组元素.即生成一个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>
Run Code Online (Sandbox Code Playgroud)

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>{});
}
Run Code Online (Sandbox Code Playgroud)

在这里你可以看到它的实际效果.

问题

这种实现有效.但是,在这个阶段我确实有几个问题.

  1. 有什么办法,我可以为这个功能添加完美前进吗?这甚至有意义吗?或者我应该声明那些数组const-references?
  2. 到目前为止的假设是,减少的返回类型是decltype(T()+T()).也就是你添加两个元素时得到的东西.虽然在大多数情况下这应该适用于添加,但对于一般的减少可能不再适用.有没有办法获得这种类型的a[0] + (a[1] + (a[2] + ... ) )?我试着像这样,但我不知道我怎么能产生的模板参数列表<T, T, T, ...>.

iav*_*avr 2

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

我更喜欢一般的reduce(或fold,或accumulate)函数作为其自己的函数参数直接对元素进行操作,而不是在像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)...));
    }
 };
Run Code Online (Sandbox Code Playgroud)

很抱歉,这充满了我自己的定义,所以这里有一些帮助: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); }
};
Run Code Online (Sandbox Code Playgroud)

然后就

using val_sum = val_fold<sum_fun>;
Run Code Online (Sandbox Code Playgroud)

当以 a 开头时,你会如何称呼它std::array?好吧,一旦你有了你的Is...,你所需要的就是

val_sum()(std::get<Is>(arr)...);
Run Code Online (Sandbox Code Playgroud)

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

val_sum()(arr[Is]...);
Run Code Online (Sandbox Code Playgroud)

现在,回答你的问题:

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>{});
}
Run Code Online (Sandbox Code Playgroud)

依此类推,wheregen_seq_array将获取 A 的原始类型(std::remove_refstd::remove_cv),推导出N,然后调用gen_seq<N>{}。如果数组元素具有移动语义,则转发有意义。它应该无处不在,例如上面的调用val_sum就像

val_sum()(std::get<Is>(std::forward<A>(a))...);
Run Code Online (Sandbox Code Playgroud)

2)返回类型:正如您所看到的,我使用它std::common_type作为返回类型,它应该用于sum最常见的算术运算。这已经是可变的了。如果您想要自己的类型函数,可以使用模板递归轻松地从二进制类型函数中创建变量。

我自己的版本common这里。它有点复杂,但它仍然是一个递归模板,其中包含一些decltype用于完成实际工作的内容。

无论如何,这比您需要的更通用,因为它是为任意数量的任何给定类型定义的。您的数组中只有一种类型T,因此您所拥有的应该足够了。