Jump to content

  • Log In with Google      Sign In   
  • Create Account

Banner advertising on our site currently available from just $5!


1. Learn about the promo. 2. Sign up for GDNet+. 3. Set up your advert!


C++ enum names as strings


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
23 replies to this topic

#21 scgames   Members   -  Reputation: 2048

Like
0Likes
Like

Posted 06 March 2007 - 01:46 PM

Quote:
Original post by Zahlman
OK, let's say we have a header file with an enum (I'd rather not touch the problem of converting stupid #define usage into enumeration usage [wink] ):


#ifndef AUDIO_H
#include AUDIO_H

enum AUDIO {
AUDIO_U8 = < unique integer value >,
AUDIO_S8 = < unique integer value >,
AUDIO_U16LSB = < unique integer value >,
AUDIO_S16LSB = < unique integer value >,
AUDIO_U16MSB = < unique integer value >,
AUDIO_S16MSB = < unique integer value >,
AUDIO_U16 = AUDIO_U16LSB,
AUDIO_S16 = AUDIO_S16LSB,
#if ENDIANNESS == LITTLE_ENDIAN
AUDIO_U16SYS = AUDIO_U16LSB,
AUDIO_S16SYS = AUDIO_S16LSB,
#else
AUDIO_U16SYS = AUDIO_U16MSB,
AUDIO_S16SYS = AUDIO_S16MSB,
#endif
DEFAULT_FORMAT = AUDIO_S16SYS
}
#endif


Now, the aliasing problem is one that *can't* be resolved perfectly, for the simple reason that information is lost - a value of type AUDIO with value AUDIO_U16 is identical to a value of type AUDIO with value AUDIO_U16LSB, so there is no way to know which symbol was used in the source code. (After all, we can also create a variable of type AUDIO by reading in an int from a file and doing an explicit cast). However, let's say arbitrarily that we will resolve these problems by always stringizing an enum value according to the *first* enumerant in the enum with the appropriate value. Thus, in effect, the way to deal with aliased values is simply to *ignore* them ;)

(That is, a multimap doesn't help: there is no way to determine which value to select. So pragmatically, we have to just pick one, which takes us back to using a plain map. Incidentally, the scheme I propose automatically causes 'DEFAULT_FORMAT to become "AUDIO_S16MSB" on a PowerPC Mac', since that's the first enumerant with that value, so that's what will be used for the stringization. I suspect this simple heuristic will be best, really.)

We then write our script as follows:

- Invoke the preprocessor on the header file, i.e. ask the compiler what the
system endianness is ;)
- From the preprocessor's output, parse out the enum declaration.
- Initialize an empty associative array from integer values to strings.
- For each enumerant:
- Determine the int value.
- If it is not found in the associative array, add it, associating it with
the stringized version of the enumerant.
- Output code which initializes a std::map<int, const char*> with the
contents of our associative array, by iterating over our AA's keys and
generating corresponding map.insert() statements. (Better yet, write code
which wraps the whole thing up in a class. We can use a single class and
create a global static instance of it for each enum, and do the initialization
by clever use of operator chaining.)


Since implicit conversion does happen from the enumeration *to* an int, we can use enumeration values to look up the name in the std::map just fine.

The class might look something like this - all completely off the top of my head at 4:30 AM, but damned if it doesn't look good to me right now ;)

*** Source Snippet Removed ***

And we just write that code once; our auto-generated code just has to initialize Enumeration<E>::instance for each typename E that is appropriate (i.e., each enumeration in the program). We just emit something like:


Enumeration<AUDIO>
Enumeration<AUDIO>::instance("AUDIO_U8")("AUDIO_S8")("AUDIO_U16LSB")
("AUDIO_S16LSB")("AUDIO_U16MSB")("AUDIO_S16MSB");


And that should work even at top level, because we're just initializing the variable; no procedural code here, nope, no sir ;) Even if that doesn't work, though, I'm fairly sure that "Enumeration<AUDIO> Enumeration<AUDIO>::instance = Enumeration<AUDIO>(etc....)" will.
Sure, that's all perfectly reasonable.

In my previous posts I may have inadvertently given the impression that I didn't understand the solutions being presented, but I was really just trying to make the point that they could not be applied to the particular problem in question without modification.

In the case of the SDL_mixer initialization code, it's my intent that the log include the perhaps multiple 'names' of (human-readable strings associated with) the requested and queried audio formats. This means, for example, that if the requested format is AUDIO_S16SYS, the log reflects that this is in fact an alias for AUDIO_S16MSB on the system in question, and also that the requested format is the default format for that system.

Obviously this isn't terribly critical information - it's no great mystery what format will be selected for each system, and in any case it can easily be determined by looking at the corresponding header file. However, I like to log details like this during development, especially when working with multiple platforms, just to make sure I know exactly what's going on under the hood.

(As it happens, I added this detailed logging while trying to track down a bug that was causing the app to crash. It turned out to be a known bug in the current version of SDL_mixer. Although the bug wasn't directly related to the issue of audio format, the specific format requested did in fact cause the bug to manifest in different ways on each platform. For that reason it was useful during the debugging process to know exactly what format was being used, whether it was the default for that system, and so on, hence my particular interest in the aliases.)

As for automation, I don't know that it would be worth the trouble in this particular case (given that, like it or not, the enumerants in question are in fact macros buried in a 3rd-party library header file).

That said, this thread has given me some ideas about how to improve support for enumerated types in my in-game console system, while perhaps addressing the aliasing problem in a more clean and consistent manner, so cheers for that :)

Sponsor:

#22 JohnBolton   Members   -  Reputation: 1372

Like
0Likes
Like

Posted 07 March 2007 - 05:43 PM

