einpoklum
einpoklum

Reputation: 132108

Appropriate generic idiom for type vectorization?

Suppose I have a type T in C++. It has all sorts of methods, it can be used as a parameter to a bunch of functions etc.

Now suppose I want to work on k elements of type T, with k being known at compile-time and also small (e.g. k=2 or k=3); and with most/all actions being elementwise. Naturally, I could hold an std::array<T, n> and fill my code with loops such as:

for(auto i = 0; i < k; i++) { c[i] = foo( a[i], b[i] ); }

for a function T3 foo(T1 a, T2 b).

but I want to avoid that. Is there some convenient idiom I could use for working with these vectorized-T's as though they were just T's?

Ideally, I would be able to write:

vectorized<T1> a = bar();
vectorized<T2> b = baz();
auto c = foo(a,b);

and, based solely on the existence of the above-mentioned foo(), that would work. I don't realistically expect to get that far, but something loopless in that general direction would be nice.

Note:

Upvotes: 3

Views: 128

Answers (2)

einpoklum
einpoklum

Reputation: 132108

A naive sketch of an approach (may not actually compile):

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) ) ... };
}

And then we would write:

vectorized<T1> a = bar();
vectorized<T2> b = baz();
auto c = elementwise_invoke(foo, a,b);

Which is not terrible, but still feels too verbose. Maybe some sort of gadget wrapping a function:

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) ) ... };
  }
}

And that will get us to:

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);

Upvotes: 1

Enlico
Enlico

Reputation: 28490

If all you need to do is elementwise operations, and if you are ok with the syntax auto c = elementwise_invoke(foo, a,b); rather than auto c = foo(a,b);, then you could probably give a look at boost::hana::zip_with, which allows exactly the syntax in your self-answer, provided you make std::array a Sequence.

I don't know why std::array is not a Sequence (pretty much like I don't know why std::vector's instance for Functor is #if 0ed out); however, making it is pretty easy:

From the documentation of Sequence you see that the minimal complete definition requires Iterable, Foldable, and make. The first two are already available with #include <boost/hana/ext/std/array.hpp>, so you only need to customize make by implementing make_impl:

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)...};
        }
    };
}

where I've used std::common_type_t to ensure that a std::array is constructed which can hold all the inputs.

Obvioulsy you also need to formalize that std::array is indeed a Sequence:

namespace boost::hana {
    template <>
    struct Sequence<ext::std::array_tag> {
        static constexpr bool value = true;
    };
}

Here's the demo.

I have no idea about the compilation time overhead, but Boost.Hana is just great, I believe.

Upvotes: 3

Related Questions