Reputation: 132108
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
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
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 0
ed 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;
};
}
I have no idea about the compilation time overhead, but Boost.Hana is just great, I believe.
Upvotes: 3