Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 33.  Multidimensional Arrays


33.3. Sized at Compile Time

In discussing the various design decisions involved in creating dynamically sized multidimensional arrays, it behooves us to consider whether any of the characteristics of the various options make it worthwhile creating statically sized multidimensional classes.

Without question, the support in C++ (and C) for built-in multidimensional arrays of fixed size is extremely good. The main reason I think it's worthwhile to consider having a class type is to get a natural enumerable iterator range—that is, via begin() / end()—although it's not exactly hard to get hold of the one-past-the-end iterator of an array:



void print_int(int const &);





int ar[10][10];





std::for_each(&ar[0][0], &ar[10][10], print_int);



33.3.1 boost::array

Boost provides a one-dimension fixed-dimension array template, boost::array, which provides an STL Sequence concept [Aust1999, Muss2001] face to built-in arrays, including begin() and end(), size() and subscript operators. Using boost::array it's not difficult to synthesize multidimensional arrays:



boost::array<boost::array<int, 10>, 10>   ar;



But this doesn't really buy us anything over the built-in arrays, because we cannot use begin() and end() to enumerate over the elements (see section 33.2.2); they will return pointers to the next dimension down—in this case boost::array<int>. Similarly, the size() method will tell us the same lies as boost::multi_array, namely, only returning the number of elements in the most significant dimension of the instance on which it's called.

It's probably better to stick with built-in arrays than to try and force such types to masquerade as multidimensional array types.[6]

[6] Note: this is not a specific criticism of boost::array, as I've never seen any documentation suggesting that it was suitable as, or intended to be, a composite type from which to build up multiple dimensions. I'm using it in that guise here for pedagogical purposes only.

33.3.2 static_array_1/2/3/4d

My solution for statically sized multidimensional arrays is a suite of templates—static_array—that are very similar in concept to the fixed_array templates (see section 33.2.4). The main difference is that the array instances contain their array elements as a member array, rather than as a pointer to an allocated array; the subdimensional slices use the same mechanism of using a pointer into the requisite part of their containing instance's contents. Despite the similarities, I think the differences are worth pointing out, as they demonstrate some of the great things that you can do with templates in C++.

With static_array there is no allocation: an instance is either a proxy and holds only a pointer, or it is the array and contains the full N-dimensional built-in array of elements.



template< typename T         // Value type


        , size_t   N0        // Most significant dimension


        , size_t   N1        // Least significant dimension


        , typename P = . . . // Construction policy


        , typename M = T[N0 * N1]


        >


class static_array_2d;



But when static_array_2d is used as a subdimension slice, it is parameterized with T*, rather than the default T[N0 * N1]. This means that rather than defining its member m_data as an internal array it is defined as a pointer.

Listing 33.4.


template< typename T


        , size_t N0


        , size_t N1


        , size_t N2


        , typename P   = . . . // Construction policy


        , typename M   =   T[N0 * N1 * N2]


        >


class static_array_3d


    : public null_allocator<T>


{


public:


  typedef static_array_3d<T, N0, N1, N2, P, M>  class_type;


  typedef static_array_2d<T, N1, N2, P, T*>     dimension_type;


  . . .



The only remaining part of the picture is the constructors and destructor, which are defined as follows:

Listing 33.5.


template <typename T, . . . >


static_array_2d<. . .>::static_array_2d(T *data)


  : m_data(data)


{}


template <typename T, . . . >


static_array_2d<. . .>::static_array_2d(value_type const &t)


{


  array_initialiser<. . .>::construct(*this, m_data, size(), t);


}


template <typename T, . . . >


static_array_2d<. . .>::~static_array_2d()


{


  if(!is_pointer_type<M>::value) // Compile-time evaluation


  {


    array_initialiser<. . . >::destroy(*this, m_data, size());


  }


}



The first constructor is a slice constructor, and when that method is instantiated, it will be for a parameterization where m_data is a pointer, not an array. Conversely, the second constructor will be a parameterization when m_data is an array, so there is no need to "allocate" or initialize the member; the constructor merely initializes it, using our old friend the array_initialiser.

The destructor uses a meta-programming construct—is_pointer_type[7]—to determine whether the member type—M in the template parameter list—is a pointer. If it is, then the template instantiation is for a proxy, and no destruction need be performed. If not, then the parameterization is for a bona fide array, and the elements must be destroyed.

[7] This is a template that simply contains an enumeration member—value—that is non-zero for pointer types. The definition of this, and many others, is included on the CD.

We have taken advantage of the ability of arrays and pointers to have similar syntaxes, since the access of the extents of the array's contents—&m_data[0] and &m_data[size()]—is the same in both cases.

Fundamentally, the static arrays are little more than a convenience, whose creation is a testament to the power and efficiency of templates. That's not to say that they do not provide useful facilities over and above built-in arrays; it's very nice to have sensible and meaningful values returned from the ubiquitous and important size(), begin(), and end() methods. It complicates the writing of generic code a great deal when these methods do not behave correctly with all types.


      Previous section   Next section