[RFC] array_view with byte stride

95 views
Skip to first unread message

Maxim Yanchenko

unread,
Mar 27, 2017, 1:10:46 PM3/27/17
to ISO C++ Standard - Future Proposals
Good day to everyone,

I checked the existing proposals regarding array_view (including the multi-dimensional and strided ones) and I found none that would support byte stride (please correct me if I missed one, apologies in advance).
I have my own custom array view written long time ago which I use a lot and found it extremely convenient, and it differs from the proposed array_view by having an additional runtime parameter byte_stride (defaulted to sizeof(T) to have usual array_view by default).
The byte_stride is not part of the type, so the type is still the same array_view<class T, int Rank=1> and you can use it for both contiguous arrays and byte-strided arrays.

So the constructor looks like this (for 1D array view):
array_view( T* ptr, size_t size, size_t byte_stride = sizeof(T) );

Motivation
In scientific/financial calculations one often has arrays of sets of parameters, i.e. arrays of structs:
struct BasicData {
  int data1;
  int data2;
};
BasicData barr[50];
It's also not unusual to have extended versions of algorithms that use additional parameters on top of the generic ones (or save additional data on top of the common one).
The natural way of dealing with this is just inheriting from the basic struct and make an array of the derived struct:
struct ExtendedData : BasicData {
  int data3;
};
ExtendedData earr[50];
this way you can access common and extended data the same way without changing your code (and you can freely move parameters from derived struct to the base and back, without affecting the specialized code that uses them):
earr.data1[i] = earr.data2[i] * earr.data3[i];

The problem here is that now even the code that doesn't need extended data (like ones gathering common statistics or statuses) and would happily run on just basic data (and therefore could have been factored out to the common unspecialized codebase)
void common_processing(array_view<BasicData>);
Such a function can't run because there is no array of basic params anymore, they are hidden inside the bigger extended params struct, and there is currently no way to get an array_view<BasicData> out of earr.
Strided array_view won't help because sizeof(BasicData) is 2*sizeof(int), while sizeof(ExtendedData) is 3*sizeof(int) - so strides of sizeof(BasicData) won't work.

Also, there can be a plugin-like architecture of the program so the host wouldn't even know the type ExtendedData that exists only in the plugin but still could access BasicData owned by the plugin if there was a way to construct
array_view<BasicData>.

All the above is easily achievable with a byte-strided array_view.


Another example: think of BasicData as RGB and ExtendedData as RGBA/ARGB: you could have an array_view<RGB> over an RGBA file and an RGB file and even an ARGB file, agnostically - they will only differ in their byte_stride value.



Here are the use cases (1D, for simplicity):

1) Use as a normal array_view:
struct B {
  int x,y;
};
B *pb = new B[50];
array_view<B> av( pb, 50 ); // byte_stride is defaulted to sizeof(B)

2) view of an array of a derived class D as an array of base class B:
struct B {
  int x,y;
};
struct D: B {
  int a;
};
D *pd = new D[50];
array_view<B> av_b( pd, 50, sizeof(D) );

(pd automatically converted to B*, sizeof(B) == 2*sizeof(int), sizeof(D) == 3*sizeof(int) so usual integer stride won't work here because sizeof(D) is not a multiple of sizeof(B))

to avoid mistakes, an obvious helper function array_view_by_base() is provided (it also checks static_assert(std::is_base_of_v<B,D>)):
array_view<B> av_b = array_view_by_base<B>( pd, 50 );

3) Member view ("array" of field b):
struct B {
  int x,y;
};
struct C {
  int a;
  B b;
};
C *pc = new C[50];
array_view<B>   av_b( &pc[0].b,   50, sizeof(C) );
array_view<int> av_y( &pc[0].b.y, 50, sizeof(C) );

Again, to avoid mistakes, a helper function
array_view_by_member()
is provided:
array_view<B>   av_b = array_view_by_member( pc, &C::b, 50 ); // both D and B are deduced from the arguments

