Multi-dimensional array class template

936 views
Skip to first unread message

Daryle Walker

unread,
Sep 6, 2013, 8:17:12 PM9/6/13
to std-pr...@isocpp.org
As an exercise in using variadic templates, I chose a multi-dimensional variant of std::array.  I eventually made the interface match std::array when the number of extents given is one.  When wondering if I should put the class template up for consideration, I realized that it would be so close to std::array, that I shouldn't propose my class directly for the Standard Library, but propose a change to std::array instead.
 
I just put it up at https://github.com/CTMacUser/multiarray-iso-proposal, for requesting comments before officially proposing it.  (I should switch to HTML or some other rich-text system for the next draft....)
 
Daryle W.
 

David Krauss

unread,
Sep 6, 2013, 9:58:30 PM9/6/13
to std-pr...@isocpp.org
I think the initializer_list overloads may be unnecessary, and turn what should be a compile-time arity check to a runtime assertion on size(). They could help to support very high dimensionality, higher than the maximum supported number of function arguments, but I don't think most relevant platforms actually have such a limitation, and low dimensionality could be penalized by adding the overhead of the pointers and "hard" stack allocation.

Another approach would be to make std::multi_array an alias template to a nested std::array type. The multidimensional accessor overloads could be added to one-dimensional std::array and delegate recursively to the underlying type. This would support existing code which nests std::array, or even other numeric libraries inside std::array (which I have done with Eigen; there are advantages to this and practical use cases). It would incur nonsense semantics for operator() on an array over a type with well-defined operator() ( std::size_t [, std::size_t … ] )— but that's a corner case, and array::operator() isn't so far defined anyway. The interface and implementation would be more lightweight.

When was the last time n-adic operator[] support was considered? Doesn't the potential use in the scientific arena outweigh the need to support abuse of the comma operator inside square brackets? C'mon, let's deprecate that syntactic sucker.

Message has been deleted

Daryle Walker

unread,
Sep 7, 2013, 12:30:46 PM9/7/13
to std-pr...@isocpp.org

On Friday, September 6, 2013 9:58:30 PM UTC-4, David Krauss wrote:
I think the initializer_list overloads may be unnecessary, and turn what should be a compile-time arity check to a runtime assertion on size(). They could help to support very high dimensionality, higher than the maximum supported number of function arguments, but I don't think most relevant platforms actually have such a limitation, and low dimensionality could be penalized by adding the overhead of the pointers and "hard" stack allocation.

The indexing operator (operator []) takes exactly one operand.  To work around that, I made the variadic operator() member templates.  I even made them return a sub-array when fewer arguments than dimensions were given.  I made versions of at to match.  Then I read the Standard and found out operator [] now supports multiple arguments indirectly through the braced-init-list syntax.  So I got to make a version of operator [] that takes "multiple" arguments, more as a demonstration than a straight-up necessity.  Then I made similar versions of at to match, then operator () so no one will be asking why I skipped that one.
 
Compilers should make true array access faster than pointer arithmetic.
 
Stack allocation is the point of things like std::array over the regular containers.  Plus, non-dynamic containers can be used in constant expressions, including constexpr contexts.
 
Another approach would be to make std::multi_array an alias template to a nested std::array type. The multidimensional accessor overloads could be added to one-dimensional std::array and delegate recursively to the underlying type. This would support existing code which nests std::array, or even other numeric libraries inside std::array (which I have done with Eigen; there are advantages to this and practical use cases). It would incur nonsense semantics for operator() on an array over a type with well-defined operator() ( std::size_t [, std::size_t … ] )— but that's a corner case, and array::operator() isn't so far defined anyway. The interface and implementation would be more lightweight.

This multi_array would be like the stack and queue adaptors?  You can already make an array of a built-in array type, another std::array, or a custom array-like class type.  But the point was to make something more integrated.
 
 
When was the last time n-adic operator[] support was considered? Doesn't the potential use in the scientific arena outweigh the need to support abuse of the comma operator inside square brackets? C'mon, let's deprecate that syntactic sucker.

I don't know why non-single parameters are banned for operator[].  But now C++11 has given us a single std::initializer_list parameter as a workaround. 
 
Daryle W.
 

David Krauss

unread,
Sep 7, 2013, 8:29:52 PM9/7/13
to std-pr...@isocpp.org

On Sunday, September 8, 2013 12:30:46 AM UTC+8, Daryle Walker wrote:

On Friday, September 6, 2013 9:58:30 PM UTC-4, David Krauss wrote:
I think the initializer_list overloads may be unnecessary, and turn what should be a compile-time arity check to a runtime assertion on size(). They could help to support very high dimensionality, higher than the maximum supported number of function arguments, but I don't think most relevant platforms actually have such a limitation, and low dimensionality could be penalized by adding the overhead of the pointers and "hard" stack allocation.

