Reputation: 13298
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?
Upvotes: 2
Views: 232
Reputation: 275966
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.
Upvotes: 1
Reputation: 8248
I'm pretty sure that an approach using
std::integer_sequence
should be possible...
Here it is:
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>
constexpr sub_array<START, COUNT> sub() {
static_assert(START + COUNT <= SIZE, "sub_array is out of range");
// The argument makes an instance of integer sequence from 0 to COUNT-1
return sub_impl<START, COUNT>(std::make_index_sequence<COUNT>{});
}
private:
template <std::size_t START, std::size_t COUNT, std::size_t ... Is>
constexpr sub_array<START, COUNT> sub_impl(std::index_sequence<Is...>) {
// Arithmetic in argument list expansion is pretty valid.
return {&data[Is+START]...};
}
};
Now the following lines:
wrap<double, 10> ds{1,2,3,4,5,6,7,8,9,10};
auto s = ds.sub<2,3>();
std::cout << *s.data[0] << std::endl;
print 3
.
This code is exactly an adaptation of documentation examples for your structure. Probably, it may be shortened. And I'm sure that more static (compile-time) assertions may be declared in your code.
Finally, following @Yakk's comments, this implementation of sub
returns a subarray copy, not a view.
Upvotes: 1