I would like to better understand the rationale for the 'Valid
attribute for scalar objects as it seems backward to me. Take the case of performing an Unchecked_Conversion
to assign an integer value to an enumeration object whose type has a representation clause assigning each member an integer value. The unchecked conversion is performed, assigning the object a (potentially erroneous) value then 'Valid
is applied to check if the object is erroneous and if so, the error is handled/corrected.
It seems it would be easier if 'Valid
could be applied to the enumeration type and the integer could be checked by doing My_Enum'Valid (My_Int)
before doing the assignment and erroneous objects could be avoided all together. This could be wrapped in a simple function:
function To_Protocol_ID (id : C.unsigned_short) return Protocol_ID is
(if Protocol_ID'Valid (id) then Protocol_ID'enum_val (id) else Protocol_None);
The best general solution I came up with is a Checked_Conversion
generic, wrapping Unchecked_Conversion
where an enumeration default value is returned in the case where 'Valid
returns false. The conversion default is specified at instantiation or as a parameter replacing the instantiated default at checked conversion time.
generic
type Source is (<>);
type Target is (<>);
Target_Default : Target;
function Checked_Conversion (S : Source;
Default : Target := Target_Default) return Target;
function Checked_Conversion (S : Source;
Default : Target := Target_Default) return Target is
function To_Target is new Ada.Unchecked_Conversion (Source, Target);
Obj : constant Target := To_Target (S);
begin
return (if Obj'Valid then Obj else Default);
end Checked_Conversion;
Is there a better way? If not, why doesn’t Ada have Ada.Checked_Conversion
?
Below is test code using the generic and its output.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Unchecked_Conversion;
with Interfaces.C;
procedure Test_Valid_Generic is
package C renames Interfaces.C;
type Protocol_ID is (Protocol_None, Protocol_A, Protocol_B, Protocol_C);
for Protocol_ID use (Protocol_None => 0, Protocol_A => 10,
Protocol_B => 20, Protocol_C => 30);
for Protocol_ID'Size use C.unsigned_short'size; -- prevents compiler warning
id_valid : C.unsigned_short := 10;
id_invalid : C.unsigned_short := 15;
generic
type Source is (<>);
type Target is (<>);
Target_Default : Target;
function Checked_Conversion (S : Source;
Default : Target := Target_Default) return Target;
function Checked_Conversion (S : Source;
Default : Target := Target_Default) return Target is
function To_Target is new Ada.Unchecked_Conversion (Source, Target);
Obj : constant Target := To_Target (S);
begin
return (if Obj'Valid then Obj else Default);
end Checked_Conversion;
function To_Protocol_ID is new Checked_Conversion
(C.unsigned_short, Protocol_ID, Protocol_None);
begin
Put_Line ("Testing 'Valid attribute on Protocol_ID enumeration type...");
Put_Line (" id_valid : C.unsigned_short = " & id_valid'image);
Put_Line (" To_Protocol_ID (id_valid) = " & To_Protocol_ID (id_valid)'image);
New_Line;
Put_Line (" id_invalid : C.unsigned_short = " & id_invalid'image);
Put_Line (" To_Protocol_ID (id_invalid) = " & To_Protocol_ID (id_invalid)'image);
New_Line;
Put_Line (" id_invalid : C.unsigned_short = " & id_invalid'image);
Put_Line (" To_Protocol_ID (id_invalid) = " & To_Protocol_ID (id_invalid, Default => Protocol_B)'image);
end Test_Valid_Generic;
$ ./test_valid_generic
Testing 'Valid attribute on Protocol_ID enumeration type...
id_valid : C.unsigned_short = 10
To_Protocol_ID (id_valid) = PROTOCOL_A
id_invalid : C.unsigned_short = 15
To_Protocol_ID (id_invalid) = PROTOCOL_NONE
id_invalid : C.unsigned_short = 15
To_Protocol_ID (id_invalid) = PROTOCOL_B