[comp.lang.c++] Multiple dimension Array Classes... Was: operator[][] ?

rmartin@clear.com (Bob Martin) (06/26/91)

There have been quite a few questions lately about how to write
classes which encapsulate arrays of multiple dimensions.  So I 
thought I'd try my hand at a general tutorial.

Neither C nor C++ actually support arrays of multiple dimensions.
The languages know only the semantics for single dimensioned arrays.
However, arrays may be of any complex type.  In fact you can declare
an array of arrays if you wish.  And this is what makes the language
appear to have multiple dimension support.

At this point you may ask "what's the difference?  If the language
supports arrays of arrays then it supports multiple dimensions.
Right?"  Not quite.  The difference is subtle but important,
especially when writing array classes.  

The [] operator is at the highest precedence level and binds left to
right.  So the declarator:

	int myArray[24][32]; is parsed as: (int ( (myArray[24])[32]) );

Study this carefully, make sure that you agree with the parsing.  
Much confusion arises because the parsing of array expressions is
not understood.  Note that myArray is an array of 24 arrays each 
containing 32 ints.  The above declaration is equivalent to:

	typedef int I32[32];
	I32 myArray[24];

Notice that we have now separated the multiply dimensioned array
into two distinct types.  This concept is essential to understanding
array classes.  Watch what happens to these types as we use 'myArray' 
in the expression:
	
	myArray[5][7] = 18;

The compiler sees 3 operators.  Two operator[]s and an operator=.
The first operator to be evaluated is 'myArray[5]'.  This operator
returns a value of the type I32. The next operator to be evaluated
is I32[7] which returns an int lvalue.  Finally the assignment
operator is invoked.

Notice that even though the operator[] was invoked twice, it was
invoked for two different types.  Namely I32 and int.  This is the
essence of how the compiler deals with multiple dimensionality.  It
breaks each dimension into a linear array of a different type.  Then it
can use different operator[]s to access each of the different types.

You can use these same concepts if you are going to write a class
which uses operator[] to support multiple dimension access.  
Let us say that we wish to provide the following semanics to the
users of our class:

	CoolClass x;
	x[myName][myStatus] = object;
	object2 = x[hisName][hisStatus];

This example purposely does not show the types used in the brackets
or the type returned by the [] operators.  These types are irrelevent
and may be anything you wish.  (Yes, you can write operator[] functions
which take strings, doubles, or even other objects as arguments.)

Since we have two dimensions, we are going to need two different
types that the operator[]s can work with.  If we look at the first
assignment statement above we see that the compiler will break it
down into two invocations of operator[].  The first will be
CoolClass::operator[].  But the second invocation of operator[] will be 
applied to whatever type the first invocation returns.  
So we must supply another type for the second operator[] to work with.
We do this by defining another class within CoolClass.

	class CoolClass
	{
		class CoolClassDim1 // dimension 1
		{
			<someObject>& operator[](<someIndex>);
		};

		CoolClassDim1& operator[](<someIndex>);
	};

	(Note that the types in <> are for you to specify.  They can be
	anything.)

This gives us the two operator[]s that we need: 

	CoolClass::operator[] which returns a CoolClassDim1, and
	CoolClassDim1::operator[] which returns a reference to the object
							  which we are providing access to.

With these two classes in place the compiler can now successfully
apply the [] operators and provide multiple dimensioned access.  You
may also apply any form of range checking or index interpretation
that you desire.

In Summary.  C++ (and C) do not directly support multiple dimensioned
arrays.  However multiple dimensionality can be achieved by declaring
arrays of arrays.  This works because each operator[] in an array
reference has its own type to operate on.  When building classes
which provide multiple dimension semantics using the [] operator
separate types must be built for each operator[].

An example program follows which implements an integer version of
CoolClass....

-----------------------  CUT HERE ----------------------------

#include <stdio.h>
#include <iostream.h>
#include <stddef.h>

class CoolClass
{
public:
	class CoolClassDim1
	{
	public:
		int iArray[32];

	int& operator[](int x);
	};

	CoolClassDim1 dim1[24];

	CoolClassDim1& operator[](int);
};

CoolClassDim1& CoolClass::operator[](int x)
{
	if ((x > 23) || (x < 0))
	{
		cout << "error in first dimension:" << x << endl;
		exit(1);
	}
	else
	{
		return dim1[x];
	}

}

int& CoolClassDim1::operator[](int x)
{
	if ((x > 31) || (x < 0))
	{
		cout << "error in second dimension:" << x << endl;
		exit(1);
	}
	else
	{
		return iArray[x];
	}

}

main()
{
	CoolClass x;
	int i,j;

	for (i=0; i<24; i++)
	{
		for(j=0; j<32; j++)
		{
			x[i][j] = i*j;
			cout << "x[" << i << "][" << j << "] = " << x[i][j] << endl;
		}
	}

	cout << "Prepare for a failure." << endl;
	x[100][100] = 1;
}
-- 
+-Robert C. Martin-----+:RRR:::CCC:M:::::M:| Nobody is responsible for |
| rmartin@clear.com    |:R::R:C::::M:M:M:M:| my words but me.  I want  |
| uunet!clrcom!rmartin |:RRR::C::::M::M::M:| all the credit, and all   |
+----------------------+:R::R::CCC:M:::::M:| the blame.  So there.     |