我想写一个 constexpr 函数,减少给定的 std::array
用二元运算。 IE。实现的函数
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.
到目前为止我得到了什么。
我使用常用的索引技巧来索引数组元素。 IE。生成 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>{});
}
Here你可以看到它的实际效果。
问题
此实现有效。但是,现阶段我确实有几个问题。
- 有什么办法可以为这个函数添加 perfect-forward 吗?这有意义吗?或者我应该将这些数组声明为 const-references?
- 到目前为止的假设是,归约的返回类型是
decltype(T()+T())
. IE。添加两个元素时得到的结果。虽然在大多数情况下这对于加法应该是正确的,但对于一般的减少可能不再适用。有没有办法获得a[0] + (a[1] + (a[2] + ... ) )
的类型? ?我试过类似 this 的东西, 但我不知道如何生成<T, T, T, ...>
的模板参数列表.
最佳答案
我的回答是基于我自己对此类人员的实现。
我更喜欢一般的 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
,它递归地将所有内容转发给自己。所以剩下的就是你自己的接口(interface)来转发输入数组,例如
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_ref
、 std::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
在你的阵列中,所以你拥有的应该足够了。
关于c++ - `constexpr` `std::array` 的二元运算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21840568/