获取Ada中枚举类型的表示值

13

我需要获取Ada枚举类型中某个值关联的数字值。不是枚举中的位置,而是在每个值上使用"for TYPE use"子句分配的值。

有人知道是否可能吗?

6个回答

13

目前还没有完全通用的解决方案。枚举表示子句似乎是设计成使得这些信息难以获得。(不过,Ada 2020将提供一种解决方案;有关详细信息,请参见本答案底部。)

以下内容:

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

这种方法在大多数情况下都可以使用,但是有一些严重的注意事项:表示值必须在 Integer'First..Integer'Last 范围内,如果 EnumInteger 的大小不匹配,则结果实际上是实现定义的(但在 GNAT 中可行)。

正如 Simon Wright 所说,RM 推荐使用 Unchecked_Conversion,但这并不是一个令人满意的解决方案,并且确定一致的目标类型很困难。

截至 2007 年 RM,推荐支持的级别为:

实现应支持至少 System.Min_Int..System.Max_Int 范围内的内部代码。

这意味着将其转换为 Integer 并不总是足够的;一个值可能小于 Integer'First 或大于 Integer'Last。即使所有值都在该范围内,也没有真正好的方法来确定与枚举类型相同大小的目标类型。例如,下面的代码:

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

在GNAT中,会产生以下警告:

warning: types for unchecked conversion have different sizes

但是在警告之后,Rep 确实返回了预期的值 10、20 和 30。
RM 明确指出,在 Unchecked_Conversion 的一个实例中,如果源大小和目标大小不匹配,而结果类型是标量,则:
如果函数的结果是实现定义的,则可能具有无效表示形式。
因此,上述情况对 GNAT 起作用并不意味着它在任何地方都能保证起作用。
对于仅支持范围 System.Min_Int..System.Max_Int 中的值的实现,可以执行以下操作:
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);

忽略警告。但编译器允许接受大于System.Max_Int值的值,只要它们在某个整数类型的范围内。例如,GNAT会拒绝此操作,但另一个Ada编译器可能会接受它:

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

如果使用Unchecked_Conversion将此枚举类型转换为任何有符号整数类型,将无法正常工作。而且,如果大小不匹配,仍然存在实现定义结果的潜在问题。

以下是一种通用解决方案,适用于任何枚举类型,如果(a)表示值在System.Min_Int..System.Max_Int范围内,且(b)如果Unchecked_Conversion的实现比Ada标准要求的更好行为:

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;

鉴于这种混淆,您可能考虑使用除枚举表示子句之外的某种机制来完成您要尝试的操作。
更新:
GNAT具有实现定义的属性'Enum_Rep和'Enum_Val。预计Ada 2020将采用它们。

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


优秀的回答。不要忘记,您还可以执行相反的操作(从整数到枚举类型),并使用“valid!”检查其有效性。 - NWS
1
非常好的答案。有一点要注意:如果创建一个新的整数类型,并为枚举和整数指定相同的Size属性,则可以避免“大小不同”的警告。 type Unsigned_Byte is new natural range 0 .. 255; for Unsigned_Byte'Size use 8; for Enum'Size use 8; - user1818839
或者像这样定义一个类型:type short_int is mod 2**enum_type'size; - Jean-François Fabre
@Jean-FrançoisFabre啊,但是如果枚举有负值怎么办? type Enum is (Minus_One, Zero, One); for Enum use (-1, 0, 1); - Keith Thompson
1
你是对的。你的评论也适用于以前的评论。 - Jean-François Fabre

6

如果你正在使用GNAT编译器,并且不介意使用特定于编译器的方法,该编译器提供了Enum_Rep属性来实现此目的。


2
我发现在GNAT中有效的方法是:

对于:

type MyEnum is (A, B, C);

我必须完成以下任务:

EVal : MyEnum := B;
IVal : Integer := MyEnum'Enum_Rep(EVal);

"'Enum_Rep'和'Enum_Val'是GNAT特有的,但它们将在2020年的Ada标准中被采用。" - Keith Thompson

2
AARM 13.4(第11/1段)建议使用Unchecked_Conversion(可能是转换为整数)。

1
如果您没有使用JVM或.NET编译器,您可以将两者叠加使用,例如:
Value : Integer;
For Value'Address use ENUM_VAR'Address;

你需要使用抑制初始化的编译指示 pragma,尽管我现在无法回忆起它的名称。

如果我没记错的话,还有一种变体记录方法,可以将字段精确地叠加,并将记录用作视图转换。


1
pragma Import (Value, Ada);,我想是这样的。 - Simon Wright
如果你要在枚举上覆盖一个数字项目,你应该确保实例的大小相同。 - Marc C

1

据我理解,质量和风格指南中,枚举的内部表示值完全不需要考虑。经过大量研究,我曾决定使用以下结构:

type enum_c is (clk_eq, clk_div_2, clk_div_16, clk_div_128, clk_div_1024);

type enum_c_values is array (enum_c) of natural; -- or any type you wish

cdiv_values : constant enum_c_values := (
   clk_eq         =>    1,
   clk_div_2      =>    2,
   clk_div_16     =>   16,
   clk_div_128    =>  128,
   clk_div_1024   => 1024 );

c : enum_c := clk_div_128;
...
put_line("c =" & c'img & " natural value associated w/ c =" & cdiv_values(c)'img);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接