The indexing operator (operator []) takes exactly one operand.  To work around that, I made the variadic operator() member templates.  I even made them return a sub-array when fewer arguments than dimensions were given.  I made versions of at to match.  Then I read the Standard and found out operator [] now supports multiple arguments indirectly through the braced-init-list syntax.

What other class braced-init-lists which are required to be an exact size? Just because the core language makes something possible, doesn't make it a good idea.
 
Compilers should make true array access faster than pointer arithmetic.

???

 Stack allocation is the point of things like std::array over the regular containers.  Plus, non-dynamic containers can be used in constant expressions, including constexpr contexts.

I mean allocating space for the index arguments, not the array. They should go into registers. When it comes to temporary, small arrays, registers = fast, stack = slow, heap = stupid.
 
Another approach would be to make std::multi_array an alias template to a nested std::array type. The multidimensional accessor overloads could be added to one-dimensional std::array and delegate recursively to the underlying type. This would support existing code which nests std::array, or even other numeric libraries inside std::array (which I have done with Eigen; there are advantages to this and practical use cases). It would incur nonsense semantics for operator() on an array over a type with well-defined operator() ( std::size_t [, std::size_t … ] )— but that's a corner case, and array::operator() isn't so far defined anyway. The interface and implementation would be more lightweight.

This multi_array would be like the stack and queue adaptors?

No, an alias template — a metafunction which looks like a class template.

template< typename t, std::size_t next_extent, std::size_t ... rem_extent >
struct multi_array_helper // metafunction
   
{ typedef std::array< typename multi_array_helper< t, rem_extent ... >::type > type; };

template< typename t, std::size_t extent >
struct multi_array_helper< t, extent >
   
{ typedef std::array< t, extent > type; };

template< typename t, std::size_t ... extent >
using multi_array = typename multi_array_helper< t, extent ... >::type; // alias template
 
  You can already make an array of a built-in array type, another std::array, or a custom array-like class type.  But the point was to make something more integrated.

"Separate" is the opposite of "integrated." The best-integrated solution is the one which leverages existing facilities and avoids creating new types.

As I said, my proposal would support existing code using nested std::array< std::array< … > > and other non-standard array types inside std::array.
 
When was the last time n-adic operator[] support was considered? Doesn't the potential use in the scientific arena outweigh the need to support abuse of the comma operator inside square brackets? C'mon, let's deprecate that syntactic sucker.

I don't know why non-single parameters are banned for operator[].  But now C++11 has given us a single std::initializer_list parameter as a workaround. 

Accepting different semantics to get a syntactic workaround is a bad idea. The ban is for the reason I mentioned, comma operator abuse e.g. x[y, z].

Daryle Walker

unread,
Sep 8, 2013, 4:30:04 PM9/8/13
to std-pr...@isocpp.org

On Saturday, September 7, 2013 8:29:52 PM UTC-4, David Krauss wrote:

On Sunday, September 8, 2013 12:30:46 AM UTC+8, Daryle Walker wrote:

On Friday, September 6, 2013 9:58:30 PM UTC-4, David Krauss wrote:
I think the initializer_list overloads may be unnecessary, and turn what should be a compile-time arity check to a runtime assertion on size(). They could help to support very high dimensionality, higher than the maximum supported number of function arguments, but I don't think most relevant platforms actually have such a limitation, and low dimensionality could be penalized by adding the overhead of the pointers and "hard" stack allocation.

The indexing operator (operator []) takes exactly one operand.  To work around that, I made the variadic operator() member templates.  I even made them return a sub-array when fewer arguments than dimensions were given.  I made versions of at to match.  Then I read the Standard and found out operator [] now supports multiple arguments indirectly through the braced-init-list syntax.

What other class braced-init-lists which are required to be an exact size? Just because the core language makes something possible, doesn't make it a good idea.
 
None, as far as I know.  But the other braced-init-lists in the standard are either aggregate initialization or constructor calls (especially std::initializer_list, especially for the dynamic containers).  This would be the first Standard type to use the operator []( std::initializer_list ) feature.
 
The main reason for the fixed size is that std::initializer_list wasn't a literal type in C++11, so I couldn't read the list's size at constexpr time and remove the appropriate number of extents from the return type.  Since I had to choose exactly one return type, I chose value_type and fixed input list length.  C++14 makes std::initializer_list a literal type, but I can only change the operator only if there's no way to make an initializer list that can only be read at run-time.
 
