Language > Objects > Operator Overloading > Overloading the Array Indexing Operator

# Overloading the Array Indexing Operator

IDL arrays can have up to eight dimensions, so IDL’s `[]` (array indexing) operator allows you to specify a maximum of eight indices or subscript ranges. The same limit of eight index values applies when overloading the `[]` operator for an object class. See Multiple Dimensions for details.

Values passed to the array indexing `[]` operator are normally converted to integers before being used to index into the array. For an overloaded `[]` operator, no conversion of the values is performed for scalar entries. This means, for example, that passing a string value to the overloaded `[]` operator method is allowed:

value = Object['some_string_value']

and the input value is passed to the overload method unchanged. Values specified as subscript ranges, however, are converted to integer type. See Subscript Ranges for details.

The `[]` operator is implemented differently depending on which side of the equal sign the overloaded operator is used:

- Right-Hand Side: If used on the right-hand side of the equal sign, the operator must return a value:

`value = Object[``index``]`

This operator is overloaded by implementing the _overloadBracketsRightSide Function method.

- Left-Hand Side: If used on the left-hand side of the equal sign, the operator does not return a value but must accept a value from the right-hand side:

`Object[``index``] = value`

This operator is overloaded by implementing the _overloadBracketsLeftSide Procedure method.

## _overloadBracketsRightSide Function

To overload the behavior of the `[]` operator when used on the right-hand side of the equal sign, the user-supplied method must be a function that accepts a minimum of two and a maximum of nine input arguments. The signature of the `_overloadBracketsRightSide` object method is:

FUNCTION className::_overloadBracketsRightSide, isRange, sub1, $

sub2, sub3, sub4, sub5, sub6, sub7, sub8

.

.

.

RETURN, something

END

The first argument will contain a vector that has one element for each argument supplied by the user; each element contains a zero if the corresponding input argument was an index variable or array of indices, or a one if the corresponding input argument was a subscript range. (See Subscript Ranges for details.) The second through ninth arguments are the index values or subscript ranges supplied by the user.

For example, the following shows a simple overloading of the right-hand side `[]` operator that retrieves values in an object member data field named `ARRAY` .

FUNCTION myObj::_overloadBracketsRightSide, isRange, $

sub1, sub2, sub3, sub4, sub5, sub6, sub7, sub8

IF (MAX(isRange) GT 0) THEN BEGIN

RETURN, 'Subscript Ranges are not allowed'

ENDIF

IF N_ELEMENTS(sub1) THEN retVal = self->ARRAY[sub1]

IF N_ELEMENTS(sub2) THEN retVal = [retVal, self->ARRAY[sub2]]

IF N_ELEMENTS(sub3) THEN retVal = [retVal, self->ARRAY[sub3]]

RETURN, retVal

END

Assuming the object class `myObj` includes a member data field named `ARRAY` that contains array data, the following lines would create the `myObj` object and print out the third element of the array:

obj = OBJ_NEW('myObj')

PRINT, obj[3]

Note that in this simple example subscript ranges are specifically not handled, and only the first three subscripts provided by the user are retrieved. For a more realistic example, see Example: Overloading Array Indexing Syntax.

## _overloadBracketsLeftSide Procedure

To overload the behavior of the `[]` operator when used on the left-hand side of the equal sign, the user-supplied method must be a procedure that accepts a minimum of four and a maximum of 11 input arguments.

The signature of the `_overloadBracketsLeftSide` object method is:

PRO className::_overloadBracketsLeftSide, objRef, rValue, $

isRange, sub1, sub2, sub3, sub4, sub5, sub6, sub7, sub8

.

.

.

END

The first argument is the object reference variable that is being indexed. (This is the same object reference as the implicit self argument and is used only in certain special circumstances; see Replacing the Object Reference for a scenario in which having this object reference is useful.) The second argument contains the value specified on the right-hand side of the equal sign. The third argument contains a vector that has one element for each index value supplied by the user; each element contains a zero if the corresponding input argument was a scalar index value or array of indices, or a one if the corresponding input argument was a subscript range. (See Subscript Ranges for details.) The fourth through eleventh arguments are the index values supplied by the user.

For example, the following shows a simple overloading of the left-hand side `[]` operator that replaces values in an object member data field named `ARRAY` .

PRO myObj::_overloadBracketsLeftSide, objRef, rValue, $

isRange, sub1, sub2, sub3, sub4, sub5, sub6, sub7, sub8

IF (MAX(isRange) GT 0) THEN BEGIN

PRINT, 'Subscript Ranges are not allowed'

RETURN

ENDIF

IF (N_ELEMENTS(sub1) && N_ELEMENTS(rValue) GT 0) $

THEN self->ARRAY[sub1] = rValue[0]

IF (N_ELEMENTS(sub2) && N_ELEMENTS(rValue) GT 1) $

THEN self->ARRAY[sub2] = rValue[1]

END

Assuming the object class `myObj` includes a member data field named `ARRAY` that contains array data, the following lines would create the `myObj` object and replace the third element of the array with the value `22`:

obj = OBJ_NEW('myObj')

obj[3] = 22

Note that in this simple example subscript ranges are specifically not handled, and only the first two subscripts provided by the user are inserted into the array.

## Implementing an Array Indexing Method

The following sections discuss some of the main things to consider when overloading the array indexing operator.

### Multiple Dimensions

IDL’s array indexing notation can accommodate up to eight array dimensions. Although there is no similar restriction on object methods, it is prudent to create array indexing operator overload methods to accommodate the same eight array dimensions. Note that even if your method will accept all eight dimensions, the end user of the overloaded operator need not specify more than one.