Quote:
Original post by gdunbar
Quote:
Original post by JohnBolton
...
Simplified cleverness removed.



Belmont, CA, huh? Did you by any chance work at Oracle at some point? I did, and their code was full of this stuff...

Nope. Never worked at Oracle, but it is a common hack -- and an ugly one (as Muhammad wrote). However, it does have the advantage of only having to specify the name once (instead of 3 times).


John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!

#23 madeso   Members   -  Reputation: 592

Like
0Likes
Like

Posted 07 March 2007 - 10:32 PM

Along the same solution as sundog, but different. This is hand created but would probably be created with a tool like EasilyConfused's. Add a Day.enum, a custom build and the source file to your project. Done :)
class Day {
public:
enum Data {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
COUNT
};

explicit Day(Data pd) :d(pd) {
assert(Day::isValid(pd));
}

// assignment
void operator=(const Day& pOther) {
assert(Day::isValid(pOther.d));
d = pOther.d;
}
void operator=(const Data& pOther) {
assert(Day::isValid(pOther));
d = pOther;
}


// equality
bool operator==(const Day& pOther) const {
return Day::isEqual(d, pOther.d);
}
bool operator==(const Data& pOther) const {
return Day::isEqual(d, pOther);
}

// inequality
bool operator!=(const Day& pOther) const {
return !Day::isEqual(d, pOther.d);
}
bool operator!=(const Data& pOther) const {
return !Day::isEqual(d, pOther);
}

static const char* asString(Data d) {
#define DATA(a) case a: return #a;
switch(d) {
DATA(Sunday)
DATA(Monday)
DATA(Tuesday)
DATA(Wednesday)
DATA(Thursday)
DATA(Friday)
DATA(Saturday)
default:
return "Unknown";
}
#undef DATA
}

static bool isValid(Data d) {
#define DATA(a) case a:
switch(d) {
DATA(Sunday)
DATA(Monday)
DATA(Tuesday)
DATA(Wednesday)
DATA(Thursday)
DATA(Friday)
DATA(Saturday)
return true;
default:
return false;;
}
#undef DATA
}

const char* toString() const {
return asString(d);
}

static bool isEqual(const Data& a, const Data& b) {
assert(Day::isValid(a));
assert(Day::isValid(b));
return a == b;
}

private:
Day() {
}
Data d;
};

ostream& operator<<( std::ostream& os, const Day& e ) {
os << e.toString();
return os;
}
bool operator!=(const Day::Data& pOther, const Day& e ) {
return e != pOther;
}

bool operator==(const Day::Data& pOther, const Day& e) {
return e == pOther;
}


usage:
void main() {
cout << "Hello world" << endl;
Day d(Day::Monday);

d = Day::Sunday;
cout << "Today is " << d << ":" << endl << " ";
if( d != Day::Monday) {
cout << "I don't want to go to work";
if( Day::Sunday == d) {
cout << " and I don't have too :)" << endl;
}
else {
cout << ", but I have to - sigh..." << endl;
}
}
else {
cout << "I think I'll call in sick!" << endl;
}

cout << "Days of week: " << endl;
for(int i=0; i<Day::COUNT; ++i) {
Day d( static_cast<Day::Data>(i) );
cout << " " << d << endl;
}

cin.get();
}


#24 madeso   Members   -  Reputation: 592

Like
0Likes
Like

Posted 12 March 2007 - 03:45 AM

conversion application src

usage and Turtle.enum file that made the Turtle headers & source
/*Turtle.enum
convert! name=Index; return=int; header=no_header; from=exception; to=exception
convert! name=Description; return=char*; header=no_header; from="Generic turtle"; to=_none

#the turtles
Leonardo: Index=4; Description="The leader"
Splinter: Index=1; Description="The rat"
April O'Neil: Index=42; Description="The female reporter"
Shredder: Index=2; Description="the evil ninja"*/


#include "Turtle.hpp"
#include "convertTurtleBetweenDescription.hpp"
#include "convertTurtleBetweenIndex.hpp"

void display(Turtle t) {
cout << t << "(" << convert::TurtleToIndex(t) << "): " << endl;
cout << convert::TurtleToDescription(t) << endl;
}

void main() {
bool run = true;
while(run) {
string input;
cout << "> ";
getline(cin, input);
if( input == "exit" || input=="quit") {
run = false;
}
else if( input=="list" ) {
cout << " ------ TURTLES ------ " << endl;
for(int i=0; i<Turtle::COUNT; ++i) {
Turtle t( static_cast<Turtle::Data>(i) );
cout << "#" << i+1 << ": " << t << "(" << convert::TurtleToIndex(t) << ")" << endl;
}
cout << " ------------------ " << endl << endl;
}
else {
istringstream str(input);
int index = 0;
str >> index;
if( !str.fail() ) {
try {
Turtle t(convert::IndexToTurtle(index));
display(t);
}
catch(exception& exp) {
cout << exp.what() << endl;
}
}
else {
try {
Turtle t( input );
display(t);
}
catch(exception& exp) {
cout << exp.what() << endl;
}
}
cout << " ------------------ " << endl << endl;
}
}
}


While this may not be a good example, a Key enum might be a better choice. You need a list for all the keys, then a conversion routine that converts between strings for debugging, console key assigning, and perhaps config-saving. You also need to convert between windows, mac and *nix keys, unless you are using DirectX on windows and SDL and OpenGL on the others. I'm sure there are other similar conversion processes, unfortunately this tool only supports one-to-one conversions, but that may change. ie if your enum wants to combine the enter/return keys you have to do one key conversion by yourself, although all keys can be directed to convert both the A and the C to F(given a one way conversion).
Besides these drawbacks I hope someone find this tool helpful.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS