Subtypes, Derived Types & Type Conversion

Distinguish subtypes (same type, extra constraint, free mixing) from derived types (new incompatible type), use explicit type conversions and qualified expressions, and learn how rounding works when converting reals to integers.

Ada gives you two ways to make a 'new kind' of number. A subtype adds a constraint to an existing type but stays the same type, so it mixes freely with its parent while still being range-checked. A derived type ('is new') creates a genuinely distinct type that needs explicit conversion. Choosing between them — and knowing how conversion and qualification work — is central to designing good Ada types.

Subtypes

'subtype Score is Integer range 0 .. 100;' declares a subset of Integer. A Score can be assigned to an Integer and vice versa without conversion; the only difference is that assignments into a Score are checked to stay within 0 .. 100. Unlike type declarations, subtype bounds may be dynamic (computed at run time), e.g. subtype Valid_Index is Positive range 1 .. N; inside a subprogram. Natural and Positive are predefined subtypes of Integer.

• subtype S is T range L .. H; — same type, added constraint. • Mixes freely with T; values assigned into S are checked. • Bounds can be dynamic. • Also used to rename a type: subtype Count is Natural;

Derived Types

'type Account_Id is new Integer;' creates a new type that inherits Integer's values and operations (+, -, <, 'Image...) but is incompatible with Integer and with any other type derived from Integer. You may add a range constraint: type Percentage is new Integer range 0 .. 100;. Use derived types when mixing values would be a bug (IDs of different entities, physical units); use subtypes when values are genuinely the same kind of thing with a narrower valid set.

• type D is new T [range ...]; — distinct type, inherits operations. • Conversion required between D and T. • Guideline: different meaning → derived type; same meaning, restricted values → subtype.

Conversion and Qualification

A type conversion Target (Expr) is allowed between numeric types, between related derived types, and between some array and tagged types. Converting a real to an integer rounds to the nearest integer, with halfway cases rounded away from zero (Integer (2.5) = 3, Integer (3.5) = 4, Integer (-2.5) = -3); use Float'Truncation, 'Floor, 'Ceiling or 'Rounding when you need a specific rule. Conversions are range-checked against the target.

A qualified expression T'(Expr) — note the tick and parentheses — is not a conversion: it asserts that Expr is of type T, used to resolve ambiguity (an overloaded literal, an aggregate whose type is unclear) and it also checks subtype constraints. For example Byte'(0) tells the compiler the literal 0 is a Byte.

• Conversion: T (X) — changes type; real → integer rounds half away from zero. • Qualification: T'(X) — states the type, no conversion; resolves ambiguity. • Both check the target subtype's constraints. • 'Floor, 'Ceiling, 'Truncation, 'Rounding for explicit rounding rules.

Code Examples

Score mixes with Integer freely but its range is enforced (the nested block catches the violation). Account_Id needs Integer (Id). Conversions from real to integer round half away from zero. Output: S = 95 N = 43 Qualified: 50 Constraint_Error: 105 not in Score Float (N) / 3.0 = 1.43333E+01 Integer (2.5) = 3, Integer (3.5) = 4

with Ada.Text_IO; use Ada.Text_IO;

procedure L8_Subtypes is
   subtype Score is Integer range 0 .. 100;     --  same type as Integer, extra constraint
   type Account_Id is new Integer;              --  NEW type derived from Integer
   S : Score := 75;
   N : Integer := 20;
   Id : constant Account_Id := 42;
begin
   S := S + N;                  --  Score and Integer mix freely (same type)
   Put_Line ("S =" & Score'Image (S));
   N := Integer (Id) + 1;       --  derived type needs explicit conversion
   Put_Line ("N =" & Integer'Image (N));
   Put_Line ("Qualified: " & Score'Image (Score'(50)));
   begin
      S := S + 10;              --  95 + 10 = 105 violates the subtype
   exception
      when Constraint_Error =>
         Put_Line ("Constraint_Error: 105 not in Score");
   end;
   Put_Line ("Float (N) / 3.0 =" & Float'Image (Float (N) / 3.0));
   Put_Line ("Integer (2.5) =" & Integer'Image (Integer (2.5)) & ", Integer (3.5) =" & Integer'Image (Integer (3.5)));
end L8_Subtypes;

Subtype vs Derived Type

Pick a subtype for restriction, a derived type for separation.

subtype Meters is Float; subtype Feet is Float; -- expecting protection type Meters is new Float; type Feet is new Float;

Subtypes of the same type mix freely; only derived types prevent mixing.

N:= Integer (X); -- expecting truncation of 2.7 to 2 N:= Integer (Float'Truncation (X));

Conversion rounds to nearest (2.7 → 3); truncate explicitly when that is what you mean.

Print (0); -- two overloaded Print procedures, ambiguous Print (Byte'(0));

Qualify the literal to resolve overloading ambiguity.

Which declaration allows free mixing with Integer while restricting values? A. type S is new Integer range 1 .. 9; B. subtype S is Integer range 1 .. 9; C. type S is range 1 .. 9; D. type S is mod 10;

B. A subtype is the same type as Integer with an extra constraint.

What is Integer (2.5)? A. 2 B. 3 C. Constraint_Error D. Compile error

B. 3 — conversion rounds halfway cases away from zero.

What does Byte'(0) do? A. Converts 0 to Byte B. Qualifies the literal as type Byte (no conversion) C. Takes the address D. Computes Byte'Pos

B. It is a qualified expression stating the type of the literal.

Can a subtype's bounds depend on a run-time value? A. Yes B. No C. Only for Float D. Only in packages

A. Yes — subtype bounds may be dynamic; type bounds must be static.

Trace the subtype example output.

Real output: S = 95 N = 43 Qualified: 50 Constraint_Error: 105 not in Score Float (N) / 3.0 = 1.43333E+01 Integer (2.5) = 3, Integer (3.5) = 4

subtype Score is Integer range 0 .. 100;     type Account_Id is new Integer;
   same type as Integer                         new, distinct type
   Integer + Score  ✓                           Integer + Account_Id  ✗
   range checked on assignment                  Integer (Id) needed
   bounds may be dynamic                        bounds must be static
with Ada.Text_IO; use Ada.Text_IO;

procedure L8_Subtypes is
   subtype Score is Integer range 0 .. 100;     --  same type as Integer, extra constraint
   type Account_Id is new Integer;              --  NEW type derived from Integer
   S : Score := 75;
   N : Integer := 20;
   Id : constant Account_Id := 42;
begin
   S := S + N;                  --  Score and Integer mix freely (same type)
   Put_Line ("S =" & Score'Image (S));
   N := Integer (Id) + 1;       --  derived type needs explicit conversion
   Put_Line ("N =" & Integer'Image (N));
   Put_Line ("Qualified: " & Score'Image (Score'(50)));
   begin
      S := S + 10;              --  95 + 10 = 105 violates the subtype
   exception
      when Constraint_Error =>
         Put_Line ("Constraint_Error: 105 not in Score");
   end;
   Put_Line ("Float (N) / 3.0 =" & Float'Image (Float (N) / 3.0));
   Put_Line ("Integer (2.5) =" & Integer'Image (Integer (2.5)) & ", Integer (3.5) =" & Integer'Image (Integer (3.5)));
end L8_Subtypes;