You can determine the number of dimension arguments used in a call to the overloaded array indexing operator by counting the number of elements of the isRange positional parameter.

For example, if the isRange vector contains three values, the user has specified three index values (corresponding to a three-dimensional array, in normal array indexing syntax). The sub1 parameter contains the first dimension’s index value, and the sub2 and sub3 parameters contain the second and third dimension’s index value.

An alternate method, if you simply need to know whether the user supplied a particular argument, is to use the N_ELEMENTS function. The N_ELEMENTS function will return zero when called on a named variable that is undefined in the current execution scope, so the code

IF N_ELEMENTS(sub3) THEN ...

will only be executed if a value for the variable `sub3` was provided by the user. (See the example function in _overloadBracketsRightSide Function for an illustration of this technique.)

### Subscript Ranges

There are six ways to describe a subarray inside of indexing brackets. This subarray syntax is described in Subscript Ranges.

The value of a isRange element reveals the type of index reference. A value of 1 (TRUE) means that the corresponding sub* parameter holds a range expression, while a value of 0 (FALSE) means that the corresponding sub* parameter is a variable or expression.

If the user specifies a range expression as one of the index values, the corresponding sub* parameter will contain a three-element array consisting of the individual elements of the range expression. For example, if a user specified the following range expression:

object[2:6]

(which would normally indicate the selection of array elements three through seven), the value of the `sub1` argument to the `_overloadBracketsRightSide` or `_overloadBracketsLeftSide` method would be the vector

[ 2, 6, 1]

Here, the first two elements of the `sub1` array indicate the beginning and ending indices of the range, and the third element indicates the stride.

If the user specifies an asterisk for the second range value:

object[2:*]

(which would normally indicate the selection of array elements from three through the end of the array), the value of the `sub1` argument to the `_overloadBracketsRightSide` or `_overloadBracketsLeftSide` method would be the array

[ 2, -1, 1]

Finally, if the user specifies a range with a stride value:

object[2,*,3]

(which would normally indicate the selection of every third array element from three through the end of the array), the value of the `sub1` argument to the `_overloadBracketsRightSide` or `_overloadBracketsLeftSide` method would be the array

[ 2, -1, 3]

Code similar to the following snippet would allow your overloaded array indexing method to determine whether a given index value is a range expression or a scalar, and process the values appropriately:

; Determine if an index value is a range expression,

; or a simple variable or constant expression.

; If a range expression, parse the string further

FOR I = 0, N_ELEMENTS(isRange) - 1 DO BEGIN

IF isRange[i] THEN BEGIN

; ...code to parse the range expression...

ENDIF ELSE BEGIN

; ...code to parse the variable or constant expression...

ENDELSE

ENDFOR

### Replacing the Object Reference

Overloading the array indexing operator for an object class works because — with one very specialized exception — the overloaded `[]` operator does not apply to arrays of objects. This means that if a variable contains an array of objects, array indexing syntax used on that variable will have the normal array indexing behavior, even if the array indexing operator is overloaded for one or more of the objects in the array:

myVariable = OBJARR(10)

HELP, myVariable[3]

IDL Prints:

<Expression> OBJREF = <NullObject>

indicating that the third element of `myVariable` contains a null object reference.

If, however, we use the same syntax on a scalar object of a class that overrides the array indexing (like the one described in _overloadBracketsRightSide Function):

myVariable = OBJ_NEW('myObj')

HELP, myVariable[3]

IDL might print something like this:

<Expression> FLOAT = 0.000000

The important point to understand here is that IDL knows the difference between an array of objects and a scalar object, and only uses the overloaded array indexing method on scalar objects.

The specialized exception mentioned above has to do with arrays that contain only a single element. To IDL, a single-element array is the same as a scalar, and a scalar can always be referenced using the array index 0. To demonstrate this, try the following:

a = 10
PRINT, a
PRINT, a[0]
HELP, a, a[0]

Notice that although the variable `a` is defined as a scalar, we can subscript `a` with zero and get the same value.

This feature of IDL is true for all variables, including object references. As a result, if the variable `myObject` contains an object reference, the “bare” variable name and the same variable name subscribed with zero are the same thing:

myObject = OBJ_NEW('IDL_Container')
HELP, myObject
HELP, myObject[0]

This presents an interesting problem for overloading the array indexing operator. If we have an object class that overloads the array indexing operator and we use the following syntax:

someObject[0] = value

by IDL’s array indexing rules we might reasonably expect `value` to replace `someObject` entirely. But because we have overloaded the array indexing operator for this object class, the replacement must be accomplished by our `_overloadBracketsLeftSide` procedure. This is where the first positional parameter to that method (objRef) comes into play.

In order to actually replace the object reference with something else, we must have the actual variable available within the `_overloadBracketsLeftSide` procedure method. Replacing the implicit self variable with a new value doesn’t work, since self is available only within the scope of the method itself, and cannot be returned to the calling routine.

The following is an example that uses the objRef argument to completely replace the original object if the user specifies the index zero:

PRO myObj::_overloadBracketsLeftSide, objRef, rValue, $

isRange, sub1, sub2, sub3, sub4, sub5, sub6, sub7, sub8

IF N_ELEMENTS(isRange) EQ 1 && (sub1 EQ 0) THEN BEGIN

PRINT, 'Replacing original object with specified value'

objRef = rValue

RETURN

ENDIF ELSE BEGIN

PRINT, 'Leaving original object unchanged'

ENDELSE

END

Remember that your overloaded array indexing operator does not need to support the single-element array syntax. If you never reference your scalar objects using the array notation, you need not implement this behavior in your `_overloadBracketsLeftSide` procedure and you will never need to use the value of the objRef argument.