PaperBirdMaster PaperBirdMaster - 7 days ago 5
C++ Question

Sub-array template

Let me explain what I'm looking for with code. Let's supose we have a template wrapping an array:

template <typename T, std::size_t SIZE>
struct wrap
{
using value_type = T;
using wrap_type = wrap;

static constexpr auto size = SIZE;
T data[size]{};
};


I would like to add a function to the
wrap
template, this function should return an object able to manipulate a specific part of the array contained in
wrap
, the function should know where the sub-array starts and how many elements it will have so I thought about a function like this:

template <typename T, std::size_t SIZE>
struct wrap
{
using value_type = T;
using wrap_type = wrap;

static constexpr auto size = SIZE;
T data[size]{};

template <std::size_t START, std::size_t COUNT>
???? sub() { ... }
};


I don't want the
????
object returned by
wrap::sub
to have a couple of pointers but an static array, so my approach was:

template <typename T, std::size_t SIZE>
struct wrap
{
using value_type = T;
using wrap_type = wrap;

static constexpr auto size = SIZE;
T data[size]{};

template <std::size_t START, std::size_t COUNT>
struct sub_array
{
using value_type = wrap_type::value_type *;

static constexpr auto start = START;
static constexpr auto count = COUNT;

value_type data[count]{};
};

template <std::size_t START, std::size_t COUNT>
sub_array<START, COUNT> sub() { ... }
};


And here is the problem: What should I write into
wrap::sub
? I'm pretty sure that an approach using
std::integer_sequence
should be possible but I lack on experience with it hence I don't even know what to try. I've tried to asume a value of
2
with both template parameters in
wrap::sub
and it worked as expected:

template <std::size_t START, std::size_t SIZE>
sub_array<START, SIZE> sub() { return { &data[START], &data[START + 1u] }; }


I've tested it as I show below:

wrap<int, 9> w{1, 2, 3, 4, 5, 6, 7, 8, 9};

// x is the view [3, 4]
auto x = w.sub<2, 2>();

std::cout << *x.data[0] << '\n'; // shows 3


So, conceptually the body of
wrap::sub
should be:

return { &data[START + 0], &data[START + 1], ... &data[START + n] };


How should I achieve the effect above?

Answer

This solves the problem as described above:

template<class=void, std::size_t...Is>
auto indexer( std::index_sequence<Is...> ) {
  return [](auto&& f){
    return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto indexer() {
  return indexer( std::make_index_sequence<N>{} );
}

This lets you create an expandable parameter pack of compile-time integers without having to write a custom helper function each time.

It is valid C++14, but some compilers (like MSVC) claim to be C++14 compilers without actually being C++14 compilers.

template <std::size_t START, std::size_t SIZE>
sub_array<START, SIZE> sub() const {
  auto index = indexer<SIZE>();
  return index(
    [&](auto...Is)->sub_array<START, SIZE>
    {
      return {data[START+Is]...};
    }
  );
}

indexer<N> returns an indexer that when passed a lambda, invokes it with std::integral_constant<std::size_t, 0> through std::integral_constant<std::size_t, N-1>.

This pack can then be expanded inline in the function that creates the indexer.


In the comments you mention you want modifications to the sub-array to be reflected in the original array.

Your design does not permit this. The sub-array is copy of a slice of the original array.

The right way to do that is for your sub-array to be a pair of pointers into your original array.

template<class T>
struct array_view {
  T* b = 0;
  T* e = 0;

  T* begin() const { return b; }
  T* end() const { return e; }

  T& operator[](std::size_t i)const { return begin()[i]; }

  bool empty() const { return begin()==end(); }
  std::size_t size() const { return end()-begin(); }

  array_view( T* s, T* f ):b(s), e(f) {}
  array_view( T* s, std::size_t l):array_view(s, s+l) {}

  array_view()=default;
  array_view(array_view const&)=default;
  array_view& operator=(array_view const&)=default;
};

An array_view is a compact way to talk about a slice of an array.

If you want stride, you have to do a bit more work.