A class with an indexing operator with an initializer-list parameter that's flexible as you suggest could be something like std::vector, except both the number of extents and their values have to be dynamic.  You would have to return objects of the same class since the selected subset would also be flexible.  Unless all std::initializer_list objects are constexpr, type and/or length fixing has to happen.
 
Compilers should make true array access faster than pointer arithmetic.

???

Given b[j] and c[j], where j is an integer value, b is an array, and c is a pointer, the assembly instructions between the two differ and the array access should be faster than the pointer version.
 
Another approach would be to make std::multi_array an alias template to a nested std::array type. The multidimensional accessor overloads could be added to one-dimensional std::array and delegate recursively to the underlying type. This would support existing code which nests std::array, or even other numeric libraries inside std::array (which I have done with Eigen; there are advantages to this and practical use cases). It would incur nonsense semantics for operator() on an array over a type with well-defined operator() ( std::size_t [, std::size_t … ] )— but that's a corner case, and array::operator() isn't so far defined anyway. The interface and implementation would be more lightweight.

This multi_array would be like the stack and queue adaptors?

No, an alias template — a metafunction which looks like a class template.

template< typename t, std::size_t next_extent, std::size_t ... rem_extent >
struct multi_array_helper // metafunction
   
{ typedef std::array< typename multi_array_helper< t, rem_extent ... >::type > type; };

template< typename t, std::size_t extent >
struct multi_array_helper< t, extent >
   
{ typedef std::array< t, extent > type; };

template< typename t, std::size_t ... extent >
using multi_array = typename multi_array_helper< t, extent ... >::type; // alias template
 
  You can already make an array of a built-in array type, another std::array, or a custom array-like class type.  But the point was to make something more integrated.

"Separate" is the opposite of "integrated." The best-integrated solution is the one which leverages existing facilities and avoids creating new types.

As I said, my proposal would support existing code using nested std::array< std::array< … > > and other non-standard array types inside std::array.

