Gem #123: Implicit Dereferencing in Ada 2012

Let's get started...

In Gem #107, we presented an accessor for safely referencing objects stored in a container. The example concerned a reference-counted pointer, but such accessors can be defined on any kind of container.

An advantage of using accessors rather than simple access types is that the former cannot be used to deallocate the designated object. However, safety always comes with a cost, in this case in the form of awkward syntax. In this Gem, we show how some features of Ada 2012 can be used to simplify the syntax.

The container holds elements that can be retrieved via some kind of key. How elements are stored and retrieved is not of interest here. What is important is that Get grants direct access to the stored element (in other words, Get does not return a copy). This is crucial if you have a big object and only want to update a component:

Get (Cont, My_Key).Data.Component := Some_Value;

Note that if the discriminant were to be defined as

notnullaccessconstant Element

, then the accessor would allow only read access. Also, the null exclusion guarantees that an accessor will always reference an object.

This syntax is quite verbose, but Ada 2012 provides a new feature that helps simplify it, namely the Implicit_Dereference aspect.

Side Note: Ada 2012 has added a general mechanism called an aspect specification that allows defining various characteristics of declarations, called aspects, as part of the declaration itself. For example, representation attributes can now be specified by using an aspect specification rather than a separate attribute definition.

Here is how the Implicit_Deference aspect would be specified for our Accessor type:

A type defined with this aspect is called a reference type, and an object of such a type is a reference object. The use of this aspect allows us to reduce the statement to:

Get (Cont, My_Key).Component := Some_Value;

Note that the call Get (Cont, My_Key) is overloaded: its result can be interpreted as either an accessor value or the accessed object itself, and the compiler resolves this based on the context of the call. (This is the reason the Implicit_Dereference aspect cannot be used on the reference-counted pointer in Gem #107, where a type conversion is needed, because the argument of a type conversion must be resolved independently of the context.)

You might argue that this is not a significant simplification. However, this is not the end of the story. We're not interested so much in how to get an accessor value (the result of function Get) as we are in getting to the elements themselves. It happens that we can elide the call to Get by means of another aspect, called Variable_Indexing, that's applied to the Container type:

type Container istaggedprivatewith Variable_Indexing => Get;

The result type of the Variable_Indexing function must be a reference type. It's worth noting that the name given in an aspect specification may denote something declared later. In this case it's a forward reference to Get, which is declared after the type.

Also, the type to which the Variable_Indexing attribute is applied must be tagged. Being a tagged type, this allows the Object.Operation notation, leading to:

Cont.Get (My_Key).Component := Some_Value;

Given the Variable_Indexing aspect that specifies Get, this can now be further reduced to simply:

Cont (My_Key).Component := Some_Value;

Effectively what we get is direct access to container elements, as though the container were a kind of array indexed by the key. In fact, it's possible to use the indexed name alone in a context requiring a variable of the element type, such as an assignment statement:

Cont (My_Key) := Some_Element_Value;

So, by combining the new aspects Implicit_Dereference and Variable_Indexing we get a concise and much more readable syntax for manipulating container elements.

Incidentally, there's also a companion aspect to Variable_Indexing called Constant_Indexing, that can be used to grant read-only access to element values. In that case, the associated function is not required to return a reference type, because all functions return a constant result.