Attributes: 'First, 'Last, 'Image, 'Value, 'Range and More

Master Ada's attribute notation — type and object attributes such as 'First, 'Last, 'Range, 'Length, 'Image, 'Value, 'Min, 'Max, 'Size, 'Succ, 'Pred, 'Pos, 'Val, 'Valid — and write code that adapts automatically to the types it works on.

Attributes are Ada's way of asking the compiler about a type or object: X'Something. They let code adapt to its types instead of hard-coding numbers: a loop 'for I in A'Range' works for any array bounds, and T'Last gives the largest value of any scalar type. Many attributes appeared in earlier lessons; this lesson collects the most important ones and shows how they make code generic and robust.

Scalar Type Attributes

For any scalar (discrete or real) type T: T'First and T'Last are the bounds; T'Range is shorthand for T'First .. T'Last; T'Min (A, B) and T'Max (A, B) return the smaller/larger value; T'Image (X) converts to String and T'Value (S) parses a String; T'Size is the minimum number of bits needed to represent all values (4 for range 1 .. 10). For discrete types, 'Pos, 'Val, 'Succ and 'Pred convert between values and positions.

Ada 2022 allows 'Image directly on objects (L'Image instead of Level'Image (L)) and on any type, including records and arrays, producing a readable representation. For real types there are further attributes: 'Digits, 'Small, 'Delta, 'Floor, 'Ceiling, 'Truncation, 'Rounding, 'Model_Epsilon.

• T'First, T'Last, T'Range — bounds. • T'Min, T'Max — comparisons; T'Image / T'Value — text conversion. • X'Image (Ada 2022) works on objects of any type. • T'Size — bits needed; 'Pos/'Val/'Succ/'Pred for discrete types. • Real types: 'Digits, 'Small, 'Floor, 'Ceiling, 'Rounding, 'Truncation.

Array and Object Attributes

For an array or string object A: A'First and A'Last are its index bounds (a String need not start at 1 — slices keep their original indices), A'Range iterates exactly over its valid indices, and A'Length is the number of components. For multidimensional arrays you give the dimension: A'Range (2). Using these attributes rather than literal numbers is one of the most important Ada habits: code keeps working when bounds change and cannot go out of range.

X'Valid returns True if a scalar object currently holds a valid value of its subtype — useful after reading data from hardware or files, or via Unchecked_Conversion. X'Address and X'Size give the memory address and size in bits (low-level programming). X'Old and F'Result are used in postconditions (contracts lesson).

• A'First, A'Last, A'Range, A'Length — never hard-code array bounds. • A'Range (N) for dimension N of multidimensional arrays. • X'Valid — validate data from outside the type system. • X'Address, X'Size — low-level; X'Old, F'Result — contracts.

Code Examples

Level'Size is 4 bits because 1 .. 10 fits in four bits. L'Image is the Ada 2022 object form. The loop uses Word'Range so it iterates over exactly the valid indices. Output: Level'First/Last : 1 10 Number of values : 10 L'Image (2022) : 7 Level'Min (3, 9) : 3 Level'Size (bits) : 4 Weekday'Succ (Tue): WED Integer'Value ("42") + 1 = 43 Word'Length : 3 Word'First/Last : 1 3 A d a

with Ada.Text_IO; use Ada.Text_IO;

procedure L9_Attrs is
   type Level is range 1 .. 10;
   type Weekday is (Mon, Tue, Wed, Thu, Fri);
   L : constant Level := 7;
   Word : constant String := "Ada";
