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. |