Enum in C++ Like Enum in Ada

Interfacing Ada enumerations and C enums

Neither is 100% correct.

In C:

typedef enum { A=1, B=2 } option_type;

In Ada:

type Option_Type is (A, B);
for Option_Type'Size use Interfaces.C.int'Size;
for Option_Type use (A=>1, B=>2);

The Ada code assumes that the C type option_type has the same size as a C int. Your second snippet assumes it has the same representation as a C unsigned int.

Neither assumption is supported by the C standard.

Quoting the N1570 draft, section 6.7.2.2, paragraph 4:

Each enumerated type shall be compatible with char, a signed
integer type, or an unsigned integer type. The choice of type is
implementation-defined, but shall be capable of representing the
values of all the members of the enumeration.

So the C type option_type could be as narrow as 1 byte or as wide as the widest supported integer type (typically 8 bytes), and it could be either signed or unsigned. C restricts the values of the enumeration constants to the range of type int, but that doesn't imply that the type itself is compatible with int -- or with unsigned int.

If you have knowledge of the characteristics of the particular C compiler you're using (the phrase "implementation-defined" means that those characteristics must be documented), then you can rely on those characteristics -- but your code is going to be non-portable.

I'm not aware of any completely portable way to define an Ada type that's compatible with a given C enumeration type. (I've been away from Ada for a long time, so I could be missing something.)

The only portable approach I can think of is to write a C wrapper function that takes an argument of a specified integer type and calls f(). The conversion from the integer type to option_type is then handled by the C compiler, and the wrapper exposes a function with an argument of known type to Ada.

void f_wrapper(int option) {
f(option); /* the conversion from int to option_type is implicit */
}

Ada Enums to Values

Enumerations were never intended for this sort of purpose, and you shouldn't try to use them for that. (I think the main purpose was to define enumerations that had values other than 0, 1, ... in some external representation or a hardware register or something.) However, you can actually fix this while keeping your code almost the same:

type Prefix is (Yocto, Zepto, Atto, Femto, Pico, Nano,
Micro, Milli, Centi, Deci, None, Deca, Hecto, Kilo,
Mega, Giga, Tera, Peta, Exa, Zetta, Yotta);
type Prefix_To_Integer_Map is array (Prefix) of Integer;
Power_of_Ten : constant Prefix_To_Integer_Map := (
Yocto => -24,
Zepto => -21,
Atto => -18,
Femto => -15,
Pico => -12,
Nano => -9,
Micro => -6,
Milli => -3,
Centi => -2,
Deci => -1,
None => 0,
Deca => 1,
Hecto => 2,
Kilo => 3,
Mega => 6,
Giga => 9,
Tera => 12,
Peta => 15,
Exa => 18,
Zetta => 21,
Yotta => 24);

Should be about as clean as what you had. And saying Power_Of_Ten (My_Prefix) is more descriptive than My_Prefix'Enum_Rep or Prefix'Enum_Rep(My_Prefix) or whatever it is.

Ada: Re-exporting enum type values

If you really want to re-export the values, you can do this:

with C;
package B is
subtype Test is C.Test;
function Test_1 return Test is (C.Test_1);
function Test_2 return Test is (C.Test_2);
function Test_3 return Test is (C.Test_3);
end B;

Unfortunately, you can't use named numbers since enumerations are not numeric types. You can make these functions normal constants instead, but conceptually, this would execute code at elaboration time (the compiler will probably optimize it away, but you can't use pragma Preelaborate anymore).

This allows you to access the literals in A using B.Test_1 etc. This is also a proper abstraction as A will not depend on the literals defined in C anymore (you can rename the literals in C without affecting A, you do need to update B though to map the new names to the original ones).

Simply importing the literal names into the namespace of A is not an abstraction.

Extending Enum in Ada

No, you cannot extend an Enum type in Ada, you can only create derivations/subtypes that cover a subset of the original one.

You have to do it the other way round:

type ABCDE_Type is (A, B, C, D, E);
type ABC_Type is new ABCDE_Type range A .. C;
-- or
subtype ABC_Type is ABCDE_Type range A .. C;

Get the representation value of an enumeration type in Ada

There is currently no completely general solution. Enumeration representation clauses seem to be designed to make this information difficult to obtain. (However Ada 2020 will add a solution; see the bottom of this answer for details.)

This:

function Rep is new Ada.Unchecked_Conversion(Enum, Integer);

is likely to work in most cases, but there are some serious caveats: the representation values have to be within the range Integer'First..Integer'Last, and if the sizes of Enum and Integer don't match the result is actually implementation defined (but it works with GNAT).

As Simon Wright says, the RM recommends Unchecked_Conversion, but this is not a very satisfying solution, and determining a consistent target type is difficult.

