I have an array (of any rank), and I would like to have an index operator that:
Allows for missing indices, such that the following is equivalent
a(1, 0, 0, 0); a(1, my::missing);
This in itself if straightforward (see example implementation below): one just recursively adds
arg * strides[dim]
untilmy::missing
it hit.Allows automatic prepending of zeros, such that the following is equivalent
a(0, 0, 0, 1); a(1);
Also this is not hard (see example implementation below): one recursively adds
arg * strides[dim offset]
.
What I cannot get my head around is: How to combine the two? The implementation of 2. makes me start of the wrong foot for 1. (I'm limited to <= C 14)
Example implementation of "my::missing" without auto pre-pending zeros
enum class my { missing };
template <size_t dim, class S>
inline size_t index_impl(const S&) noexcept
{
return 0;
}
template <size_t dim, class S, class... Args>
inline size_t index_impl(const S& strides, enum my arg, Args... args) noexcept
{
return 0;
}
template <size_t dim, class S, class Arg, class... Args>
inline size_t index_impl(const S& strides, Arg arg, Args... args) noexcept
{
return arg * strides[dim] index_impl<dim 1>(strides, args...);
}
template <class S, class Arg, class... Args>
inline size_t index(const S& strides, Arg arg, Args... args)
{
return index_impl<0>(strides, arg, args...);
}
int main()
{
std::vector<size_t> strides = {8, 4, 2 ,1};
std::cout << index(strides, 1, 2, 0, 0) << std::endl;
std::cout << index(strides, 1, 2, my::missing) << std::endl;
}
Example implementation of prepending zeros without "my::missing"
template <size_t dim, class S>
inline size_t index_impl(const S&) noexcept
{
return 0;
}
template <size_t dim, class S, class Arg, class... Args>
inline size_t index_impl(const S& strides, Arg arg, Args... args) noexcept
{
return arg * strides[dim] index_impl<dim 1>(strides, args...);
}
template <class S, class Arg, class... Args>
inline size_t index(const S& strides, Arg arg, Args... args)
{
constexpr size_t nargs = sizeof...(Args) 1;
if (nargs == strides.size())
{
return index_impl<0>(strides, arg, args...);
}
else if (nargs < strides.size())
{
return index_impl<0>(strides.cend() - nargs, arg, args...);
}
return index_impl<0>(strides, arg, args...);
}
int main()
{
std::vector<size_t> strides = {8, 4, 2 ,1};
std::cout << index(strides, 1, 2) << std::endl;
std::cout << index(strides, 0, 0, 1, 2) << std::endl;
}
CodePudding user response:
Here is how you can fork implementation for both scenarios:
template<typename T, typename ...Ts>
struct last_type_helper
{
using type = typename last_type_helper<Ts...>::type;
};
template<typename T>
struct last_type_helper<T>
{
using type = T;
};
template<typename... Ts>
using last_type = typename last_type_helper<Ts...>::type;
enum class my { missing };
template<typename ...Ts>
constexpr bool LastTypeIsMy = std::is_same<my, last_type<Ts...>>::value;
template <size_t dim, class S, class Arg, class... Args>
inline size_t head_index(const S& strides, Arg arg, Args... args) noexcept
{
// details of implemetion left for you
return 0;
}
template <size_t dim, class S, class Arg, class... Args>
inline size_t tail_index(const S& strides, Arg arg, Args... args) noexcept
{
// details of implemetion left for you
return 1;
}
template <class S, class... Args>
size_t index(const S& strides, Args... args)
{
return LastTypeIsMy<Args...> ? tail_index<0>(strides, args...) : head_index<0>(strides, args...);
}
https://godbolt.org/z/5qroKzn6c
Details of implementation I'm leaving to you (I just solved your main problem).
CodePudding user response:
C 14 distinctively lacks fold expressions, which usually makes parameter packs much harder to work with. Thankfully, we can use array initializations to fake fold expressions, which is allowed in constexpr
functions, so we can get away with no recursion and better code readability.
template<typename... Idxs>
constexpr int indexing_type()
{
size_t integrals = 0;
bool unused[] = {(integrals = std::is_integral<Idxs>::value, false)...};
(void)unused;
bool mys[] = {std::is_same<my, Idxs>::value...};
bool last = mys[sizeof(mys) - 1];
if(integrals == sizeof...(Idxs))
return 1;
if(integrals == sizeof...(Idxs) - 1 && last)
return 2;
return 0;
}
template<typename S, size_t... Is, typename... Idxs>
inline auto mul_reduce(const S& strides, size_t off, std::index_sequence<Is...>, Idxs... idxs)
{
size_t sum = 0;
bool unused[] = {(sum = strides[off Is] * size_t(idxs), false)...};
(void)unused;
return sum;
}
template<typename S, typename... Idxs>
inline auto index(const S& strides, Idxs... idxs)
-> std::enable_if_t<indexing_type<Idxs...>() == 1, size_t>
{
auto off = strides.size() - sizeof...(Idxs);
return mul_reduce(strides, off, std::make_index_sequence<sizeof...(Idxs)>{}, idxs...);
}
template<typename S, typename... Idxs>
inline auto index(const S& strides, Idxs... idxs)
-> std::enable_if_t<indexing_type<Idxs...>() == 2, size_t>
{
return mul_reduce(strides, 0, std::make_index_sequence<sizeof...(Idxs)>{}, idxs...);
}
indexing_type
tells you which function to dispatch to, and also disables index
from overload resolution in case the parameter types are wrong.
In mul_reduce
, we abuse the fact that size_t(missing) == 0
to avoid needing to remove it from the end.