Sign in to follow this  

C++ enum names as strings

This topic is 3931 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

In Ada and probably numerous other languages, string representations of each enum value name are available in an elegant manner, and the language has built-in functions for converting between the actual value and the string representation, and vice versa. The language implementation is able to do this in an optimized manner as well. I suspect that it can't be as easy as that in C++, given its lower-level nature. If that's the case it's unfortunate, since it seems to be a recurring pattern for me to need to convert between enum values and string representations of those values. I was wondering if there is anything out there maybe in the form of a template that makes it easier for us to convert enum values to string representations and vice versa. Hopefully something compact as well. I thought I spotted something similar in some proposed Boost features, but there wasn't enough information on it, and I had hoped to avoid the headaches and confusion of installing a large template library for a few features. Thanks for any advice.

Share this post


Link to post
Share on other sites
You are correct that you can't do this in a built in way with C++.

When I needed something like this a couple of years ago, I wrote a little program that took a text file as input, for example say we had fruits.txt:


apples
oranges
pears


and generated a .h file like:


#ifndef fruits_H
#define fruits_H

enum fruits { apples,oranges,pears };
const char *fruits_str[]={ "apples","oranges","pears" };

#endif


Obviously this was a pretty trivial program to implement.

Share this post


Link to post
Share on other sites
I usually keep synchronized my enum and string arrays manually, with a static assert to help me catch at compile time to ensure they are the same size, and help to prevent forgetting to update one after the other.

Share this post


Link to post
Share on other sites
Another approach is to overload operator<<


enum fruits
{
apples,
oranges,
pears
};

std::ostream& operator<<( std::ostream& os, const fruits& fruit )
{
switch( fruit )
{
case apples: os << "apples"; break;
case oranges: os << "oranges"; break;
case pears: os << "pears"; break;
}
}


I suppose you could write a program to generate this as well.

Share this post


Link to post
Share on other sites
This is rather clever, even though a bit ugly.

Alternatives exist at the IDE-level. Using vim, Emacs, or Visual Studio you can use a script/macro that generates a string table from an enumeration automatically. Vim already has such a script here. It should be trivial to write one for Visual Studio using C#/VB.NET.

Share this post


Link to post
Share on other sites
Although I'm sure there are more sophisticated approaches one could take, I generally use a std::map, e.g.:

std::map<Uint16,std::string> formats = boost::assign::map_list_of
(AUDIO_U8, "AUDIO_U8" )
(AUDIO_S8, "AUDIO_S8" )
(AUDIO_U16LSB, "AUDIO_U16LSB")
(AUDIO_S16LSB, "AUDIO_S16LSB")
(AUDIO_U16MSB, "AUDIO_U16MSB")
(AUDIO_S16MSB, "AUDIO_S16MSB")
(AUDIO_U16, "AUDIO_U16" )
(AUDIO_S16, "AUDIO_S16" )
(AUDIO_U16SYS, "AUDIO_U16SYS")
(AUDIO_S16SYS, "AUDIO_S16SYS");


Advantages of this approach include:

1. Easier to maintain than a switch statement
2. More flexible than overloading operator<<()
3. Enumerants can have arbitrary values (as opposed to the array method)
4. 'Invalid' enumerants won't lead to UB (as opposed to the array method)

Share this post


Link to post
Share on other sites
I've used EasilyConfused's pattern in the past (though often with a "MAX_FRUIT" enum at the end which helps in looping over the group and similar constraint needs).

jyk's suggestion though is clearly superior in the face of enums which aren't simply 0->MAX.

Share this post


Link to post
Share on other sites
Quote:
Original post by EasilyConfused
You are correct that you can't do this in a built in way with C++.

When I needed something like this a couple of years ago, I wrote a little program that took a text file as input, for example say we had fruits.txt:


apples
oranges
pears


and generated a .h file like:


#ifndef fruits_H
#define fruits_H