As of the 2007 RM, the recommended level of support is:

An implementation should support at least the internal codes in the
range System.Min_Int..System.Max_Int.

which means that converting to Integer is not always sufficient; a value could be less than Integer'First, or greater than Integer'Last. And even if all the values are in that range, there's no really good way to determine a target type that's the same size as the enumeration type. For example, this:

type Enum is (Ten, Twenty, Thirty);
for Enum use (10, 20, 30);
function Rep is new Ada.Unchecked_Conversion(Enum, Integer);

produces this warning in GNAT:

warning: types for unchecked conversion have different sizes

But after the warning, Rep does return the expected values 10, 20, and 30.

The RM explicitly states that if the source and target sizes in an instance of Unchecked_Conversion don't match, and the result type is scalar, then

the result of the function is implementation defined, and can have an
invalid representation

So the fact that the above works for GNAT doesn't mean it's guaranteed to work everywhere.

For an implementation that only supports values in the range System.Min_Int..System.Max_Int, you can do something like this:

type Enum is (...);
for Enum use (...);
type Longest_Integer is range System.Min_Int .. System.Max_Int;
function Rep is new Ada.Unchecked_Conversion(Enum, Longest_Integer);

and ignore the warning. But compilers are allowed to accept values greater than System.Max_Int, as long as they're within the range of some integer type. For example, GNAT rejects this, but another Ada compiler might accept it:

type Longest_Unsigned is mod System.Max_Binary_Modulus;
type Unsigned_Enum is (Zero, Huge);
for Unsigned_Enum use (0, Longest_Unsigned'Last);

and an Unchecked_Conversion from this to any signed integer type will not work. And you still have the potential problem of implementation defined results if the sizes don't match.

Here's a generic solution that should work for any enumeration type if (a) the representation values are in the range System.Min_Int..System.Max_Int, and (b) if the implementation of Unchecked_Conversion is better behaved than the Ada standard requires it to be:

type Longest_Signed is range System.Min_Int .. System.Max_Int;

generic
type Enum is (<>);
function Generic_Rep(E: Enum) return Longest_Signed;

function Generic_Rep(E: Enum) return Longest_Signed is
function Rep is new Ada.Unchecked_Conversion(Enum, Longest_Signed);
begin
return Rep(E);
end Generic_Rep;

Given all this confusion, you might consider using some mechanism other than enumeration representation clauses to do whatever you're trying to do.

UPDATE:

GNAT has implementation-defined attributes 'Enum_Rep and 'Enum_Val. Ada 2020 is expected to adopt them.

http://www.ada-auth.org/standards/2xrm/html/RM-13-4.html#p10.1

Ada Enumerated Type Range

For the subtypes, a 'pos will return the same value as it would have for the base type (1..4 and 2..3 respectively, I believe). Subtypes aren't really new and different types, so much as they are the same old type, but with some extra limitations on its possible values.

But it should be noted that these values are assigned under the scenes. It really should make no difference to you what they are, unless you are using the 'val and 'pos attributes, or you are interfacing to code written outside of Ada (or to hardware).

Plus, if it does end up mattering, you should know that the situation is actually much more complicated. 'pos and 'val don't return the actual bit value the compiler uses for those enumeration values when it generates code. They just return their "ordinal position"; their offset from the first value.

By default they will usually be the same thing. However, you can change the value assignments (but not the ordinal position assignments) yourself with a for ... use clause, like in the code below:

for Status_Type use
(Undefined => 1,
Available => 2,
Out => 4,
Assigned => 8,
Effected => 16,
Cleared => 32);

Enum literal also used as parameter name

Your problem is that the parameter B in procedure Bar hides the enumeration identifier B declared in the enclosing scope for procedure Bar. You need only name the scope with the parameter:

with Ada.Text_IO;

procedure Main is
type My_Type is
(A,
B,
C);

procedure Foo (The_Type : My_Type) is
begin
null;
end Foo;

procedure Bar (B : String) is
begin
Foo (The_Type => Main.B);
end Bar;
begin
Bar ("Hello");
end Main;

Compare Enum Values

You compare values of objects of enumeration types just like objects of any other type, using =:

if I = None then
...
end if;

Ada enumerations with duplicate values

I don't think so. But I think it would be more elegant for you to create two enumerated types indicating the one that corresponds to the possible readable values of the register and the other one which corresponds to the writable values.

Something like:

type Register_Status is (Done, Pending)    -- Values that can be read
type Soft_Reset is (No_Action, Reset) -- Values that can be written

Gneuromante's post at the bottom is a direct answer to your question.



Related Topics



Leave a reply



Submit