用于类型向量化的适当通用习语?

问题描述

假设我在 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::vectorFunctor 实例是 {{1 }}退出);然而,让它变得非常简单:

Sequence 的文档中您可以看到最小完整定义需要 #if 0IterableFoldable。前两个已经在 #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

Here's the demo.

我不知道编译时间开销,但我相信 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);