Problem with enum and binary or operator

Started by
51 comments, last by ChaosEngine 6 years, 9 months ago

Finalstate, look at my first post, you can use that.  I showed you how to do it.

Also your attempt where you claimed you had compilation errors, works fine on my end, here's the full code:


# include <thread>
# include <mutex>
# include <condition_variable>
# include <vector>
# include <deque>
# include <atomic>
# include <iostream>
# include <functional>
# include <algorithm>

using namespace std;




typedef enum fpl_KeyboardModifierType {
	fpl_KeyboardModifierType_None = 0,
	fpl_KeyboardModifierType_Alt = 1 << 0,
	fpl_KeyboardModifierType_Ctrl = 1 << 1,
	fpl_KeyboardModifierType_Shift = 1 << 2,
	fpl_KeyboardModifierType_Super = 1 << 3,
} fpl_KeyboardModifierType;

#ifdef __cplusplus
	inline fpl_KeyboardModifierType operator |(fpl_KeyboardModifierType a, fpl_KeyboardModifierType b) {
		return static_cast<fpl_KeyboardModifierType>(static_cast<int>(a) | static_cast<int>(b));
	}
	inline fpl_KeyboardModifierType operator &(fpl_KeyboardModifierType a, fpl_KeyboardModifierType b) {
		return static_cast<fpl_KeyboardModifierType>(static_cast<int>(a) & static_cast<int>(b));
	}
	inline fpl_KeyboardModifierType& operator |=(fpl_KeyboardModifierType& a, fpl_KeyboardModifierType b) {
		return a = a | b;
	}
#endif


void Func(fpl_KeyboardModifierType f) {
	if (f & fpl_KeyboardModifierType_Alt) cout << "fpl_KeyboardModifierType_Alt" << endl;
	if (f & fpl_KeyboardModifierType_Ctrl) cout << "fpl_KeyboardModifierType_Ctrl" << endl;
	if (f & fpl_KeyboardModifierType_Shift) cout << "fpl_KeyboardModifierType_Shift" << endl;
	if (f & fpl_KeyboardModifierType_Super) cout << "fpl_KeyboardModifierType_Super" << endl;
	}


// ----- main -----
void main() {

	Func(fpl_KeyboardModifierType_Shift | fpl_KeyboardModifierType_Super);


	// done
	std::cout << "done" << std::endl;
	getchar();
	}

Just copied/paste and threw it together, no changes needed.  What you were doing worked fine.  Its best to test these things in a smaller 'test unit' alone, and then bring them into the larger project.

Advertisement
1 hour ago, Ryan_001 said:

Finalstate, look at my first post, you can use that.  I showed you how to do it.

Also your attempt where you claimed you had compilation errors, works fine on my end, here's the full code:



# include <thread>
# include <mutex>
# include <condition_variable>
# include <vector>
# include <deque>
# include <atomic>
# include <iostream>
# include <functional>
# include <algorithm>

using namespace std;




typedef enum fpl_KeyboardModifierType {
	fpl_KeyboardModifierType_None = 0,
	fpl_KeyboardModifierType_Alt = 1 << 0,
	fpl_KeyboardModifierType_Ctrl = 1 << 1,
	fpl_KeyboardModifierType_Shift = 1 << 2,
	fpl_KeyboardModifierType_Super = 1 << 3,
} fpl_KeyboardModifierType;

#ifdef __cplusplus
	inline fpl_KeyboardModifierType operator |(fpl_KeyboardModifierType a, fpl_KeyboardModifierType b) {
		return static_cast<fpl_KeyboardModifierType>(static_cast<int>(a) | static_cast<int>(b));
	}
	inline fpl_KeyboardModifierType operator &(fpl_KeyboardModifierType a, fpl_KeyboardModifierType b) {
		return static_cast<fpl_KeyboardModifierType>(static_cast<int>(a) & static_cast<int>(b));
	}
	inline fpl_KeyboardModifierType& operator |=(fpl_KeyboardModifierType& a, fpl_KeyboardModifierType b) {
		return a = a | b;
	}
#endif


void Func(fpl_KeyboardModifierType f) {
	if (f & fpl_KeyboardModifierType_Alt) cout << "fpl_KeyboardModifierType_Alt" << endl;
	if (f & fpl_KeyboardModifierType_Ctrl) cout << "fpl_KeyboardModifierType_Ctrl" << endl;
	if (f & fpl_KeyboardModifierType_Shift) cout << "fpl_KeyboardModifierType_Shift" << endl;
	if (f & fpl_KeyboardModifierType_Super) cout << "fpl_KeyboardModifierType_Super" << endl;
	}


// ----- main -----
void main() {

	Func(fpl_KeyboardModifierType_Shift | fpl_KeyboardModifierType_Super);


	// done
	std::cout << "done" << std::endl;
	getchar();
	}

Just copied/paste and threw it together, no changes needed.  What you were doing worked fine.  Its best to test these things in a smaller 'test unit' alone, and then bring them into the larger project.

 

finalstate *sigh*

 

A single enum will compile, but a second one will break it!

 

I tested it on vs 2015 and 2017:

Adding a second enum compiles... will test it out in my library...

You don't need the "typedef" in C++.

 

Hm, and while nit-picking, "main" returns an integer.

I have no idea why it wont compile, i made a branch and checked in the enum operators - last try before i give up:

https://github.com/f1nalspace/final_game_tech/blob/enum_nonsense/final_platform_layer.h

https://github.com/f1nalspace/final_game_tech/tree/enum_nonsense/demos

 

Can someone open it up in vstudio - at least reproduce the bug?

On 7/14/2017 at 8:34 AM, Finalspace said:

A normal enum are just a single 32 bit integer value, including its field accessible by a constant.

Nope.  It is implementation dependent size if none is specified, and on most major compilers defaults to a 32 bit signed integer usually, but sometimes defaults to a 32 bit unsigned value or a 64 bit signed or unsigned value, and may potentially be something else entirely.

On 7/14/2017 at 8:34 AM, Finalspace said:

So using it as flags are totally valid, because its just a stupid integer.

No. An enum is not "just a stupid integer".  At no point in the language history has it ever been "just a stupid integer". Even going back to C, an enum was something more specific than the two things it replaced, a stupid integer constant and a macro-defined value. An enum is more than either of those things.  By calling it an enum you are assigning it specific meaning which the compiler can use.  You are -- as a convenience -- treating it as an integer.  

You are also treating it as a plain int, which, by the way, is also mostly going away.  If you have a plain old int in modern code then you're doing something wrong.  Specify the width and signed-ness.

10 minutes ago, Finalspace said:

I have no idea why it wont compile, i made a branch and checked in the enum operators - last try before i give up:

You have been told why, and provided with THREE alternate solutions to do what you are trying to do.

The language is trying to protect you.  If you are dead-set on removing those protections you are free to do so, but it is not a wise decision.

Telling the compiler you have one intention, then moving on with a different set of uses that violate those intentions, that is a sure-fire way to introduce bugs in your program.

56 minutes ago, Finalspace said:

finalstate *sigh*

A single enum will compile, but a second one will break it!

I tested it on vs 2015 and 2017:

Adding a second enum compiles... will test it out in my library...

I don't understand by what you mean 'a single enum will compile, but a second one will break it!'.  I don't have time to download and debug your code you for (no offense, just that is a lot to ask).  We've all been there, best thing is to take a break, grab a beer, take a walk, it'll clear your head.  Then try picking it apart piece by piece.  My gut feeling (after just taking a quick look) is that you may have a preprocessor definition going awry.  But again, cursory glance.  I threw together this:


#define CPP_ENUM_OPERATORS(T)																										\
	inline T operator |(T a, T b) { return static_cast<T>(static_cast<int>(a) | static_cast<int>(b)); }		\
	inline T operator &(T a, T b) { return static_cast<T>(static_cast<int>(a) & static_cast<int>(b)); }		\
	inline T& operator |=(T& a, T b) { return a = a | b; }


typedef enum fpl_KeyboardModifierType {
	fpl_KeyboardModifierType_None = 0,
	fpl_KeyboardModifierType_Alt = 1 << 0,
	fpl_KeyboardModifierType_Ctrl = 1 << 1,
	fpl_KeyboardModifierType_Shift = 1 << 2,
	fpl_KeyboardModifierType_Super = 1 << 3,
} fpl_KeyboardModifierType;
CPP_ENUM_OPERATORS(fpl_KeyboardModifierType)

typedef enum fpl_InitFlag {
		fpl_InitFlag_None = 0,
		fpl_InitFlag_Window = 1 << 0,
		fpl_InitFlag_VideoOpenGL = 1 << 1,
	} fpl_InitFlag;
CPP_ENUM_OPERATORS(fpl_InitFlag)

enum class SomeMoreFlags {
	flag_0,
	flag_1,
	flag_2,
	};
CPP_ENUM_OPERATORS(SomeMoreFlags)

And it compiles and works fine as far as I can tell (VS 2017).

 

14 minutes ago, frob said:

Nope.  It is implementation dependent size if none is specified, and on most major compilers defaults to a 32 bit signed integer usually, but sometimes defaults to a 32 bit unsigned value or a 64 bit signed or unsigned value, and may potentially be something else entirely.

No. An enum is not "just a stupid integer".  At no point in the language history has it ever been "just a stupid integer". Even going back to C, an enum was something more specific than the two things it replaced, a stupid integer constant and a macro-defined value. An enum is more than either of those things.  By calling it an enum you are assigning it specific meaning which the compiler can use.  You are -- as a convenience -- treating it as an integer.  

You are also treating it as a plain int, which, by the way, is also mostly going away.  If you have a plain old int in modern code then you're doing something wrong.  Specify the width and signed-ness.

You have been told why, and provided with THREE alternate solutions to do what you are trying to do.

The language is trying to protect you.  If you are dead-set on removing those protections you are free to do so, but it is not a wise decision.

Telling the compiler you have one intention, then moving on with a different set of uses that violate those intentions, that is a sure-fire way to introduce bugs in your program.

With all due respect, what he is attempting to do is valid and well defined according to the latest spec n4659.  I explained in detail in an earlier post.  And while I do agree that in32_t is by far superior 99% of the time, in this particular circumstance int is perfectly fine.

On 14.7.2017 at 4:03 PM, Mussi said:

All variables are just bits in memory, so using it as whatever is totally valid, because they're just stupid bits. That's a weak argument. An enumeration has by definition a restricted range of values, the compiler checks for this and makes sure you don't write buggy code by going outside of this range, which is what you were trying to do.

Since you are working with flags, it makes no sense to use an enumeration type. What you want is an unsigned type and some constants. I'd simply do the following:



enum {
	fpl_InitFlag_None = 0,
	fpl_InitFlag_Window = 1 << 0,
	fpl_InitFlag_VideoOpenGL = 1 << 1,
};

typedef unsigned int fpl_InitFlag;

It's clear from the naming that these are flags and there's also a hint to the user that he should use the constants starting with fpl_InitFlag_.

Thats a good solultion, seems i have missed your post there... Problem solved.

If you want to use enumeration values to refer to bits, is there any particular reason you aren't using std::bitset, which is specifically designed for this sort of thing?

Also, it is only "scoped" enums (enum classes, in other words) which have an underlying type of int unless otherwise specified. The unnamed, unscoped enums in this thread are not scoped, therefore this does not apply to them. Instead, section 10.2.5 specifies that the underlying type is the type of the first assigned expression to the enum, or an unspecified integral type wide enough to contain the largest value (if such a type exists, otherwise the program is malformed).

Also, that "if the underlying type is fixed" part is important. The underlying type is only fixed if a) the underlying type is explicitly specified or b) the enum is scoped (in which case it defaults to int).

Paragraph 10.2.7 says: "For an enumeration whose underlying type is not fixed, the underlying type is an integral type that can represent all the enumerator values defined in the enumeration. If no integral type can represent all the enumerator values, the enumeration is ill-formed. It is implementation-defined which integral type is used as the underlying type except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int. If the enumerator-list is empty, the underlying type is as if the enumeration had a single enumerator with value 0."

I'll grant you that 10.2.8 does say that unfixed type enumerations can hold values that are not defined by any of the enumerators, but... std::bitset, anyone?

One thing to consider, the choice of type for unfixed enums is restricted to integral types.  Integral types are defined in 6.9.1 (7).  While it may be possible to define an integral type (on some bizzare-o world system) that isn't a multiple of 8 bits, the standard does require it to be binary: "The representations of integral types shall define values by use of a pure binary numeration system", which means if you define a flag, then that flag 'or'd' with all the other flags will yield a defined value.  In a 'pure binary numeration system' all bit operations (and, or, not, xor, etc...) are all valid, well defined, and yield predictable results (no possibility for overflows or other undefined behavior).  So even in an unfixed enumeration, you can still use flags without worry.  You just have no knowledge of the underlying type.  But you know it will be an integral type large enough for use (and if one doesn't exist on the system you get a compilation error, no undefined behavior).  And if you need to know the size, that's why you can specify them.

10 hours ago, Finalspace said:

For me enums are just a group of named integers, so i can access it in C++ by Enum::A and in C Enum_A.

That's not what an enum is. if you just want a collection of named integer constants, then do that.


namespace InitFlags
{
	const int None = 0;
	const int Window = 1 << 0;
	const int VideoOpenGL = 1 << 1;
}

There, done. 

Even if the language allows you to treat enums as "a group of named integers", that's not what they're for. If you treat everything that C++ allows you to do as good practice, you're in for a world of pain. 

 

if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

This topic is closed to new replies.

Advertisement