C++中的枚举类型是否类似于Ada中的枚举类型?

4

曾经我考虑过在C++中实现一个类/模板,支持像Ada中一样的枚举类型。已经有一段时间没有思考这个问题了,我想知道是否有人解决了这个问题?

编辑:

抱歉,我应该澄清一下我认为在Ada中实现枚举类型的哪些功能是有用的。给定枚举值

type fruit is (apple, banana, cherry, peach, grape);

我们知道水果中包括以下几种:苹果、香蕉、樱桃、桃子和葡萄。这点与C ++没有什么不同。
以下是在Ada中使用每个枚举时自动获得的非常有用的功能:
  • 打印枚举值会生成字符串版本
  • 您可以递增枚举变量
  • 您可以递减枚举变量
我希望这更明确地定义了问题。

注释添加自评论:

Ada枚举类型的有用特性

  • 枚举中的第一个值是fruit'first,它代表apple
  • 枚举中的最后一个值是fruit'last,它代表grape
  • 增量操作是fruit'succ(apple),它代表banana
  • 减量操作是fruit'pred(cherry),它也代表banana
  • 从枚举到整数的转换是fruit'pos(cherry),因为Ada使用基于0的枚举,所以返回2
  • 从整数到枚举的转换是fruit'val(2),它返回cherry
  • 从枚举到字符串的转换是fruit'Image(apple),它返回(大写)字符串"APPLE"
  • 从字符串到枚举的转换是fruit'Value("apple"),它返回值apple

参见相关的SO问题:


1
如果您能解释一下Ada中枚举的行为,那会很有帮助的;-) - Mecki
2
http://en.wikipedia.org/wiki/Enumerated_type#Ada - Judah Gabriel Himango
1
更具体地说,您想要模拟Ada枚举行为的哪个方面? - Greg Hewgill
你也可以使用枚举类型作为数组类型的索引,即使这些值不是连续的。 - NWS
枚举可以在循环中使用,例如for a_fruit in fruit'Range loop - Jossi
显示剩余4条评论
8个回答

3

好的,我们先不谈C++。C++只是C的超集(这意味着C中所有能做到的事情在C++中同样也能做到)。因此,我们将专注于纯C(因为那是我熟悉的语言)。C有枚举:

enum fruit { apple, banana, cherry, peach, grape };

这是完全合法的C语言,值是连续的,苹果的值为零,香蕉的值为苹果加一。你可以创建有空缺的枚举,但只有在你明确地制造空洞时才能这样做。

enum  fruit { apple = 0, banana, cherry = 20, peach, grape };

虽然苹果是0,香蕉是1,樱桃是20,因此桃子是21,葡萄是22,而1到20之间的所有内容都是未定义的。通常你不想要这些空缺。你可以采取以下措施:

enum fruit { apple = 0, banana, cherry, peach, grape };
enum fruit myFruit = banana;
myFruit++;
// myFruit is now cherry
printf("My fruit is cherry? %s\n", myFruit == cherry ? "YES" : "NO");

这将打印出YES。你也可以进行以下操作:
enum fruit { apple = 0, banana, cherry = 20, peach, grape };
enum fruit myFruit = banana;
myFruit++;
// myFruit is now cherry
printf("My fruit is cherry? %s\n", myFruit == cherry ? "YES" : "NO");

这将打印出“NO”,并且myFruit的值与任何枚举常量都不相同。
顺便提一下,为了避免这种情况,您可以使用typedef避免枚举。只需在自己的行上使用“typedef enum fruit fruit;”。现在,您可以说“fruit myFruit”而无需在前面添加enum。通常在定义枚举时直接完成此操作。
typedef enum fruit { apple = 0, banana, cherry, peach, grape } fruit;

fruit myFruit;

