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