enum fruits { apples,oranges,pears };
const char *fruits_str[]={ "apples","oranges","pears" };

#endif


Obviously this was a pretty trivial program to implement.


Here's a way to avoid putting more tools into your build chain (but it uses boost, and worse than templates --- it uses macros):

#include <boost/preprocessor.hpp>

#define SEQ (apples)(oranges)(pears)
#define TO_STR(unused,data,elem) BOOST_PP_STRINGIZE(elem) ,

enum fruits { BOOST_PP_SEQ_ENUM(SEQ) };
const char * fruit_strings[]={ BOOST_PP_SEQ_FOR_EACH(TO_STR,~,SEQ) };

#undef SEQ
#undef CAT



EDIT: Enterprisey-fied:


#include <boost/preprocessor.hpp>

#define PROJECT_PREFIX_DO_EVIL_TO_STR(unused,data,elem) BOOST_PP_STRINGIZE(elem) ,
#define PROJECT_PREFIX_DO_EVIL(enum_,strings,elements) \
enum enum_ { BOOST_PP_SEQ(elements) }; \
const char * strings[]={ BOOST_PP_SEQ_FOR_EACH(PROJECT_PREFIX_DO_EVIL_TO_STR,~,SEQ) };

//-----------------------

PROJECT_PREFIX_DO_EVIL(fruit,fruit_strs,(apple)(pear)(pineapple))
PROJECT_PREFIX_DO_EVIL(animal,animal_strs,(dog)(cat)(panda)(monkey))



(edit/note: trailing whitespaces to preserve \s inserted)

Share this post


Link to post
Share on other sites
Quote:
Original post by Simian Man
Another approach is to overload operator<<


enum fruits
{
apples,
oranges,
pears
};

std::ostream& operator<<( std::ostream& os, const fruits& fruit )
{
switch( fruit )
{
case apples: os << "apples"; break;
case oranges: os << "oranges"; break;
case pears: os << "pears"; break;
}
}


I suppose you could write a program to generate this as well.



Try that and include this header from several different cpp's... Now
take a hex-editor and search the generated exe for "apples" - the
entire lookup table will be there as often as you included it.

Regards



Share this post


Link to post
Share on other sites
Quote:
Original post by Muhammad Haggag
This is rather clever, even though a bit ugly.

Here is a simplified version:

#define DAYS_OF_THE_WEEK \
ENUM_OR_STRING( Sunday ), \
ENUM_OR_STRING( Monday ), \
ENUM_OR_STRING( Tuesday ), \
ENUM_OR_STRING( Wednesday ), \
ENUM_OR_STRING( Thursday ), \
ENUM_OR_STRING( Friday ), \
ENUM_OR_STRING( Saturday )

// Enum

#undef ENUM_OR_STRING
#define ENUM_OR_STRING( x ) x

enum DaysOfTheWeek
{
DAYS_OF_THE_WEEK
};

// Strings

#undef ENUM_OR_STRING
#define ENUM_OR_STRING( x ) #x

char * DaysOfTheWeekStrings[] =
{
DAYS_OF_THE_WEEK
};
You could also put the elements in an include file instead of a macro:
---- DaysOfTheWeek.h

ENUM_OR_STRING( Sunday ),
ENUM_OR_STRING( Monday ),
ENUM_OR_STRING( Tuesday ),
ENUM_OR_STRING( Wednesday ),
ENUM_OR_STRING( Thursday ),
ENUM_OR_STRING( Friday ),
ENUM_OR_STRING( Saturday )

---- source file

// Enum

#undef ENUM_OR_STRING
#define ENUM_OR_STRING( x ) x

enum DaysOfTheWeek
{
#include "DaysOfTheWeek.h"
};

// Strings

#undef ENUM_OR_STRING
#define ENUM_OR_STRING( x ) #x

char * DaysOfTheWeekStrings[] =
{
#include "DaysOfTheWeek.h"
};

Share this post


