C ++ patterns: avoid zero-length arrays without taking up too much space

Note:

The answer to this question works, according to liveworkspace.org , for the latest versions of g ++ (4.7.2), clang (3.2) and icc (13.0.1), but, according to Stephen Lin, this depends on empty database optimization and std::tuple implementation std::tuple .


The original question:

If I have a template structure that looks like this:

 template< class T1, unsigned short N1, class T2, unsigned short N2 > struct ComboThree { T1 data_1[N1]; T2 data_2[N2]; }; 

I can avoid zero-length arrays and extra alignment by specializing in it:

 template<class T1, class T2> struct ComboThree<T1, 0, T2, 0> { }; template<class T1, class T2, unsigned short N2> struct ComboThree<T1, 0, T2, N2> { T2 data_2[N2]; }; template<class T1, unsigned short N1, class T2> struct ComboThree<T1, N1, T2, 0> { T1 data_1[N1]; }; 

But specializing like this becomes a bit of a hassle when X of TX / NX pairs gets a lot bigger. In my project, the actual number of different combinations is likely to be less than five, so I may not use templates at all, but I was curious:

Is there a way to use TEMPLATE MAGIC to avoid zero-length arrays while avoiding extra space?

For example, this:

 template<class T, unsigned short N> struct Array { T data[N]; }; template<class T> struct Array<T, 0> {}; template< class T1, unsigned short N1, class T2, unsigned short N2 > struct ComboTwo { Array<T1, N1> data_1; Array<T2, N2> data_2; }; 

avoids arrays of zero length, but empty structures take up extra space. On the other hand, these are:

 template<class T, unsigned short N> struct Array { T data[N]; }; template<class T> struct Array<T, 0> {}; template< class T1, unsigned short N1, class T2, unsigned short N2 > struct ComboFour : Array<T1, N1>, Array<T2, N2> {}; 

seems to do what I want (is that so?), but I have no idea how to access the data in Array <> base arrays in my program. It also has other limitations noted below by Stephen Lin.

+4
source share
1 answer

This is not a complete answer, but it is too heavy to fit into a comment. To access ComboFour subobjects, if you really want to make the latter, you need the following ugly syntax:

 ComboFour<int, 2, float, 1> cf; cf.Array<int, 2>::data[0] = 0; cf.Array<int, 2>::data[1] = 1; cf.Array<float, 1>::data[0] = 2.0f; 

You might be able to clear this with some accessor functions, but still it would not be so.

The big problem, however, is this:

 ComboFour<int, 1, int, 1> cf2 // fails to compile 

because the same class cannot be used as a parent twice.

(In addition, the note, Array<T, 0> ComboFour subobjects may or may not accept zero space, this is called "empty base optimization" and is permitted, but not necessarily, by the standard.)

There is probably some way around the second problem ... I think that inheritance from std::tuple<...> (which may or may not be possible using empty database optimization inside your standard library implementation) Array<T, N> is perhaps the easiest way to do this if you really need to, but it will make the syntax even uglier.

EDIT : this works for me on GCC 4.7.2

 template< class T1, unsigned short N1, class T2, unsigned short N2, class T3, unsigned short N3 > struct Combo : std::tuple<Array<T1, N1>, Array<T2, N2>, Array<T3, N3>> { }; 

and later ...

 Combo<int, 2, int, 2, float, 3> c; std::get<0>(c).data[0] = 0; std::get<0>(c).data[1] = 1; std::get<1>(c).data[0] = 2; std::get<1>(c).data[1] = 3; std::get<2>(c).data[0] = 0.0; std::get<2>(c).data[1] = 1.0; std::get<2>(c).data[2] = 2.0; assert(sizeof(Combo<int, 0, int, 0, float, 1>) == sizeof(float)); 

(Honestly, the standard library more or less requires empty database optimization to be usable, so although it is not required, I would be surprised if some recent compiler did not support it, whether or not std::tuple<...> in writing for the proper use of this optimization is another matter.)

+2
source

All Articles