I have some auxiliary code that performs vector reshuffling using compile-time indices. It is of upmost importance that the generated code is as efficient as possible. I am relying on parameter packs with fold expressions, and I was wondering what is the best practice in writing such code.
A practical example: let there be a function insert
which inserts the elements of container y
into container x
at positions Ii
where the positions are compile-time constants. The basic signature of this function would be something like this:
template<size_t... Ii, size_t Xsize, size_t Size>
constexpr container<Xsize> insert(container<Xsize> x, container<Ysize> y);
And its invoked like this: insert<0, 2>(x, y)
. I see two obvious possibilities of implementing this.
First: using an auxiliary index variable to iterate over y
:
template<size_t... Ii, size_t Xsize, size_t Size>
constexpr container<Xsize> insert(container<Xsize> x, container<Ysize> y) {
int i = 0;
((x[Ii] = y[i++]), ...);
return x;
}
My problem with this solution is the variable i
: I have to rely on the compiler to optimise it out.
The second solution avoids any runtime dependencies, but it requires an auxiliary function, making the entire implementation rather ugly:
template<size_t... Ii, size_t... Yi, size_t Xsize, size_t Size>
constexpr container<Xsize> insert_(container<Xsize> x, container<Ysize> y, std::index_sequence<Yi...>) {
((x[Ii] = y[Yi]), ...);
return x;
}
template<size_t... Ii, size_t Xsize, size_t Size>
constexpr container<Xsize> insert(container<Xsize> x, container<Ysize> y) {
return insert_<Ii...>(x,y, std::make_index_sequence<sizeof...(Ii)> {});
}
Is there a way to get this done avoiding both runtime variables and an auxiliary function?
It is of upmost importance that the generated code is as efficient as possible.
Just a side note concerning your example: You should make sure that the performance does not suffer from passing function arguments by value. Same for the return value.
Is there a way to get this done avoiding both runtime variables and an auxiliary function?
You can implement reusable helper functions. As an example, consider the following code.
static_assert(__cplusplus >= 201703L, "example written for C++17 or later");
#include <cstddef>
#include <array>
#include <type_traits>
#include <utility>
namespace detail {
template<std::size_t... inds, class F>
constexpr void gen_inds_impl(std::index_sequence<inds...>, F&& f) {
f(std::integral_constant<std::size_t, inds>{}...);
}
}// detail
template<std::size_t N, class F>
constexpr void gen_inds(F&& f) {
detail::gen_inds_impl(std::make_index_sequence<N>{}, std::forward<F>(f));
}
// the code above is reusable
template<
std::size_t... inds_out,
class T, std::size_t size_out, std::size_t size_in
>
constexpr std::array<T, size_out> insert1(
std::array<T, size_out> out,
std::array<T, size_in> in
) {
static_assert((... && (inds_out < size_out)));
static_assert(sizeof...(inds_out) <= size_in);
gen_inds<sizeof...(inds_out)>([&] (auto... inds_in) {
((out[inds_out] = in[inds_in]), ...);
});
return out;
}
A similar alternative is the static_for
approach:
static_assert(__cplusplus >= 201703L, "example written for C++17 or later");
#include <cstddef>
#include <array>
#include <type_traits>
#include <utility>
namespace detail {
template<std::size_t... inds, class F>
constexpr void static_for_impl(std::index_sequence<inds...>, F&& f) {
(f(std::integral_constant<std::size_t, inds>{}), ...);
}
}// detail
template<std::size_t N, class F>
constexpr void static_for(F&& f) {
detail::static_for_impl(std::make_index_sequence<N>{}, std::forward<F>(f));
}
// the code above is reusable
template<
std::size_t... inds_out,
class T, std::size_t size_out, std::size_t size_in
>
constexpr std::array<T, size_out> insert2(
std::array<T, size_out> out,
std::array<T, size_in> in
) {
static_assert(sizeof...(inds_out) >= 1);
static_assert((... && (inds_out < size_out)));
static_assert(sizeof...(inds_out) <= size_in);
constexpr std::size_t N = sizeof...(inds_out);
static_for<N>([&] (auto n) {
// note the constexpr
constexpr std::size_t ind_out = std::array{inds_out...}[n];
constexpr std::size_t ind_in = n;
out[ind_out] = in[ind_in];
});
return out;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With