I don't prefer nested smart-array approach.  It looks ugly, and if there's trailing padding in any instance, any higher-level instances will get padding holes everywhere.  (You can also get it if there's heading padding.  Heading padding can only occur if the element type is not standard-layout.)  A key point of my example is that it's backwards-compatible with C++11 std::array (when using one extent), so you can still do nested smart-arrays if you want.
 
Daryle W.
 

David Krauss

unread,
Sep 8, 2013, 8:35:04 PM9/8/13
to std-pr...@isocpp.org


On Monday, September 9, 2013 4:30:04 AM UTC+8, Daryle Walker wrote:
None, as far as I know.  But the other braced-init-lists in the standard are either aggregate initialization or constructor calls (especially std::initializer_list, especially for the dynamic containers).  This would be the first Standard type to use the operator []( std::initializer_list ) feature.

Ah, right — that leads to the correct implementation of the subscript interface,

operator [] ( std::array< std::size_t, sizeof ... (extents) > )

This can also be initialized by a braced-init-list. The remaining downside is that if the list is too short, it will be padded out with zeroes and you can't diagnose the condition. So, I still don't think it's a good idea.
 
A class with an indexing operator with an initializer-list parameter that's flexible as you suggest could be something like std::vector,

I did not suggest to use a flexible parameter. I suggested that you not do so.
 
 Given b[j] and c[j], where j is an integer value, b is an array, and c is a pointer, the assembly instructions between the two differ and the array access should be faster than the pointer version.

Not all arrays are created equal; a std::array argument is more likely to be promoted to registers than the underlying array of std::initializer_list, whose begin member provides pointer-semantic access to an array. So it seems that you're on the wrong side of your own argument…
 
 I don't prefer nested smart-array approach.  It looks ugly,

It looks the same from the user's perspective, for well-formed code, except that there are no new types, just new functionality for existing types.
 
and if there's trailing padding in any instance, any higher-level instances will get padding holes everywhere.

Huh? Access would be delegated through the successive inner types, which need not even represent flat arrays.

Daryle Walker

unread,
Sep 11, 2013, 5:54:24 PM9/11/13
to std-pr...@isocpp.org
On Friday, September 6, 2013 8:17:12 PM UTC-4, Daryle Walker wrote:
As an exercise in using variadic templates, I chose a multi-dimensional variant of std::array.  I eventually made the interface match std::array when the number of extents given is one.  When wondering if I should put the class template up for consideration, I realized that it would be so close to std::array, that I shouldn't propose my class directly for the Standard Library, but propose a change to std::array instead.
 
I just put it up at https://github.com/CTMacUser/multiarray-iso-proposal, for requesting comments before officially proposing it.  (I should switch to HTML or some other rich-text system for the next draft....) 
 
I uploaded another draft earlier today.  Now it's in HTML.  I've added a make_array function template and an (explicit) conversion operator.  I'll probably make it an official proposal soon.
 
Daryle W.
 

David Krauss

unread,
Sep 11, 2013, 8:05:08 PM9/11/13
to std-pr...@isocpp.org


On Thursday, September 12, 2013 5:54:24 AM UTC+8, Daryle Walker wrote:
On Friday, September 6, 2013 8:17:12 PM UTC-4, Daryle Walker wrote:
As an exercise in using variadic templates, I chose a multi-dimensional variant of std::array.  I eventually made the interface match std::array when the number of extents given is one.  When wondering if I should put the class template up for consideration, I realized that it would be so close to std::array, that I shouldn't propose my class directly for the Standard Library, but propose a change to std::array instead.
 

Please avoid putting the comma after a non-linked URL on this board, and please provide a link to the actual file. The best I can find is this link which provides incorrect MIME so the browser doesn't render anything, but displays HTML source.

How much testing and research has gone into this? (Has it actually been used by people in a real project? How much have you used it?) Boost.Multiarray isn't even mentioned. Is submission as an international standard really the next step for this library?

I can't really read it, but only looking at make_array,
  1. The common_type makes it too easy to accidentally get the wrong type of array. Besides numeric cases, a base-class argument would incur slicing derived-class arguments.
  2. Separate T parameter does not seem to serve a purpose, except perhaps to prevent a signature that would instantiate ill-defined std::common_type<>, but that doesn't seem a likely reason. The solution to that problem would be a requires clause.
  3. This does not conceptually belong to a "multiarray" library. It is a utility for creating a one-dimensional array. (Although it does evaluate the array_t metafunction, it is a pointless exercise as the arguments seem to map directly to std::array<T,N>. Although I can't see where std::array<…>::type is defined at all. Using a valid object type as a metafunction is sure to be controversial, if that's really what this is doing, but I'll wait until I can read the proposal without straining.)

Richard Smith

unread,
Sep 11, 2013, 8:30:29 PM9/11/13
to std-pr...@isocpp.org
On Wed, Sep 11, 2013 at 5:05 PM, David Krauss <pot...@gmail.com> wrote:


On Thursday, September 12, 2013 5:54:24 AM UTC+8, Daryle Walker wrote:
On Friday, September 6, 2013 8:17:12 PM UTC-4, Daryle Walker wrote:
As an exercise in using variadic templates, I chose a multi-dimensional variant of std::array.  I eventually made the interface match std::array when the number of extents given is one.  When wondering if I should put the class template up for consideration, I realized that it would be so close to std::array, that I shouldn't propose my class directly for the Standard Library, but propose a change to std::array instead.
 

Please avoid putting the comma after a non-linked URL on this board, and please provide a link to the actual file. The best I can find is this link which provides incorrect MIME so the browser doesn't render anything, but displays HTML source.

How much testing and research has gone into this? (Has it actually been used by people in a real project? How much have you used it?) Boost.Multiarray isn't even mentioned. Is submission as an international standard really the next step for this library?

I can't really read it, but only looking at make_array,
  1. The common_type makes it too easy to accidentally get the wrong type of array. Besides numeric cases, a base-class argument would incur slicing derived-class arguments.
  2. Separate T parameter does not seem to serve a purpose, except perhaps to prevent a signature that would instantiate ill-defined std::common_type<>, but that doesn't seem a likely reason. The solution to that problem would be a requires clause.
  3. This does not conceptually belong to a "multiarray" library. It is a utility for creating a one-dimensional array. (Although it does evaluate the array_t metafunction, it is a pointless exercise as the arguments seem to map directly to std::array<T,N>. Although I can't see where std::array<…>::type is defined at all. Using a valid object type as a metafunction is sure to be controversial, if that's really what this is doing, but I'll wait until I can read the proposal without straining.)

--
 
---
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.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Daryle Walker

unread,
Sep 12, 2013, 1:16:04 PM9/12/13
to std-pr...@isocpp.org
On Wednesday, September 11, 2013 8:05:08 PM UTC-4, David Krauss wrote:
On Thursday, September 12, 2013 5:54:24 AM UTC+8, Daryle Walker wrote:
On Friday, September 6, 2013 8:17:12 PM UTC-4, Daryle Walker wrote:
As an exercise in using variadic templates, I chose a multi-dimensional variant of std::array.  I eventually made the interface match std::array when the number of extents given is one.  When wondering if I should put the class template up for consideration, I realized that it would be so close to std::array, that I shouldn't propose my class directly for the Standard Library, but propose a change to std::array instead.
 

Please avoid putting the comma after a non-linked URL on this board, and please provide a link to the actual file. The best I can find is this link which provides incorrect MIME so the browser doesn't render anything, but displays HTML source.
 
AFAIK, GitHub never instantiates rich formats.  Binary files are shown as a blob flag and text-based files (like HTML) are shown their raw text source.
 
Like Richard Smith said, you can go to http://htmlpreview.github.io/ and feed its form the URL of a HTML-based GitHub file for test viewing.  I didn't know that either; I just previewed with my PC's browser's local mode.  I think GitHub added a web-page service; I'll see if I can use that.
 
How much testing and research has gone into this? (Has it actually been used by people in a real project? How much have you used it?) Boost.Multiarray isn't even mentioned. Is submission as an international standard really the next step for this library?
 
As I said earlier, I came up with this idea a few months ago as an exercise.  AFAIK, it's only been used in my unit test program for it.  But I don't think there's any experience requirement; as long as a proposal is submitted, a library with a decade of in-field hard testing and a library from a five-minute as^H^Hhead-pull are equivalent.  Since this library isn't trying to bring new innovations in (C++) programming, simple inspection is still useful.
 
I haven't mentioned Boost.Multiarray since it's dynamic-memory based.
 
I can't really read it, but only looking at make_array,
  1. The common_type makes it too easy to accidentally get the wrong type of array. Besides numeric cases, a base-class argument would incur slicing derived-class arguments.
The expected use cases would have the same type for all of arguments; I used common_type to automate the process.  As said with other libraries, if you use a set of arguments that'll break when used together, it's your own fault.  What alternative would you suggest for your example?  You can always manually cast each argument to your chosen element type.  There's no common_except_when_it_will_break_stuff_type template.
  1. Separate T parameter does not seem to serve a purpose, except perhaps to prevent a signature that would instantiate ill-defined std::common_type<>, but that doesn't seem a likely reason. The solution to that problem would be a requires clause.
The purpose of a separate first parameter is to enforce that make_array is passed at least one argument.  (If zero-arguments were allowed, what would the element type be?  How could I determine it?)  I guess a Requires clause could work.  The declaration of common_type in the Standard is only a type parameter pack, although its definition precludes zero-size type lists.  (Maybe I'll try making my test make_array use only a general type pack, and if it chokes with zero parameters, maybe that'll be sufficient.)
  1. This does not conceptually belong to a "multiarray" library. It is a utility for creating a one-dimensional array. (Although it does evaluate the array_t metafunction, it is a pointless exercise as the arguments seem to map directly to std::array<T,N>. Although I can't see where std::array<…>::type is defined at all. Using a valid object type as a metafunction is sure to be controversial, if that's really what this is doing, but I'll wait until I can read the proposal without straining.)
Single-dimension arrays are a subset of multi-dimensional ones, especially since I'm modifying the existing single-dimension std::array instead of making a new class template.  Should it be a separate proposal?  Use of array_t as the return type of make_array was a mistake; I've uploaded corrected versions that use array.  (The array_t alias can't be used, since it resolves to a built-in array type, which can't be used as return types.)
 
The new array uses the standard variadic recursion shtick in my sample implementation.  That means each instantiation carries around a type-alias to whatever built-in array type it uses for its data.  My sample library uses "array_type" as the name.  A trait for getting a built-in array type given an element type an a list of extents should be a separate class template, but the best name is already being used!  Since I need that type-alias around anyway, why not pull double-duty and use "type" in the Standard version?
 
Daryle W.
 

David Krauss

unread,
Sep 12, 2013, 7:39:22 PM9/12/13
to std-pr...@isocpp.org


On Friday, September 13, 2013 1:16:04 AM UTC+8, Daryle Walker wrote:
On Wednesday, September 11, 2013 8:05:08 PM UTC-4, David Krauss wrote:
How much testing and research has gone into this? (Has it actually been used by people in a real project? How much have you used it?) Boost.Multiarray isn't even mentioned. Is submission as an international standard really the next step for this library?
 
As I said earlier, I came up with this idea a few months ago as an exercise.  AFAIK, it's only been used in my unit test program for it.  But I don't think there's any experience requirement; as long as a proposal is submitted, a library with a decade of in-field hard testing and a library from a five-minute as^H^Hhead-pull are equivalent.  Since this library isn't trying to bring new innovations in (C++) programming, simple inspection is still useful.

Except that it is trying to be innovative.

Simple (code) inspection is useful, but at this stage you or your friends should be the one doing it. IMHO (I'm relatively new here) the official channels have their hands full without reviewing almost completely untested, rough drafts. There's no rule against submitting whatever you like, but that doesn't mean you should.

There are more issues, for example arr[ arr.size() - 1 ] is all but guaranteed to go past the end in the multidimensional case. value_type and dereference need to be sorted out. Cursory usage will turn up major flaws the library.
 
I haven't mentioned Boost.Multiarray since it's dynamic-memory based.

Memory allocation is but one concern.
 
That means each instantiation carries around a type-alias to whatever built-in array type it uses for its data.  My sample library uses "array_type" as the name.  A trait for getting a built-in array type given an element type an a list of extents should be a separate class template, but the best name is already being used!  Since I need that type-alias around anyway, why not pull double-duty and use "type" in the Standard version?

Using an object type as a metafunction requires that all declarations besides ::type be instantiated when the metafunction is evaluated. This requires that those declarations will be well-formed even though they aren't used, and it requires the compiler to do the extra work.

B'sides… separation of concerns. Need I say more?

bryan....@gmail.com

unread,
Sep 12, 2013, 8:12:02 PM9/12/13
to std-pr...@isocpp.org
I also have an implementation that I finished last night: http://bryanstamour.com/array.cxx

I haven't fully tested everything yet, and I hope to nail down the constexpr/noexcept bits next to ensure that it exactly matches the interface of std::array. Perhaps we can work together if you wish to move forward with a proposal? I'd be interested in helping out...

bryan....@gmail.com

unread,
Sep 12, 2013, 8:14:12 PM9/12/13
to std-pr...@isocpp.org
I also have an implementation of a multi_array that is the same as std::array, down to also being an aggregate type. http://bryanstamour.com/array.cxx

If you find it interesting, would you want to work on a proposal together?


On Friday, 6 September 2013 20:17:12 UTC-4, Daryle Walker wrote:

Bryan St. Amour

unread,
Sep 12, 2013, 8:36:09 PM9/12/13
to std-pr...@isocpp.org
I also have an implementation that I started on a few nights ago, and can be seen here: http://bryanstamour.com/array.cxx

My implementation mimics that of std::array so that there are no surprises with it's use. It can be indexed via successive calls to operator [], just like C-style arrays. And it's also a simple aggregate type, so the layout is nice too.

If you are interested in collaboration, I would love to help out with your proposal effort.

Bryan St. Amour


On Friday, 6 September 2013 20:17:12 UTC-4, Daryle Walker wrote:

Bryan St. Amour

unread,
Sep 12, 2013, 8:37:42 PM9/12/13
to std-pr...@isocpp.org
P.S. Sorry.

It's of course not quite complete yet: I need to go over the description of std::array with a fine toothed comb to ensure that my constexpr/noexcept specifications are correct.

B

Daryle Walker

unread,
Sep 17, 2013, 3:53:27 AM9/17/13
to std-pr...@isocpp.org
On Thursday, September 12, 2013 7:39:22 PM UTC-4, David Krauss wrote:
On Friday, September 13, 2013 1:16:04 AM UTC+8, Daryle Walker wrote:
On Wednesday, September 11, 2013 8:05:08 PM UTC-4, David Krauss wrote:
How much testing and research has gone into this? (Has it actually been used by people in a real project? How much have you used it?) Boost.Multiarray isn't even mentioned. Is submission as an international standard really the next step for this library?
 
As I said earlier, I came up with this idea a few months ago as an exercise.  AFAIK, it's only been used in my unit test program for it.  But I don't think there's any experience requirement; as long as a proposal is submitted, a library with a decade of in-field hard testing and a library from a five-minute as^H^Hhead-pull are equivalent.  Since this library isn't trying to bring new innovations in (C++) programming, simple inspection is still useful.

Except that it is trying to be innovative.

Simple (code) inspection is useful, but at this stage you or your friends should be the one doing it. IMHO (I'm relatively new here) the official channels have their hands full without reviewing almost completely untested, rough drafts. There's no rule against submitting whatever you like, but that doesn't mean you should.

There are more issues, for example arr[ arr.size() - 1 ] is all but guaranteed to go past the end in the multidimensional case. value_type and dereference need to be sorted out. Cursory usage will turn up major flaws the library.
 
Calling the subscript operator like that is a misuse.  Doing it is just like indexing by T in a multidimensional built-in array of T using one index.
 
A sequence container is linear, but a multi-dimensional container is non-linear.  Fortunately, those definitions only come in conflict with the definitions of operator[]() and at().  I specifically exclude those two operations from the sequence requirements with non-one rank in my modifications to section 23.2.3 and refer to section 23.3.2.D for the substitute operations.
 
For the non-linear arrays, I move from subscripting being an addition to begin() to a multi-coordinate address system.  Iteration is still supported, so the linear case, range-for, and standard algorithms still work.  An alternate form of access is passing a function object while the library code does the iteration, but the function object receives the element's coordinates with the element.  Note that all the access methods return a reference to either value_type or a built-in array type, never some sort of sub-std::array.
 
value_type is the type given in the template header.  dereference is the type returned from the scalar version of the subscripting operator.  (That's why it's omitted when sizeof...(N) == 0.)  In the linear case, the two type-aliases point to the same type.  Even for higher-ranks, dereference aliases one-extent less than the internal built-in array type.
 
I haven't mentioned Boost.Multiarray since it's dynamic-memory based.

Memory allocation is but one concern. 
 
Boost.Multiarray uses a different static interface (The template arguments are the element type and the rank), so it's incompatible with my proposal to begin with, which extents the current std::array in a backwards-compatible way.  BM gets the actual extent values during the constructor call, while new std::array keeps them in the template header.  Both classes support indexing through bracket chains and a single multi-index object.  BM has an extensive view system, while new std::array keeps the lack of same as current std::array and built-in arrays do.  BM has a same-type & same-rank reshaping, one preserving total size and another for adding or removing elements; I just added a reshaping function that can change type, rank, and total size, but it always makes a copy.
 
That means each instantiation carries around a type-alias to whatever built-in array type it uses for its data.  My sample library uses "array_type" as the name.  A trait for getting a built-in array type given an element type an a list of extents should be a separate class template, but the best name is already being used!  Since I need that type-alias around anyway, why not pull double-duty and use "type" in the Standard version?

Using an object type as a metafunction requires that all declarations besides ::type be instantiated when the metafunction is evaluated. This requires that those declarations will be well-formed even though they aren't used, and it requires the compiler to do the extra work.
 
I tried to check this, but Chapter 14 (Template) is very deep and dense.  What sections were you looking at?  Even if all the other declarations are instantiated, all of them should always be well-formed (except maybe the zero-elements specializations).
 
B'sides… separation of concerns. Need I say more?
 
Daryle W.
 

Daryle Walker

unread,
Sep 17, 2013, 3:57:46 AM9/17/13
to std-pr...@isocpp.org
On Thursday, September 12, 2013 8:37:42 PM UTC-4, Bryan St. Amour wrote:
P.S. Sorry.

It's of course not quite complete yet: I need to go over the description of std::array with a fine toothed comb to ensure that my constexpr/noexcept specifications are correct.
 
That may be harder than it seems, because I don't know if those specifications are stable.  The C++14 draft before last (N3690) had as many member functions as possible marked constexpr in std::array, but the latest draft (N3691) purged all those qualifiers except for size/empty/max_size.

David Krauss

unread,
Sep 17, 2013, 4:40:43 AM9/17/13
to std-pr...@isocpp.org


On Tuesday, September 17, 2013 3:53:27 PM UTC+8, Daryle Walker wrote:
Calling the subscript operator like that is a misuse.  Doing it is just like indexing by T in a multidimensional built-in array of T using one index.

According to you. Without the special exception you propose for table 101, generic code can assume that & c[n] == &* ( c.begin() + n ).
 
 A sequence container is linear, but a multi-dimensional container is non-linear.

A multi-dimensional array is always linear. Each immediate subobject is an array.
 
  Fortunately, those definitions only come in conflict with the definitions of operator[]() and at().  I specifically exclude those two operations from the sequence requirements with non-one rank in my modifications to section 23.2.3 and refer to section 23.3.2.D for the substitute operations.

This is inconsistent with other containers.
 
Boost.Multiarray uses a different static interface (The template arguments are the element type and the rank), so it's incompatible with my proposal to begin with, which extents the current std::array in a backwards-compatible way.  BM gets the actual extent values during the constructor call, while new std::array keeps them in the template header.

There are far more applications for variable-size multidimensional arrays.


  Both classes support indexing through bracket chains and a single multi-index object.  BM has an extensive view system,

Whether you have a fixed or variable amount of data, the multiply indexed views have the same use. For example some code could work with both a fixed-size kernel and the data series it operates on.

Perhaps a generic view is what we need, along the lines of std::slice and std::gslice but divorced from valarray. Those classes could even perhaps be reused.
 
while new std::array keeps the lack of same as current std::array and built-in arrays do.  BM has a same-type & same-rank reshaping, one preserving total size and another for adding or removing elements; I just added a reshaping function that can change type, rank, and total size, but it always makes a copy.

This is a symptom of having used the multidimensional object to specify the underlying storage. The view and the storage are separate concerns.

I tried to check this, but Chapter 14 (Template) is very deep and dense.

Instantiating a class template instantiates the declarations therein. Instantiations of definitions are deferred until use.

Daryle Walker

unread,
Sep 21, 2013, 8:13:02 PM9/21/13
to std-pr...@isocpp.org
On Tuesday, September 17, 2013 4:40:43 AM UTC-4, David Krauss wrote:
On Tuesday, September 17, 2013 3:53:27 PM UTC+8, Daryle Walker wrote:
Calling the subscript operator like that is a misuse.  Doing it is just like indexing by T in a multidimensional built-in array of T using one index.

According to you. Without the special exception you propose for table 101, generic code can assume that & c[n] == &* ( c.begin() + n )
 
The table is for OPTIONAL behavior (and each row is considered independently).  The author's opinion (mine) is all that matters.  I kept the behavior where it makes sense.  It also happens to be the same type needed to preserve backwards-compatibility.  And where is the assertion for generic code; I didn't see it looking through the Standard.
 
  1. The Standard algorithms work on iterator pairs, not containers.  And there isn't a way to reverse-engineer a container from iterators.
  2. There are no generic traits for containers, unlike iterators.  You have to look up container behavior in tables, like Table 101, which can be changed for newly-introduced types (like all non-linear array instantiations).  What would that code do when given a std::list, for instance?  Or std::map?
 
 A sequence container is linear, but a multi-dimensional container is non-linear.

A multi-dimensional array is always linear. Each immediate subobject is an array. 
 
But linear purity is limiting; there's no value-add.  Templates like a std::multi_array that would nest (current) std::arrays together aren't that useful.  Those templates says they work with T, but that's a lie, they're really a (linear) array of an intermediate class no one cares about.  Everything (value_type, iterators, data, fill, etc.) are in terms of that implementation type.  If the user is just using a bunch of [] calls to get T references, then there's little difference, but no other operation will meet expectations since they're in the wrong units.  In fact, making a std::multi_array map to std::array<T[Inner]..[Extents], OuterExtent> would be more useful since the data member being a C-level multidimensional array means the user can exploit the sloppy array-aggregate initialization syntax.  (Aggregate class-types, at least up to and including C++11, don't have that, so you would have to use double-braces everywhere and get the nesting right.)
 
  Fortunately, those definitions only come in conflict with the definitions of operator[]() and at().  I specifically exclude those two operations from the sequence requirements with non-one rank in my modifications to section 23.2.3 and refer to section 23.3.2.D for the substitute operations.

This is inconsistent with other containers. 
 
None of them use (or need) multi-coordinate access, so new considerations are required.
 
Boost.Multiarray uses a different static interface (The template arguments are the element type and the rank), so it's incompatible with my proposal to begin with, which extents the current std::array in a backwards-compatible way.  BM gets the actual extent values during the constructor call, while new std::array keeps them in the template header.

There are far more applications for variable-size multidimensional arrays.
 
std::vector and (current) std::array didn't put the other out of business.  (Or in this case, vector's existence wasn't proof to block array getting in.)  I don't see why static and dynamic multi-dimensional containers can't co-exist.
 
Anyway, a dynamic multi-dimensional container will have the same "conflicts" with operator[]() that you bring up.
 
  Both classes support indexing through bracket chains and a single multi-index object.  BM has an extensive view system,

Whether you have a fixed or variable amount of data, the multiply indexed views have the same use. For example some code could work with both a fixed-size kernel and the data series it operates on.

Perhaps a generic view is what we need, along the lines of std::slice and std::gslice but divorced from valarray. Those classes could even perhaps be reused. 
 
Well, someone else can propose that.  Just like the current array wraps a single-extent C-level array as a Regular type, the proposed extension does the same for multiple-extent C-level arrays.  Those could all co-exist; there're no need to wait for a "one-to-rule-them-all" type.  (We haven't done it with the dynamic-memory containers.)  What if those other proposals never come?
 
while new std::array keeps the lack of same as current std::array and built-in arrays do.  BM has a same-type & same-rank reshaping, one preserving total size and another for adding or removing elements; I just added a reshaping function that can change type, rank, and total size, but it always makes a copy.

This is a symptom of having used the multidimensional object to specify the underlying storage. The view and the storage are separate concerns.
 
But the point of the array class template is to use the same view and storage models as C-level arrays.  (That's why operator[]() works the way it does, although it violates linear-container purity.)  If you want something beyond that, make your own proposal.
 
BTW, I uploaded my sixth draft earlier today at my GitHub repository.
 
Daryle W.
 
Reply all
Reply to author
Forward
0 new messages