begin
   Put_Line ("Level'First/Last  :" & Level'Image (Level'First) & Level'Image (Level'Last));
   Put_Line ("Number of values  :" & Level'Image (Level'Last - Level'First + 1));
   Put_Line ("L'Image (2022)    :" & L'Image);
   Put_Line ("Level'Min (3, 9)  :" & Level'Image (Level'Min (3, 9)));
   Put_Line ("Level'Size (bits) :" & Integer'Image (Level'Size));
   Put_Line ("Weekday'Succ (Tue): " & Weekday'Image (Weekday'Succ (Tue)));
   Put_Line ("Integer'Value (""42"") + 1 =" & Integer'Image (Integer'Value ("42") + 1));
   Put_Line ("Word'Length       :" & Integer'Image (Word'Length));
   Put_Line ("Word'First/Last   :" & Integer'Image (Word'First) & Integer'Image (Word'Last));
   for I in Word'Range loop
      Put (Word (I) & " ");
   end loop;
   New_Line;
end L9_Attrs;

Attribute Cheat Sheet

Attributes are the standard way to query properties of types and objects.

for I in 1 .. S'Length loop Put (S (I));

for I in S'Range loop Put (S (I));

A String slice may not start at 1; S (I) with I from 1 can be out of range. Always iterate with 'Range.

Max : constant := 2_147_483_647;

Max : constant Integer := Integer'Last;

Use attributes instead of machine-dependent magic numbers.

Level'Range_Length -- GNAT-specific Level'Pos (Level'Last) - Level'Pos (Level'First) + 1

Some attributes are GNAT extensions; prefer standard attributes for portable code.

Which attribute gives the number of elements of an array A? A. A'Size B. A'Length C. A'Last D. A'Count

Why use 'for I in S'Range' instead of 'for I in 1 .. S'Length'? A. Faster B. S may not start at index 1 C. Style only D. 'Length is illegal

B. Strings and slices can have any lower bound; 'Range always matches the actual indices.

What does X'Valid check? A. X is not null B. X holds a valid value of its subtype C. X is initialized to 0 D. X is a variable

B. That the scalar currently holds a value within its subtype.

What does T'Size return for type T is range 1 .. 10? A. 32 B. 4 C. 10 D. 8

B. 4 — the minimum number of bits to represent all values (verified in the example).

Trace the attribute example.

Real output: Level'First/Last : 1 10 Number of values : 10 L'Image (2022) : 7 Level'Min (3, 9) : 3 Level'Size (bits) : 4 Weekday'Succ (Tue): WED Integer'Value ("42") + 1 = 43 Word'Length : 3 Word'First/Last : 1 3 A d a

Scalar T      'First 'Last 'Range 'Min 'Max 'Image 'Value 'Size
Discrete T    'Pos 'Val 'Succ 'Pred
Real T        'Digits 'Small 'Delta 'Floor 'Ceiling 'Rounding 'Truncation
Array A       'First 'Last 'Range 'Length   ('First (N) for dimension N)
Object X      'Image (2022) 'Valid 'Address 'Size 'Old (post) 
Function F    'Result (in postconditions)
Tagged T      'Class 'Tag
with Ada.Text_IO; use Ada.Text_IO;

procedure L9_Attrs is
   type Level is range 1 .. 10;
   type Weekday is (Mon, Tue, Wed, Thu, Fri);
   L : constant Level := 7;
   Word : constant String := "Ada";
begin
   Put_Line ("Level'First/Last  :" & Level'Image (Level'First) & Level'Image (Level'Last));
   Put_Line ("Number of values  :" & Level'Image (Level'Last - Level'First + 1));
   Put_Line ("L'Image (2022)    :" & L'Image);
   Put_Line ("Level'Min (3, 9)  :" & Level'Image (Level'Min (3, 9)));
   Put_Line ("Level'Size (bits) :" & Integer'Image (Level'Size));
   Put_Line ("Weekday'Succ (Tue): " & Weekday'Image (Weekday'Succ (Tue)));
   Put_Line ("Integer'Value (""42"") + 1 =" & Integer'Image (Integer'Value ("42") + 1));
   Put_Line ("Word'Length       :" & Integer'Image (Word'Length));
   Put_Line ("Word'First/Last   :" & Integer'Image (Word'First) & Integer'Image (Word'Last));
   for I in Word'Range loop
      Put (Word (I) & " ");
   end loop;
   New_Line;
end L9_Attrs;