tags:

views:

1496

answers:

9

At one point I had looked at implementing a class/template in C++ that would support an Enum that would behave like it does in Ada. It has been some time since I thought about this problem and I was wondering if anyone has ever solved this problem?

EDIT:

My apologies, I should clarify what functionality I thought were useful in the Ada implementation of the Enum. Given the enumeration

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

We know that fruit is one of the listed fruits: apple, banana, cherry, peach, grape. Nothing really different there from C++.

What is very useful are the following pieces of functionality that you get with every enum in Ada without any additional work:

  • printing out an enumerated value generates the string version
  • you can increment the enumerated variable
  • you can decrement the enumerated variable

I hope this defines the problem a bit more.


Notes added from comments:

Useful features of Ada enumerations

  • The first value in the enumeration is fruit'first which gives apple.
  • The last value in the enumeration is fruit'last which gives grape.
  • The increment operation is fruit'succ(apple) which gives banana.
  • The decrement operation is fruit'pred(cherry) which also gives banana.
  • The conversion from enumeration to integer is fruit'pos(cherry) which returns 2 because Ada uses 0-based enumerations.
  • The conversion from integer to enumeration is fruit'val(2) which returns cherry.
  • The conversion from enumeration to string is fruit'Image(apple) which returns the (upper-case) string "APPLE".
  • The conversion from string to enumeration is fruit'Value("apple") which returns the value apple.


See also related SO questions:

A: 

My apologies, I should clarify what functionality I thought were useful in the Ada implementation of the Enum. Given the enumeration

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

We know that fruit is one of the listed fruits: apple, banana, cherry, peach, grape. Nothing really different there from C++.

What is very useful are the following pieces of functionality that you get with every enum in Ada without any additional work:

  • printing out an enumerated value generates the string version
  • you can increment the enumerated variable
  • you can decrement the enumerated variable

I hope this defines the problem a bit more.

billcoke
Does Ada also provide 'first' and 'last' mechanisms, for establishing the first and last values in the enumeration?
Jonathan Leffler
Added to the question, not posed as an answer.
paxdiablo
Since the material in this 'answer' is now in the question, please remove this 'answer'.
Jonathan Leffler
A: 

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.

Jonathan Leffler
C++ is a superset of C and in C they are required to be contiguous, unless you explicitly skip numbers. Please refer to any C standard of your choice.
Mecki
@Mecki: enum { A = 0, B = 0, C = -1, D = -200, E = 30000 }; is perfectly valid. The results are not contiguous or unique!
Jonathan Leffler
Yes, because you MADE it have holes. But by default, if you give no numbers or if you just give a number for the first one, the standard DEMANDS them to be contiguous. And why would you intentionally make holes if you know you will later on increase/decrease them?
Mecki
@Mecki: so, as I said, enumeration constants are not required to be unique or contiguous. Yeah, sure, by default they are distinct, contiguous and start at zero; however, they are not required to be like that.
Jonathan Leffler
Just like in Ada. In Ada they don't need to be contiguous either. You can define them the same way you did above in C (A being 0, D being -200 and E being 30000). Thus you can't use that as argument for anything at all. In this aspect Ada and C are absolutely identical.
Mecki
i agree to your answer. upvoted. but this doesn't mean it's not possible
Johannes Schaub - litb
Mecki, they are identical in form, but not in functionality. That's the whole point of his question!If C had first, last, succ, pred, pos, and val Enum functions, then we'd be cooking with gas.
scott_karana
+1  A: 

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

Ray Tayek
+2  A: 

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 :-)

coryan
A: 

This article shows you how to generate the string version of an enumerated value, although it requires that you write the code to do so yourself. It also provides a preprocessor macro to very easily permit incrementing and decrementing enumerated variables, as long as your enum is continuous.

This library provides more flexible incrementing and decrementing.

The enum_rev4.6.zip library in the Boost Vault provides easy string conversions. It looks like it supports incrementing and decrementing using iterators (which is probably less convenient, but it works). It's basically undocumented, although the libs/test directory contains some good example code.

Josh Kelley
+1  A: 

