Patrick
Patrick

Reputation: 525

Using a Variant Record by Pointer

I simply don't catch why the following does not work. Could someone help me to fix it? It complains (at runtime):

raised CONSTRAINT_ERROR : variant2.adb:21 discriminant check failed

procedure Variant2 is

  type POWER is (NONE,GAS, STEAM);

  type VEHICLE (Engine : POWER := NONE) is
  record
     Model_Year : INTEGER range 1888..1992;
     case Engine is
        when NONE   => null;
        when GAS    => Cylinders   : INTEGER range 1..16;
        when STEAM  => Boiler_Size : INTEGER range 5..22;
                       Coal_Burner : BOOLEAN;
     end case;
  end record;

 Works : VEHICLE;
 Works_Not : access VEHICLE := new VEHICLE;

begin
   Works         := (GAS,1980,4); -- (1)
   Works_Not.all := (GAS,1981,8); -- (2)
end Variant2;

(1) is working, but (2) does not

Thanks in advance!

Upvotes: 1

Views: 625

Answers (2)

ajb
ajb

Reputation: 31699

As egilhh said, when you allocate a discriminant record using new, you can't change the discriminant of the record you allocated, even though you could do this for a variable of the type (as opposed to an allocated record). This rule has been around since Ada 83. The rationale was, I believe, that it allows the compiler to optimize space when allocating records. In your example, if we assume all the fields (including the discriminant) are 1 word, then the record will be 2 words if ENGINE=NONE, 3 words if ENGINE=GAS, 4 words if ENGINE=STEAM. When Works_Not is initialized, it's initialized to a NONE, which means it may take only 2 words on the heap (note: it's not a requirement that compilers optimize in this way). If it uses only 2 words, then reassigning the record to one with ENGINE=GAS would be a disaster--you'd be overflowing the area that you previously allocated, and stomping on other data.

Whether this was a good language design decision or not, I can't say; I don't know how many compilers, and how many applications, needed to take advantage of this optimization. Somebody 33 years ago thought it would be useful, and they must have had some good reasons for thinking so.

The restriction is annoying but not insurmountable. I've definitely run into it before, multiple times, but the simple answer is to wrap it in another record.

type VEHICLE_DATA (Engine : POWER := NONE) is
record
   Model_Year : INTEGER range 1888..1992;
   case Engine is
      when NONE   => null;
      when GAS    => Cylinders   : INTEGER range 1..16;
      when STEAM  => Boiler_Size : INTEGER range 5..22;
                     Coal_Burner : BOOLEAN;
   end case;
end record;
type VEHICLE is record
    Data : VEHICLE_DATA;
end record;

Now_Works : access VEHICLE := new VEHICLE;  -- still sets ENGINE=NONE

Now_Works := (Data => (Gas, 1981, 8));  -- legal
Now_Works.Data := (Gas, 1981, 8);       -- legal, does the same thing

These are OK because the allocated record is a VEHICLE, which isn't a discriminant record. It's OK to change the discriminant of a subcomponent like this. That's how I've gotten around the rule.

Upvotes: 0

egilhh
egilhh

Reputation: 6430

The RM explicitly states that "If the designated type is composite, [...] the created object is constrained by its initial value (even if the designated subtype is unconstrained with defaults)." (RM 4.8(6/3))

which means you have to reallocate your access type

Works_Not := new VEHICLE'(GAS,1981,8);

(of course, you should deallocate the old access value first (see RM 13.11.2 Unchecked Storage Deallocation), but I leave that as an exercise)

UPDATE: as discussed in the comments

Here's an example you can play around with:

with Ada.Text_IO;

procedure Array_Of_Aliased is

   type POWER is (NONE, GAS, STEAM);

   type VEHICLE(Engine : POWER := NONE) is
   record
      Model_Year : Integer range 1888..1992;
      case Engine is
         when NONE => null;
         when GAS => Cylinders : INTEGER range 1..16;
         when STEAM => Boiler_Size : INTEGER range 5..22;
                       Coal_Burner : BOOLEAN;
      end case;
   end record;

   -- array of aliased elements
   type Vehicle_Array is array(1..5) of aliased VEHICLE;

   -- the access type need to be "all" or "constant" in order to access aliased values
   type Vehicle_Access is access all VEHICLE;

   Vehicles : Vehicle_Array;

   Works : Vehicle_Access;
begin 

   -- access to the first element of the array. Can't change discriminant this way...
   Works := Vehicles(1)'Access;

   Ada.Text_IO.Put_Line(POWER'Image(Works.Engine));

   -- However, using the array, we _can_ change the discriminant, since it's _not_ an access value
   Vehicles(1) := (STEAM, 1890, 20, True);
   Vehicles(2) := (GAS, 1981, 8);


   Ada.Text_IO.Put_Line(POWER'Image(Works.Engine));

   -- We can still update the record elements using the access value, as long as the discriminant stays the same
   Works.all := (STEAM, 1900, 15, False);

end Array_Of_Aliased;

Upvotes: 4

Related Questions