问题描述
假设我在 C++ 中有一个 T
类型。它有各种各样的方法,可以用作一堆函数的参数等。
现在假设我想处理 T
类型的 k 个元素,其中 k 在编译时已知并且小(例如 k=2或 k=3);并且大多数/所有动作都是元素式的。当然,我可以持有一个 std::array<T,n>
并用循环填充我的代码,例如:
for(auto i = 0; i < k; i++) { c[i] = foo( a[i],b[i] ); }
对于函数 T3 foo(T1 a,T2 b)
。
但我想避免这种情况。是否有一些方便的习语可以用来处理这些矢量化的 T,就好像它们只是 T 一样?
理想情况下,我可以写:
vectorized<T1> a = bar();
vectorized<T2> b = baz();
auto c = foo(a,b);
而且,仅基于上述 foo()
的存在,这会起作用。我实际上并不期望能走那么远,但在这个大方向上无环的东西会很好。
注意:
- 我们在这里不是在谈论 SIMD 向量化(尽管这是编译器需要考虑的潜在优化)。
- 不幸的是,首选 C++14 解决方案。 C++17/20 解决方案是相关的(但不会是公认的答案,因为我坚持使用 C++14)。
解决方法
如果您需要做的只是元素操作,并且您对语法 auto c = elementwise_invoke(foo,a,b);
而不是 auto c = foo(a,b);
没问题,那么您可能可以看看 boost::hana::zip_with
,它允许如果您将 std::array
设为 Sequence
。
我不知道为什么 std::array
不是 Sequence
(就像我不知道为什么 std::vector
的 Functor
实例是 {{1 }}退出);然而,让它变得非常简单:
从 Sequence
的文档中您可以看到最小完整定义需要 #if 0
、Iterable
和 Foldable
。前两个已经在 #include <boost/hana/ext/std/array.hpp>
中可用,因此您只需要通过实现 make
来自定义 make
:
make_impl
我使用 std::common_type_t
来确保构造了一个可以容纳所有输入的 namespace boost::hana {
template <>
struct make_impl<ext::std::array_tag> {
template <typename ...Xs>
static constexpr
std::array<std::common_type_t<Xs...>,sizeof...(Xs)>
apply(Xs&& ...xs) {
return {static_cast<Xs&&>(xs)...};
}
};
}
。
显然,您还需要形式化 std::array
确实是 std::array
:
Sequence
我不知道编译时间开销,但我相信 Boost.Hana 很棒。
,一种简单的方法草图(可能实际上无法编译):
constexpr const std::size_t k { whatever };
template <typename T>
using vectorized = std::array<T,k>;
template <typename F,typename... Ts>
auto elementwise_invoke(F f,vectorized<Ts>&&... vectorized_args)
{
using result_type = typename std::result_of_t<F(vectorized<Ts>......)>;
return result_type { f( std::forward<vectorized<Ts>>(vectorized_args) ) ... };
}
然后我们会写:
vectorized<T1> a = bar();
vectorized<T2> b = baz();
auto c = elementwise_invoke(foo,b);
这并不可怕,但还是感觉太冗长了。也许某种包装函数的小工具:
template <typename F>
struct vec {
// ...
template <typename... Ts>
using result_type = typename std::result_of_t<F(vectorized<Ts>.....)>;
template <typename... Ts>
result_type<Ts...> operator()(vectorized<Ts>&&...) const noexcept(/*etc. etc.*/) {
return result_type { f( std::forward<vectorized<Ts>>(vectorized_args) ) ... };
}
}
这将使我们:
vec foo_ {foo}; // or auto foo_ = make_vec(foo) in C++14
vectorized<T1> a = bar();
vectorized<T2> b = baz();
auto c = foo_(a,b);