Okay, let's leave C++ aside for a moment. C++ is just a superset of C (which means everything that can be done in C can be done in C++ as well). So let's concentrate on plain-C (because that's a language I know well). C has enumerations:

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

This is perfectly legal C and the values are contiguous, and apple has the value zero and banana has the value apple + 1. You can create enumerations with holes, but only if you explicitly make holes like this

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

While apple is 0 and banana is 1, cherry is 20, thus peach is 21 and grape is 22 and everything between 1 and 20 is undefined. Usually you don't want holes. You can do the following:

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");

This will print YES. You can also do the following:

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");

This will print NO, and the value of myFruit is not the same as any of the enumeration constants.

BTW, to avoid that you must say "enum fruit myFruit", you can avoid the enum with a typedef. Just use "typedef enum fruit fruit;" on an own line. Now you can say "fruit myFruit" without enum in front. It is often done directly when the enum is defined:

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

fruit myFruit;

Disadvantage is that you don't know anymore that fruit is an enum, it might be an object, a structure or anything else. I usually avoid these type of typedefs, I rather write enum in front if an enum and struct in front if a struct (I will just use them here because it looks nicer).

Getting the string value is not possible. At runtime an enumeration is just a number. That means, it's not possible if you don't know what kind of enumeration that is (as 0 might be apple, but it might also be a different thing of a different enumeration set). However, if you know it is a fruit, then it's easy to write a function that will do it for you. The preprocessor is your friend :-)

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;
}

Output:

banana
peach
I got called with fruit grape

This is exactly what you wanted or am I totally on the wrong track here?

Mecki
actually, the first value of an enum, unless specified otherwise, is always 0.
coppro
Can you back this up with a quote from any C standard that this is demanded? Just because (almost?) every compiler does so by default (as I wrote) doesn't mean that the standard actually demands that. If it does not, a compiler starting at 1 would behaving oddly, but valid, for example.
Mecki
6.7.2.2: If the first enumerator has no =, the value of its enumeration constant is 0.
Jonathan Leffler
You do not seem to understand the careful terminology I used, which is frustrating. Very frustrating. Of course it is true in the simple example that the constants are unique and contiguous and start from zero. The problem is that not every enumeration is that simple.
Jonathan Leffler
You have not addressed the issue of succ and pred functions (successor and predecessor values), nor the issue of incrementing or decrementing, in the non-trivial case where there are holes in the enumeration. I remain to be convinced that there is an easy way of handling that.
Jonathan Leffler
Do you know what Ada does with duplicate values in an enumeration?
Jonathan Leffler
Note that if you use the 'cherry = 20" version of the fruit enum, the fragment of code that prints YES as written then prints NO. Also note that myFruit no longer contains the value of any of the enumeration constants.
Jonathan Leffler
@Jonathan: Again, since the programmer he himself is in control how the enumeration will look like (he creates it!!!), why would he make holes if he don't want holes to be there. You always say there *CAN* be... yes, there can, just like in Ada! But if he don't want it, he simply won't make it!
Mecki
@Jonathan: He said that *HE* wants to implement an enum. Not take an enum some other programmer has implemented (read the question!) and when he implements it, he simply won't put any holes into it and your argument is nil and nothing.
Mecki
+1  A: 

If you're interested in enumgen, I made a simple demo with your example. As already mentioned, I implemented it using common lisp, so the input file you write is lispy, but I tried really hard to make the syntax reasonable.

Here it is:

$ 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

To view the generated code, visit this url: http://code.google.com/p/enumgen/source/browse/#svn/trunk/demo

While it's pretty feature-rich as it is, there are a lot of things that can be tweaked as well, by setting variables in the input file or by specifying attributes of the enumerators.

For example, by default it represents the string names using std::string, but it can use char const * or any user-defined string class given a little effort.

You can have multiple names map to the same enum value, but must pick one to be the "primary" such that mapping the value to a string will result in this name (as opposed to the others.)

You can explicitly provide values to the enums, and they do not need to be unique. (Duplicates are implicit aliases for the previous enum with the same value.)

Further, you can iterate over all the unique values, and for each value over all its aliases, which is useful if you want to generate script-language "wrappers" for these, like we do using ruby.

If you're interested in using this and have questions, feel free to contact me via email. (cuzdav at gmail).

Hope this helps. (There isn't a lot of documentation aside from the test suite and demo code, and the source if you care about that.)

Chris

A: 

I wrote an enum_iterator that does this, together with a ENUM macro using 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;
  }
}

It declares the enum as plain old enum, so you may still use it for "normal" purposes. The iterator can be used for other normal enums too that have sequential values, that's why it has a second template parameter which defaults to litb::ConsequtiveRange<>. It conforms to the bidirectional iterator requirements.

The silly code can be downloaded from here

Johannes Schaub - litb
A: 

It's unreleased software but it seems BOOST_ENUM from Frank Laub could fit the bill. The part I like about it is that you can define an enum within the scope of a class which most of the Macro based enums usually don't let you do. It is located in the Boost Vault at: http://www.boostpro.com/vault/index.php?action=downloadfile&amp;filename=enum%5Frev4.6.zip&amp;directory=&amp; It hasn't seen any development since 2006 so I don't know how well it compiles with the new Boost releases. Look under libs/test for an example of usage. There is also Boost smart_enum (not released either). It does the iterator part of your question but not the output to a string. http://cryp.to/smart-enum/

Alexis