4

有一次,我曾考虑在 C++ 中实现一个类/模板,该类/模板将支持 Enum,其行为类似于在 Ada 中的行为。自从我考虑这个问题以来已经有一段时间了,我想知道是否有人解决了这个问题?

编辑:

抱歉,我应该澄清一下我认为在 Enum 的 Ada 实现中哪些功能有用。鉴于枚举

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

我们知道水果是列出的水果之一:苹果、香蕉、樱桃、桃子、葡萄。与 C++ 没有什么不同。

非常有用的是以下功能,您可以在 Ada 中的每个枚举中获得以下功能,而无需任何额外工作:

  • 打印出枚举值生成字符串版本
  • 您可以增加枚举变量
  • 您可以减少枚举变量

我希望这能更多地定义问题。


从评论中添加的注释

Ada 枚举的有用特性

  • 枚举中的第一个值是fruit'firstwhich 给出apple.
  • 枚举中的最后一个值是fruit'lastwhich 给出grape.
  • 递增操作fruit'succ(apple)给出banana.
  • 减量操作fruit'pred(cherry)也给出了banana.
  • 从枚举到整数的转换是fruit'pos(cherry)返回的2,因为 Ada 使用基于 0 的枚举。
  • 从整数到枚举的转换是fruit'val(2)返回cherry
  • 从枚举到字符串的转换是fruit'Image(apple)返回(大写)字符串"APPLE"
  • 从字符串到枚举的转换是fruit'Value("apple")返回值apple

另请参阅相关的 SO 问题:

4

8 回答 8

3

好的,让我们暂时搁置 C++。C++ 只是 C 的超集(这意味着可以在 C 中完成的所有事情也可以在 C++ 中完成)。所以让我们专注于plain-C(因为这是我熟悉的语言)。C有枚举:

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

这是完全合法的 C 并且值是连续的,apple 的值为 0,banana 的值为 apple + 1。您可以创建带有孔的枚举,但前提是您明确地制作这样的

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

apple 为 0,banana 为 1,cherry 为 20,因此 peach 为 21,grape 为 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 的值与任何枚举常量都不相同。

顺便说一句,为避免您必须说“enum fruit myFruit”,您可以使用 typedef 避免枚举。只需使用“typedef enum fruitfruit;” 在自己的线上。现在你可以说“fruit myFruit”,前面没有枚举。通常在定义枚举时直接完成:

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

fruit myFruit;

缺点是你不再知道fruit 是一个枚举,它可能是一个对象、一个结构或其他任何东西。我通常避免使用这些类型的 typedef,如果是 enum,我宁愿在前面写 enum,如果是 struct,我宁愿在前面写 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

这正是您想要的,还是我完全走错了路?

于 2008-11-19T23:35:56.170 回答
2

One of my colleagues has implemented a tool to generate classes that do most (if not all) of what you want:

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

The current implementation is in Lisp, but do not hold that against him :-)

于 2008-11-19T01:11:48.583 回答
2

我写了一个enum_iterator这样做的,连同一个ENUM使用 Boost.Preprocessor 的宏:

#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<>. 它符合双向迭代器的要求。

愚蠢的代码可以从这里下载

于 2009-09-17T03:43:24.420 回答
1

There isn't an easy way to do that in C++, not least because the enumeration constants are not required to be unique or contiguous. The conversion from value to string is also non-trivial; the solutions I know of involve C/C++ Preprocessor hackery - and that is a pejorative use of the term hackery.

I'm tempted to say "No"; I'm not certain that's correct, but it most certainly is non-trivial.

于 2008-11-19T01:05:49.687 回答
1

you might take a look at the java enum (http://madbean.com/2004/mb2004-3/) and this idea: http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern

于 2008-11-19T01:06:20.747 回答
1

如果你对 enumgen 感兴趣,我用你的例子做了一个简单的演示。如前所述,我使用通用 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​​ 一样。

如果您有兴趣使用它并有任何疑问,请随时通过电子邮件与我联系。(在 gmail 的 cuzdav)。

希望这可以帮助。(除了测试套件和演示代码之外,没有很多文档,如果您关心的话,可以参考源代码。)

克里斯

于 2008-11-19T23:43:48.667 回答
0

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

该库提供更灵活的递增和递减。

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

于 2008-11-19T01:41:35.873 回答
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/

于 2009-11-04T16:17:15.833 回答