Unfortunately, there is currently no way in C++ to have a nested pointer-to-member so it's impossible to write something like &C::b::y. This can be emulated, to some extent, by providing a tuple like (&C::b, &B::y) but it's not much more readable than the unsafe version. So the safe version for av_y would be:
array_view<int> av_y = array_view_by_member( pc, std::make_tuple(&C::b, &B::y), 50 ); // both D and int are deduced from the arguments

4) Strided views for all the cases above:
Just call the constructor with stride*sizeof(D) instead of just sizeof(D)


Note 1: when the argument p above is not a plain pointer but a container that knows its size (via std::size - think of std::vector, std::array, or even another array_view) then you don't need to provide the size manually:
std::vector<B> vb;
array_view<B> av_b( vb );
std::vector<D> vd;
array_view<B> av_b = array_view_by_base<B>( vd );
std::vector<C> vc;
array_view<B> av_b =
array_view_by_member( vc, &C::b );

Note 2: advantage over some other proposals is in the uniform type array_view<T,Rank> that doesn't depend on the "continuousness" (unlike n4177 where strided view is a separate type strided_array_view).
For those who need to know if the view is contiguous (for example, to do a bulk memcpy instead of element-wise copying) there is a member function  bool continuous() const provided that just checks if sizeof(T)==byte_stride.

Note 3: array_view supports default construction and assignment (this is necessary for the cases when we have a view in the host program, then we load a plugin, attach to its memory, and initialized our array_view to look at the plugin's memory, and then we can unload the plugin and load another, with a different derived type, and reinitialized our array_view again).


Comments?
If there is
an agreement that it would be good to have (from my experience, it is) I'll write a formal proposal.

Thanks everyone,
Yours sincerely,
Maxim Yanchenko

Jonathan Coe

unread,
Mar 27, 2017, 1:18:35 PM3/27/17
to std-pr...@isocpp.org
A byte-strided array_view would lose many of the advantages of memory contiguity. I'd be inclined to use a struct of array_views rather than a strided array view of structs.
 
struct C_view {
  array_view<int> as;
  array_view<B> bs;
};

I think there are already some proposals in flight to convert vectors of structs into structs of vectors.

Regards,

Jon

Thanks everyone,
Yours sincerely,
Maxim Yanchenko

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CA%2B6mTDf_%2BdxPnZBjLUKgWnEG9N6X_Q%3Dg5Umvmd78GXfDG2K2xw%40mail.gmail.com.

Michał Dominiak

unread,
Mar 27, 2017, 1:29:55 PM3/27/17
to std-pr...@isocpp.org
And how exactly do you initialize those array_views inside?

You do realize that the vector of structs case is vastly different, because vectors are owning and need to allocate memory, while this allocates no memory at all, right?

(These are the effects of religiously following a paradigm and trying to use it as a hammer.)

To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Maxim Yanchenko

unread,
Mar 27, 2017, 1:30:39 PM3/27/17
to ISO C++ Standard - Future Proposals
Hi Jon,

> A byte-strided array_view would lose many of the advantages of memory contiguity.
Yes, I mentioned this in the Note 2. You can always check if the view is contiguous by calling av.contiguous() if you want to do something specific to a contiguous chunk of memory.

> I think there are already some proposals in flight to convert vectors of structs into structs of vectors.
Yes, and it's useful, but this is orthogonal to array_view.
Both have their different spheres of application.

Thanks,
Maxim

Jonathan Coe

unread,
Mar 27, 2017, 1:38:19 PM3/27/17
to std-pr...@isocpp.org
On 27 March 2017 at 18:29, Maxim Yanchenko <maxim.y...@gmail.com> wrote:
Hi Jon,

> A byte-strided array_view would lose many of the advantages of memory contiguity.
Yes, I mentioned this in the Note 2. You can always check if the view is contiguous by calling av.contiguous() if you want to do something specific to a contiguous chunk of memory.

> I think there are already some proposals in flight to convert vectors of structs into structs of vectors.
Yes, and it's useful, but this is orthogonal to array_view.
Both have their different spheres of application.


Understood.
 
Reply all
Reply to author
Forward
0 new messages