Link to post
Share on other sites
Quote:
Original post by Kitt3n
Try that and include this header from several different cpp's... Now
take a hex-editor and search the generated exe for "apples" - the
entire lookup table will be there as often as you included it.


Thinking about it, that would be equally true of the example I provided. I must have used that approach back in the early days when I used to just preprocess my entire program into one translation unit. [smile]

I guess to use my autogenerated sourcefiles approach properly, the program would have to generate a .h and .cpp file. Given the trouble and complexity this starts creating, I'd probably go more with something like jyk or JohnBolton or others have suggested.

Share this post


Link to post
Share on other sites
Although I stand by my suggestion of using a std::map for associating strings with enumerants, in all fairness I have to acknowledge that there was a mistake in my posted example:

std::map<Uint16,std::string> formats = boost::assign::map_list_of
(AUDIO_U8, "AUDIO_U8" )
(AUDIO_S8, "AUDIO_S8" )
(AUDIO_U16LSB, "AUDIO_U16LSB")
(AUDIO_S16LSB, "AUDIO_S16LSB")
(AUDIO_U16MSB, "AUDIO_U16MSB")
(AUDIO_S16MSB, "AUDIO_S16MSB")
(AUDIO_U16, "AUDIO_U16" )
(AUDIO_S16, "AUDIO_S16" )
(AUDIO_U16SYS, "AUDIO_U16SYS")
(AUDIO_S16SYS, "AUDIO_S16SYS");

Oops! Some of these enumerants are aliases for each other. The *SYS variants are aliases for the *SB variants as determined by the endianness of the system, while *16 aliases to *16LSB.

Furthermore, I didn't notice the error until I compared this version of the code with a version that inserted the map elements manually. Because map_list_of inserts the elements in a different order than the corresponding 'manual' code, the end effect of the aliasing was different in each case. Subtle :-|

Now, unless I'm missing something else obvious, this doesn't negate any of the advantages of this method (over arrays or switch statements) mentioned earlier. However, although using a map means you don't have to worry about the particular values of the enumerants, you do have to know if any of them alias each other. This is particularly important when dealing with a third-party API rather than your own code (I had to check the corresponding SDL header to know that *16 aliases *16LSB, which isn't necessarily intuitively obvious).

Share this post


Link to post
Share on other sites
Quote:
Original post by jyk
However, although using a map means you don't have to worry about the particular values of the enumerants, you do have to know if any of them alias each other.


Which is why I would generate code rather than using tools like boost::assign. :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
Which is why I would generate code rather than using tools like boost::assign. :)
Sure, although I'd be interested to know how you'd apply this solution in the particular case that I presented.

Unless I've missed something, in both of the 'generated code' examples presented earlier in the thread the enums themselves are generated, not just the associated strings. Therefore the values are known, and furthermore the values are known to be sequential and zero-based. The corresponding strings are then stored in an array.

In the case I presented the enumerants come from a third-party library. Although one can of course examine the appropriate header file, let's say for the sake of argument that the values are not known. How would one automate the generation of associated strings in this case?

Replacing map with multimap in my previous example solves the problem and makes the solution robust in the presence of arbitrary values for the enumerants, including those that are aliases of each other. However, this comes at the cost of added complexity elsewhere in the code.

So although I'm sure you're right, I'm not quite clear on how your suggestion to use code generation rather than 'naive' application of a map specifically addresses the problem I presented earlier. I'd certainly be interested in seeing a concrete example - you always seem to have interesting tricks up your sleeve :-)

Share this post


Link to post
Share on other sites
A generation tool can read the enumeration description from a file and output the correct display function. For instance:


// some defines in an "enum-helper" file
#define GEN_OUTPUT(X)
#define GEN_ALT_OUTPUT(X)
#define GEN_IGNORE

// ======================================================
// An enumeration definition, which is tagged
// to generate an output function
GEN_OUTPUT(generated.foo.hpp)
enum foo {

// use the default name and value
apples,

// set the numeric value ourselves
bananas = 35,

// set the displayed text ourselves
GEN_ALT_OUTPUT(green lemon) lime = 34,

// we don't want this value to be used
GEN_IGNORE ignored = 35
};

#include "generated.foo.hpp"

// ======================================================
// An example of generated file:
const char _foo_apples[] = "apples";
const char _foo_bananas[] = "bananas";
const char _foo_lime[] = "green lemon";

STATIC_ASSERT(apples != bananas);
STATIC_ASSERT(bananas != lime);
STATIC_ASSERT(apples != lime);

std::ostream& operator<<(std::ostream& out, const foo& f)
{
switch(f)
{
case apples: return out << _foo_apples;
case bananas: return out << _foo_bananas;
case lime: return out << _foo_lime;
default: assert(false);
}
}




Optimizations would include transforming the switch statement into an if-else tree based on the likelihood of each enumeration value.

If enumeration values overlap, the generated operator would fail to compile. It's also possible to generate a set of static assertions, as illustrated above, which would cause failure in a cleaner way.

Share this post


Link to post
Share on other sites
Quote:
Original post by ToohrVyk
A generation tool can read the enumeration description from a file and output the correct display function. For instance:

*** Source Snippet Removed ***

Optimizations would include transforming the switch statement into an if-else tree based on the likelihood of each enumeration value.

If enumeration values overlap, the generated operator would fail to compile. It's also possible to generate a set of static assertions, as illustrated above, which would cause failure in a cleaner way.
Let me describe more clearly the particular case from which my example was drawn.

SDL uses a number of macros to identify various audio sample formats. Some are aliases for others, and some are switched based on the endianness of the platform. I'll 'paraphase' the relevant portion of the header file here:

#define AUDIO_U8        < unique integer value > 
#define AUDIO_S8 < unique integer value >
#define AUDIO_U16LSB < unique integer value >
#define AUDIO_S16LSB < unique integer value >
#define AUDIO_U16MSB < unique integer value >
#define AUDIO_S16MSB < unique integer value >
#define AUDIO_U16 AUDIO_U16LSB
#define AUDIO_S16 AUDIO_S16LSB
#if ENDIANNESS == LITTLE_ENDIAN
#define AUDIO_U16SYS AUDIO_U16LSB
#define AUDIO_S16SYS AUDIO_S16LSB
#else
#define AUDIO_U16SYS AUDIO_U16MSB
#define AUDIO_S16SYS AUDIO_S16MSB
#endif
#define DEFAULT_FORMAT AUDIO_S16SYS

The purpose of the code I posted is relatively simple: to associate with these values human-readable strings for output to a logging system. The log records, among other things, whether the requested specs match the queried specs, and what the requested format translated to on the system in question (e.g. DEFAULT_FORMAT becomes AUDIO_S16MSB on a PowerPC Mac).

The question then is how best to automate the generation of string representations for these values, and whether it's worth the trouble. It seems to me that none of the examples presented thus far, including the example you posted above, are directly applicable in this case without significant modification (the fact that the enumerants are macros is incidental - the same would be true were they elements of an enum).

I'm happy to concede the point based solely on your and Zahlman's considerable expertise, but I would still be interested in seeing how the proposed solutions could be applied here without undue difficulty.

Share this post


Link to post
Share on other sites
(EDIT: Fixed stupidly wide code block; reformatted the ending comments; added a comment about serendipitous alias resolution. Also fixed some variable names - obviously I can't call an instance of an enumeration 'enum' [razz])

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


template <typename E>
class Enumeration {
// Map values must always be string literals! We will never do any memory
// management here; we freely copy pointers, and at the end, the static
// section of the executable data is cleaned up in one go, and there are no
// leaks or double-deletes.
typedef const char* symbol;
typedef std::map<int, symbol> table_t;
table_t table;
static symbol nil;
static Enumeration instance;
int nextkey;

public:
// We will use operator chaining in order to initialize a single static
// instance that's accessible to preprocessor magics. :) No need to make this
// a Singleton; this class only gets instantiated by our auto-generated code.
// Callers don't need to know it exists to use it ;)
Enumeration(symbol name, int value = 0) : nextkey(0) { (*this)(name, value); }

Enumeration& operator()(symbol name, int value = nextkey) {
assert(value >= nextkey);
nextkey = value + 1;
table[value] = name;
return *this;
}

symbol operator()(E value) {
table_t::iterator it = table.find(value);
return (it == table.end()) ? nil : it->second;
}
};

template <typename E>
Enumeration<E>::symbol Enumeration<E>::nil = "";
// If we don't trust the compiler to share that string constant, we could force
// that by making a separate "" constant instead and having nil alias it...

// Now, the templating so far just looks like it gives us type-safety for the
// operator()(E). But actually it will let us implement some obscene syntactic
// sugar as well, if I'm thinking clearly ;)
template <typename E>
ostream& operator<<(ostream& os, const E& e) {
return os << Enumeration<E>::instance(e);
}
// And thus we accomplish the claimed goal of users not needing to know about
// the Enumeration class. For non-enumeration types, this should not cause any
// interference, due to SFINAE - again, if I'm thinking clearly... if not, I'm
// sure there's a way we can make it work... ;)



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.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I would create a class for each enumeration. It might be a little more work but you can just work from generic template and fill in the values, or use a script. You get the added benefit of type safety, and if there are functions that make sense to put in the enumration directly, then you can just stick them in there and maintain your . For example you could have a months enumeration and then have a funciton month.days() which returns the number of days in the month(well I guess you would have an exception for February, but just as an example !)

Example:

.h:

class MyEnumeration {

private std:string name;

public:
static MyEmuneration enum1;
static MyEmuneration enum2;
static MyEmuneration enum3;
...
static map stringMap();

int ordinal();

std::string name();

private:

int ordinal;
std:string name;

void MyEnumeration(int ordinal, std::string name);

}

.cpp:

MyEnumeration MyEnumeration::enum1(0, "enum1");
MyEnumeration MyEnumeration::enum1(1, "enum2");
MyEnumeration MyEnumeration::enum1(2, "enum3");
MyEnumeration MyEnumeration::map();

void MyEnumeration::MyEnumeration (int ordinal, std::string name) {
this->ordinal = ordinal;
this->name = name;
stringMap->add(name, this);
}

int MyEnumeration::ordinal() {
return ordinal;
}

std::string MyEnumeration::name() {
return name;
}


If you need to map back from ordinals to enumerations, you just add another map and modify the constructor . If you need an iterator, you can provide one, etc.

Share this post


Link to post
Share on other sites
Err, well what I wanted to say that the above was generally good programming practice. One of the problems with C++ enums is that they are integers, so there is no type information when passing paramters. If you get paramter order fouled up or pass an illegal value, this error will be detected only at run time.

You could also avoid passing the ordinal in the constructor with a static ordinal field that gets incremented every time the constuctor is called. This way you won't have to pass an ordinal value to the constuctor. Also, you could implement next() and prev() functions or even use the STL's iterator mechanism.

Share this post


Link to post
Share on other sites
Quote:
Original post by JohnBolton
Quote:
Original post by Muhammad Haggag
This is rather clever, even though a bit ugly.


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. I believe there was one header file that got included 7 times into a source file doing cleverness like the above. Ultimately I think this path ends up being more confusing than helpful; at least it confused the hell out of me! Having experienced that, I'd probably be more likely to go with matching defines and strings, and some sort of asserts to be sure things stay in sync.

By no means am I denigrating what you've suggested; it's probably the most "correct" way to do something like like in C/C++. I just want to issue this cautionary tale.

Thanks,
Geoff

Share this post


Link to post
Share on other sites
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 :)

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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();
}

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

This topic is 3931 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this