• Advertisement
Sign in to follow this  

What are possible side effects and/or portability concerns to this?

This topic is 415 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

I was toying around with auto-registration of enum tables without resorting to fancy solutions like custom enum classes. I pretty much expected this to be a lost cause. However, this genuinely surprised me.

I'm on VS2013, debug mode.

Handling sequential enums is easy. However I didn't really have a good idea as to handle arbitrarily constructed enum/flag tables. Nevertheless I decided to give it a go for the heck of it. Basically my assumption (which was, admittedly, unfounded) was that enums are evaluated at compile time and since VS2013 doesn't have constexpr, I was ready to go full experimental on this and bring out the big guns (template recursion) to turn something like this:

IMPL_ENUM(eEnum,
eVal1 = (1<<0),
eVal2 = (1<<1),
eVal3 = (1<<2));

into something like this:

enum class eEnum : uint32
{
eVal1 = (1<<0),
eVal2 = (1<<1),
eVal3 = (1<<2)
};

and this:

static const char* STRINGIFY_eEnum[]
{
"eVal1",
"eVal2",
"eVal3"

};

Which is when I gave it a whir and discovered that the enum is actually built during runtime. Which means I can macro FOR_EACH through the __VA_ARGS__ and use an end node like this to modify the string to whatever I want. In this case:
 
const char* _make_enum_string(IN char* str)
{
	boolean end					= false;
	size_t len					= STRLEN(str);
	for(size_t i = 0; i < len; ++i)
		{
		// if the string contains a whitespace, allocate a new one and use that one instead
		if(isspace(str[i]))
			{
			char* newStr			= new char[i + 1];
			for(size_t j = 0; j < i; ++j)	{ newStr[j] = str[j]; }
			newStr[i]			= 0;
			return				newStr;
			}
		}

	return str;
}

#define __list_enum(T, param)				_make_enum_string(#param), 


And it works! Quite contrary to my expectations. Which is either fantastic or I'm doing something that is really not portable and/or is prone to deprecation. Either way, it feels a little bit  :ph34r: .

Which, in turn, brings me to my original question: I'm a little bit out of my comfort zone in terms of the C++ standard here (nor is the VS2013 compiler the most compliant one out there, even in terms of when it was released) - are there any caveats to doing something like this? My present concerns aren't on porting my code (or, if anything, then to a newer version of VS). However, I do want to keep as much of my code as possible compatible with GCC for when/if there is reason to start thinking about porting the it to Linux.

Share this post


Link to post
Share on other sites
Advertisement

Which is when I gave it a whir and discovered that the enum is actually built during runtime

I'm a little unclear what you mean by this, but enum values are definitely be computed at compile time.

Macros run as part of the preprocessor, which runs *before* compilation. Your macro is effectively inserting a call to a runtime function at each and every place you are attempting to retrieve the enum string (so you aren't computing that value once, you are recomputing it at every invocation). Edited by swiftcoder

Share this post


Link to post
Share on other sites

This would be easier to answer if you could write a small complete program and post it here.

 

This is what you get when you spend 12 hours coding without breaks - confusion over the simplest things :wacko: . The enum has nothing to do with the string being built. In fact it is never touched. The above code basically resolves to this pseudocode:

enum class eEnum : uint32 {
Val1,
Val2,
Val3
};

static const char* stringfied_enum[] = { 
 string_manipulate(STRINGIFY(eEnum::Val1)),
 string_manipulate(STRINGIFY(eEnum::Val2)),
 string_manipulate(STRINGIFY(eEnum::Val3))
};

Where string_manipulate() is a function that replaces the preprocessor-strigified enum field name with a dynamically allocated one. Being static, this gets evaluated per module that includes the enum definition.

Share this post


Link to post
Share on other sites
As for the questions about style and implementation concerns, I'll point out that the functionality is part of some other languages, but is not part of C++. The functionality has been suggested to the language committee many times, including back in the 90's when it was first standardized, but it was rejected for many reasons.

That code is leaking implementation details. That code is conflating types of data. That code shows a lack of understanding about what enums are in the language, which is quite different from languages like C# or Java.

As one of many such concerns, what will you do when multiple enumerators have the same value? For instance, enum values like { begin=0, start=0, first=0, ... is perfectly legal and appropriate for an enum. None of those solutions presented so far address this type of real concern.

While this is different in other languages, in c++ enum values are essentially integer values. There are no uniqueness constraints, and there are all the assorted issues like integer promotion rules that apply. Many people love to abuse enums into something they are not through macros and other means, but generally over time it adds complexity and programmer brainpower requirements, and tends to introduce far many more problems than it solves.

Share this post


Link to post
Share on other sites

As for the questions about style and implementation concerns, I'll point out that the functionality is part of some other languages, but is not part of C++. The functionality has been suggested to the language committee many times, including back in the 90's when it was first standardized, but it was rejected for many reasons.

That code is leaking implementation details. That code is conflating types of data. That code shows a lack of understanding about what enums are in the language, which is quite different from languages like C# or Java.

As one of many such concerns, what will you do when multiple enumerators have the same value? For instance, enum values like { begin=0, start=0, first=0, ... is perfectly legal and appropriate for an enum. None of those solutions presented so far address this type of real concern.

While this is different in other languages, in c++ enum values are essentially integer values. There are no uniqueness constraints, and there are all the assorted issues like integer promotion rules that apply. Many people love to abuse enums into something they are not through macros and other means, but generally over time it adds complexity and programmer brainpower requirements, and tends to introduce far many more problems than it solves.

 

It starts with the what the code aims to do, which is (what I should probably have started with :) ):

a) provide quasi-automated reflection for string matching and value translation when loading resource files
b) require minimal manual adjustment to existing code
c) avoid code bloat and manual work like fully-fledged enum classes tend to do