缺点是你不再知道水果是一个枚举,它可能是一个对象、一个结构或其他任何东西。我通常避免这种类型的typedef,我更喜欢在枚举前面写enum,在结构前面写struct(我只是在这里使用它们,因为它看起来更好)。
获取字符串值是不可能的。在运行时,枚举只是一个数字。这意味着,如果你不知道是什么类型的枚举(因为0可能是苹果,但它也可能是不同枚举集合中的不同事物),那么就不可能做到。然而,如果你知道它是一个水果,那么编写一个可以为你完成此操作的函数就很容易了。预处理器是你的朋友 :-)。
typedef enum fruit {
    apple = 0,
    banana,
    cherry,
    peach,
    grape
} fruit;

#define STR_CASE(x) case x: return #x
const char * enum_fruit_to_string(fruit f) {
    switch (f) {
        STR_CASE(apple); STR_CASE(banana); STR_CASE(cherry);
        STR_CASE(peach); STR_CASE(grape);
    }
    return NULL;
}
#undef STR_CASE

static void testCall(fruit f) {
    // I have no idea what fruit will be passed to me, but I know it is
    // a fruit and I want to print the name at runtime
    printf("I got called with fruit %s\n", enum_fruit_to_string(f));
}

int main(int argc, char ** argv) {
    printf("%s\n", enum_fruit_to_string(banana));
    fruit myFruit = cherry;
    myFruit++; // myFruit is now peach
    printf("%s\n", enum_fruit_to_string(myFruit));
    // I can also pass an enumeration to a function
    testCall(grape);
    return 0;
}

输出:

banana
peach
I got called with fruit grape

这正是您想要的吗?还是我完全走错了方向?

3
如果第一个枚举变量没有等号,其枚举常数的值为0。 - Jonathan Leffler
你似乎不理解我使用的精确术语,这让人感到沮丧。非常令人沮丧。当然,在简单的例子中,常量是唯一的、连续的,并从零开始。问题在于,并非每个枚举都那么简单。 - Jonathan Leffler
1
@Jonathan:再次强调,程序员自己控制枚举的外观(他自己创建它!!!),如果他不想要空洞,为什么会刻意创建呢?你总是说“可能会有...”是的,可能会有,就像在Ada中一样!但是,如果他不想要它,他就不会创建它! - Mecki
1
@Jonathan:他说想要实现一个枚举。不是使用其他程序员已经实现的枚举(请仔细阅读问题!)当他实现它时,他只需不在其中放置任何空洞,你的论点就无效了。 - Mecki
@JonathanLeffler 我知道这是一个僵尸线程,但是... Ada不允许枚举中有重复的值。枚举的原始目的是定义表示不同事物的文字;在C/C++枚举中出现重复值通常意味着它们被滥用了。另请参见https://wiki.sei.cmu.edu/confluence/display/c/INT09-C.+Ensure+enumeration+constants+map+to+unique+values - cosimo193
显示剩余7条评论

2
我写了一个enum_iterator,结合使用Boost.Preprocessor的ENUM宏,实现了这个功能。
#include <iostream>
#include "enum.hpp"

ENUM(FooEnum, 
  (N)
  (A = 1)
  (B = 2)
  (C = 4)
  (D = 8));

int main() {
  litb::enum_iterator< FooEnum, litb::SparseRange<FooEnum> > i = N, end;
  while(i != end) {
    std::cout << i.to_string() << ": " << *i << std::endl;
    ++i;
  }
}

它将枚举声明为普通的旧式枚举,因此您仍然可以将其用于“正常”目的。迭代器也可用于具有连续值的其他普通枚举,这就是它具有第二个模板参数(默认为litb:: ConsequtiveRange<>)的原因。它符合双向迭代器要求。
可从这里下载这个有趣的代码。

2

我的同事已经实现了一个工具,可以生成类并完成你想要的大部分(如果不是全部)功能:

http://code.google.com/p/enumgen/

目前的实现是用Lisp语言编写的,但请不要因此而有偏见 :-)


1

在C++中没有简单的方法来做到这一点,最重要的原因是枚举常量不需要是唯一或连续的。从值到字符串的转换也是非常棘手的;我知道的解决方案涉及C/C++预处理器技巧——这是“hackery”这个术语的贬义用法。

我倾向于说“不行”;我不确定这是否正确,但它肯定是非常棘手的。


C++是C的超集,在C中它们必须是连续的,除非你明确跳过数字。请参考您选择的任何C标准。 - Mecki
@Mecki:正如我所说,枚举常量不需要是唯一的或连续的。是的,当然,默认情况下它们是不同的、连续的,并从零开始;然而,它们并不需要是这样的。 - Jonathan Leffler
1
就像在Ada中一样。在Ada中它们也不需要是连续的。你可以像在C中上面所做的那样定义它们(A为0,D为-200,E为30000)。因此,你不能将其作为任何参数来使用。在这方面,Ada和C完全相同。 - Mecki
我同意你的回答。已点赞。但这并不意味着不可能。 - Johannes Schaub - litb
2
Mecki,它们在形式上是相同的,但功能不同。这就是他问题的关键!如果C语言有first、last、succ、pred、pos和val枚举函数,那么我们就可以大展拳脚了。 - scott_karana
显示剩余4条评论


1

如果您对enumgen感兴趣,我使用您的示例制作了一个简单的演示。正如先前提到的,我使用common lisp来实现它,因此您编写的输入文件是lispy的,但我努力使语法合理。

这就是它:

$ cat Fruit.enum
(def-enum "Fruit" (("apple")
                   ("banana")
                   ("cherry")
                   ("peach")
                   ("grape")
                   ("INVALID_")))

$ enumgen Fruit.enum
Using clisp
;; Loading file /tmp/enumgen/enumgen.lisp ...
;; Loaded file /tmp/enumgen/enumgen.lisp
loading def file:
;; Loading file /tmp/enumgen/enumgen.def ...
;; Loaded file /tmp/enumgen/enumgen.def
generating output:
  Fruit.cpp
  Fruit.ipp
  Fruit.hpp
DONE

要查看生成的代码,请访问此网址: http://code.google.com/p/enumgen/source/browse/#svn/trunk/demo

虽然它已经非常功能丰富,但是还有很多可以通过在输入文件中设置变量或指定枚举器属性来进行调整。

例如,默认情况下,它使用std::string表示字符串名称,但是可以通过一些努力使用char const *或任何用户定义的字符串类。

您可以将多个名称映射到相同的枚举值,但必须选择一个作为“主要”名称,以便将该值映射到字符串将导致此名称(而不是其他名称)。

您可以明确地为枚举提供值,并且它们不需要是唯一的。(重复项是具有相同值的先前枚举的隐式别名。)

此外,您可以遍历所有唯一值,并针对每个值遍历其所有别名,这对于生成脚本语言“包装器”非常有用,就像我们使用ruby一样。

如果您对使用此内容感兴趣并有疑问,请随时通过电子邮件与我联系 (cuzdav@gmail.com)。

希望这可以帮到您。(除了测试套件和演示代码以及源代码外,没有太多的文档可供参考。)

Chris


0

这是未发布的软件,但似乎Frank Laub的BOOST_ENUM可能适合您的需求。我喜欢它的部分原因是您可以在类的范围内定义枚举,而大多数基于宏的枚举通常不允许您这样做。它位于Boost Vault中:http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=& 自2006年以来,它没有进行任何开发,因此我不知道它是否与新的Boost版本兼容。 在libs/test下查找使用示例。 还有一个Boost smart_enum(也未发布)。它执行了您问题的迭代器部分,但不会输出到字符串。http://cryp.to/smart-enum/


0

本文向您展示如何生成枚举值的字符串版本,尽管这需要您自己编写代码来实现。它还提供了一个预处理器宏,非常容易地允许枚举变量的递增和递减,只要您的枚举是连续的。

这个库提供了更灵活的递增和递减。

Boost Vault中的enum_rev4.6.zip库提供了简单的字符串转换。看起来它支持使用迭代器进行递增和递减(可能不太方便,但它可以工作)。它基本上没有文档,尽管libs/test目录包含一些很好的示例代码。


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