2

Here's a simple 2-dimensional C-style array example:

int c[2][2] = {
    { 1, 2 },
    { 3, 4 }
};

If I want this to be std::array, I have to use this:

std::array<std::array<int, 2>, 2> c = {
    { { 1, 2 } },
    { { 3, 4 } }
};

The declaration is more involved, and I have to use extra { } for initialization.

Is it possible to create a C-style array wrapper, with which I can do this?

my_array<int, 2, 2> c = {
    { 1, 2 },
    { 3, 4 }
};

So the declaration is simpler, and there is no need of the extra { }.

If creating something like this is possible, would this solution have some drawbacks compared to std::array?


I've managed to do this:

template <typename T, std::size_t ...S>
struct ArrayTypeHelper;

template <typename T, std::size_t HEAD, std::size_t ...TAIL>
struct ArrayTypeHelper<T, HEAD, TAIL...> {
    using Type = typename ArrayTypeHelper<T, TAIL...>::Type[HEAD];
};
template <typename T, std::size_t HEAD>
struct ArrayTypeHelper<T, HEAD> {
    using Type = T[HEAD];
};

template <typename T, std::size_t ...S>
struct my_array {
    typename ArrayTypeHelper<T, S...>::Type data;
};

But this still needs extra { }:

my_array<int, 2, 2> b = { {
    { 1, 2 },
    { 3, 4 }
} };
geza
  • 28,403
  • 6
  • 61
  • 135
  • See also https://stackoverflow.com/questions/34832090/whats-the-difference-between-span-and-array-view-in-the-gsl-library – Jay Dec 02 '18 at 16:20
  • How about `std::array, 2> c{1, 2, 3, 4};`? – user7860670 Dec 02 '18 at 16:22
  • With C++17 you can do it simpler since it can deduce the template arguments from the initialization. – Jesper Juhl Dec 02 '18 at 16:27
  • @VTT: I don't really like this style. And there are compilers which warn for this. – geza Dec 02 '18 at 16:28
  • This is too broad a question. Firstly because it's in fact two questions. And secondly because the second question is open ended. It depends on what is expected from such a hypothetical type besides the amount of braces in its initialization. – StoryTeller - Unslander Monica Dec 02 '18 at 16:33
  • 2
    @geza I guess by "some compilers" you mean clang? Then it is a [known bug](https://bugs.llvm.org/show_bug.cgi?id=21629). You can still keep each row on separate line to make it look like matrix – user7860670 Dec 02 '18 at 16:33
  • @VTT: I'm not sure that this is the same case. That bug is not about 2-dim cases. They say that's fixed, but clang still produces warnings for your suggestion. – geza Dec 02 '18 at 16:41
  • @StoryTeller: I expect a similar/same type as `std::array`, but with the possibility of creating the type as I described in my example. I don't really see why this is broad at all. Can you explain why it is broad? How can I make it not broad? – geza Dec 02 '18 at 16:43
  • For better readability why not just write `c[0] = { 1, 2 };` `c[1] = { 3, 4 };` – Mikhail V Dec 02 '18 at 17:33
  • @MikhailV: Unfortunately, that is not an initialization. – geza Dec 02 '18 at 17:42
  • Have you looked at [`boost::multi_array`](https://www.boost.org/doc/libs/release/libs/multi_array/doc/user.html)? – zett42 Dec 02 '18 at 17:55
  • You didn't ask, but arrays are better stored column by column rather than row by row, The matrix algebra works better that way, and you can pass them unmodified to BLAS/LAPACK. – thb Dec 02 '18 at 17:59

1 Answers1

3

The problem is that a c-array member is always viewed by the brace elision algorithm as a single entity. If the inner initializer begins with a brace , the algorithm expects an initializer list with one element. So the solution is to define the carray wrapper in such a way that the brace elision algorithm knows how many elements are aggregated by the wrapper.

To do this, the only solution I see is to simulate an array through multiple inheritance:

#include <utility>
using namespace std;

template<class T,size_t DeDuplicate>
struct holder{
  T val;
  };

template<class T,class IndSeq>
struct carray_wrapper_b;

template<class T,size_t...Is>
struct carray_wrapper_b<T,index_sequence<Is...>>
  :holder<T,Is>...
  {   };

template<class T,size_t I,size_t...Is>
struct make_carray_{
  using type = carray_wrapper_b<typename make_carray_<T,Is...>::type
                               ,make_index_sequence<I>>;
  };
template<class T,size_t I>
struct make_carray_<T,I>{
  using type = carray_wrapper_b<T,make_index_sequence<I>>;
  };

template<class T,size_t...Is>
using carray_wrapper = typename make_carray_<T,Is...>::type;

carray_wrapper<int,2,2>  x = { {1,2},{3,4}};
carray_wrapper<int,2,2>  y = { 1,2,3,4};

This array wrapper can also be used for initialization purpose only:

template<class T,size_t I,size_t...Is>

struct carray{
  carray(initializer_list<T>);
  carray(initializer_list<carray_wrapper<T,Is...>>);
  };

carray<int,2,2,3> arr{1,2,3,4,5,6,7};
carray<int,2,2,3> arr2{{1,2},{3,4}};
carray<int,2,2,3> arr3{{{1,2},{3,4}},{1,2}};
Oliv
  • 17,610
  • 1
  • 29
  • 72
  • @geza Finaly I found a solution, you can make a c-style array member of your wrapper class and use initializer_list constructors that takes as elements the carray_wrapper I proposed. – Oliv Dec 02 '18 at 18:19
  • Nice trick, thanks Oliv! I'd upvote this answer more if I could, it deserves more than 2. – geza Dec 02 '18 at 18:28
  • @geza Thank you :) – Oliv Dec 02 '18 at 19:30