This isn't an all-around solution for every enum, but rather an optional solution for specific enums that can evolve as I implement new features, but need to be fully up to date/free of mistakes and easily registered with things like the resource manager. Basically, I don't want to maintain a reflection mechanism separately and I don't want to worry about syntax/complexity when creating a new enum that requires it.

I'm acutely aware of unique/non-sequential values in enum fields and memory/performance concerns, which is why I ended up going with the following solution:

a) creating an enum via DECL_ENUM() delcares the enum itself along with a raw string map generated by the preprocessor and uninitialized string and value list pointers, which are declared as extern
b) if the enum requires reflection, REFLECT_ENUM() then parses the raw string list during runtime, clipping fields with custom values to just the names, emulates sequential value progression as in regular simple enums and evaluates custom fields to construct the value list. Essentially it does what the compiler does, but does not support tokens not present in the enum declaration itself.

The overhead is single call to REFLECT_ENUM(eEnum) in any a source file, a few allocations and some string manipulation, which I'm fairly happy with.

To be honest I don't even know how enums work in C# or Java :).

 

The call to 'new' bugs me for having no matching delete, and for occurring before main. This would fail code review on some cross platform engines that I've worked on.

 

There are two options that I can think of: I can defer the reflected string/value initialization to a function or use my memory manager, which is initialized on demand. Without further manual decoupling of definition from initialization this would resolve either one of the two issues you pointed out, although could you please elaborate on why calling new before main is unfavorable?

Share this post


Link to post
Share on other sites

 

REFLECT_ENUM() then parses the raw string list during runtime, clipping fields with custom values to just the names

I'm not clear on why this step is needed. Why is there any data in the string other than the name, in the first place?

 

 

Initialization of the reflection data. Consider the following:

#define DECL_ENUM(_name, ...)																						\
    typedef uint32 TYPEOF_##_name;																					\
    enum class _name : TYPEOF_##_name { __VA_ARGS__, eEnumDelimiter };												\
    static const char* STRINGIFY_raw_##_name[] { DO_FOR_EACH(__stringify_enum_raw, 0, __VA_ARGS__) "eEnumDelimiter" }; \
    extern const char** STRINGIFY_##_name;																			\
    extern TYPEOF_##_name* NUMERIFY_##_name;																		\


#define REFLECT_ENUM(_name)																						\
    const char** STRINGIFY_##_name = preprocess_enum(ENUMEX_GetNumFields(_name), STRINGIFY_raw_##_name);			\
    TYPEOF_##_name* NUMERIFY_##_name = evaluate_enum<TYPEOF_##_name>(ENUMEX_GetNumFields(_name), STRINGIFY_##_name, STRINGIFY_raw_##_name);

Where preprocess_enum() initalizes an array of strings that contain plain field names, stripping away the assignment part (if present) and evaluate_enum() initializes an array of values as parsed (and optionally evaluated) from the raw string array.

The upshot is that enum behaves conventionally without calling REFLECT_ENUM(), but in order to be able to enumerate field names and values, these need to be explicitly initialized.

Share this post


Link to post
Share on other sites
So why are you trying to do this in C++? There are many languages that have it as part of the language.

This isn't functionality the language provides, it is not a good fit for the language, and there are far better tools available. It's like the case of having a hammer so everything looks like a nail but in this case a better tool is a lathe.

From what you describe as your goals, write up some python code that does what you need, then link that in or run it through a scripting system at runtime.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement