Problem with enum and binary or operator

Started by
51 comments, last by ChaosEngine 6 years, 9 months ago
1 hour ago, Kylotan said:

1. That loses most of the type safety, no? What stops me passing in the wrong bitset? Or passing in a wrong enum when setting the flags?

2. The OP seems to want to support both C and C++ with one codebase.

1. std::bitset is specialized on the number of bits it can support. At the very least, the type system will prevent you from assigning incompatible bitsets. If you wanted more type safety on top of that, you could have a wrapper structure (or typedef) that takes both the bitflag enum type and the number of bits as template arguments, making the enum part of the type, but getting the convenient interface of bitset.

2. That seems suboptimal and I would be curious as to the reasoning.

1 hour ago, Ryan_001 said:

Its not type safe?  You can't use combinations (where a single enumeration maps to multiple flags)?  I don't have a problem with people using other methods, I just don't like it how many have claimed things about enum that are not true (according to the standard).

bitset is an interesting solution, how would you use it in a normal function?  Almost every usage of flags looks like:

Func(SomeEnum::flag_1 | SomeEnum::flag_2 | SomeEnum::flag_3);

but with bitset it would seem tedious to me to have to write:

SomeBitSetTypedef bs;

bs.add(SomeEnum::flag_1);

bs.add(SomeEnum::flag_2);

bs.add(SomeEnum::flag_3);

Func(bs);

Or is there a clever way around this?  TBH I've never used bitset...

You can construct the bitset with an initial value, either by passing the initial value as an integer type or as a string (which it will parse). Surprisingly and somewhat annoyingly, it doesn't appear there's a way to pass in an initializer list of the flags, but you could easily write a function that builds a raw bitset from a list of flags.

See the cppreference link I posted above for std::bitset for more info.

 

Advertisement
2 hours ago, Ryan_001 said:

Or is there a clever way around this?

bit fields in a struct would work, also giving you multi-bit values if you need more than 2 values in a variable.

1 hour ago, frob said:

Both toward the original topic regarding the bitwise or operations no longer working for enum types, and toward your concerns, it seems a history lesson is in order.

Back in the 1980s when C++ was created, people already knew this was an issue with enum. That is why every language that branched out from the proto-C of the 1970s has implemented different forms of enum.  

Back in 1994, Stroustrup wrote in his famous book on the history of the language, that "C enumerations constitute a curiously half-baked concept", then described various ways that they were broken in regards to C++. The use of them as a container for flags was one of those flaws described 23 years ago.

The use of enums to wrap flags as a type has been considered a defect in the language since the language came out of Bell Labs, and was considered by a few people to be a defect in the C language before that. 

The C++ standards committee debated and discussed how to fix what they considered flaws, and to do it in a way that did not break legacy code. Some of the proposal attempts are documented on the committee's official site: N1513 N1579 N1719 N2213 Note how in several of the documents it is described as the first and biggest flaw, the compelling reason to get rid of it.  

Note the names on those proposals: David Miller, GCC steering committee founder.  Bjarne Stroustrup, creator of C++.  Herb Sutter, C++ language committee convener. There are other high-profile names in the discussion notes, but those three as the change's sponsors are critical. The most influential people on the language and its future features all consider that patterns as a defect.

Yes, many code bases rely on the behavior. That is why the many people and organizations that direct the language's growth over time cannot get rid of the behavior.  But the compiler creators, the language standard committee, the compiler steering committees, various compiler vendors, and many professionals all feel this is an abuse, misuse, or otherwise a flaw in the language that should be avoided and eventually removed.

 

So attempting to keep it back on topic, the operations are no longer defined. They will not be returning.  You can write code to coerce the operations to exist using code as others have described, but the language has been slowly moving away from that construct over the past 20 years, and it will become less and less supported over time.

Your wording seems intentionally misleading...  Not a single claim you make is supported in those documents.  All 4 of those proposals state essentially the same problems: implicit conversion to an integer, inability and/or unpredicatble underlying type, scope issues, and (which also leads us to present day enum proposals) reflection.  Not one of them even mentions or suggests removing flags or other type of capabilities from enums.

 "C enumerations constitute a curiously half-baked concept" - this does not refer to flags in enums.  I have sitting in front of me here, right from my bookshelf, "The C++ Programming Langauge" by Bjarne Stoustrup, and on page 77, is enums.  Not only does he show an example "flag f3 = flag(z | e) // ok: flag(12) is of type flag and within the range of the flag" which uses bit operators on unscoped enumerations (at the time of this book there was no such thing as scoped enumerations); he also even states: "bit-manipulation examples that require values outside the set of enumerations to be well-defined have a long history in C and C++.".  That does not sound at all like he feels that flags are bad, and certainly not UB.  There's not a single mention to avoid flags, talks of work arounds, or anything that you are claiming.  Rather he is showing clear examples of it being used, showing how to use it properly, and claiming the uses are well defined and valid within a C++ program.

I also can't find a single mention of that quote "C enumerations constitute a curiously half-baked concept" in reference to flags online, every single one I find is referring to implicit integer conversions and unscoped enums (which was known to be a problem, which was why they added scoped ones).  If you want to post an actual link to where Bjarne (or any of the name's you've dropped) criticizing the use of flags in scoped enums in C++ I would love to read it (and that's not sarcasm, I love to read their thoughts on language development).

Scoped enum's were never designed to provide backwards compatibility, unscoped enums were.  Scoped enum's were a completely new construct, with new syntax, that was designed to move enum's closer to what they felt they should be, and farther away from C.  In fact the proposals you posted as proof directly disprove your assertion.

Quote

This proposal is in two parts, following the EWG direction to date:

• provide a distinct new enum type having all the features that are considered desirable; and

• provide pure backward-compatible extensions for existing enums with a subset of those features (e.g., the ability to specify the underlying type).

Emphasis is mine.  Scopped enum's were brand new, and they included static cast, known/fixed sizes, by design.  None of that was accidental, unintentional, or to satisfy backwards compatibility issues/existing codebases.  The language has never 'been moving away from 'flags', as you claim.

Enums didn't even exist in the original C, they weren't added until ANSI ratified it in 1989 (hence why its called c89), so there's no way people could consider enum flags to be a problem back in the early 80's or even the 70's, since prior to then it didn't exist in the language and was just a bunch of #define's.  They knew that using #define or just integer constants was problematic, and that unscoped enums were problematic (for the reasons listed above in the 4 proposals, but does not include in those reasons flags).

Your claims that Strustroup, Miller, or Sutter "all consider that patterns as a defect" is completely unsubstantiated by the documents you have linked and the claims you have made.  But perhaps you have more knowledge on this than I do: post some links that actually back up your claims, where any of the 3 (or Stepanov, or Alexandrescu, I'd love to know their thoughts as well) state that scopped enumerations are not designed to be used in that manner.

This would be so nice, but to simple for mankind:


enum set MyFlags : uint32_t {
	A, // 1
	B  // 2
	C  // 4
};

static void DoSomethingWithFlags(const MyFlags flags) {
	if (flags & MyFlags::A) {
	}
    // Or using the "in" operator
	if (MyFlags::A in flags) {
	}
}

 

10 hours ago, Oberon_Command said:

std::bitset is specialized on the number of bits it can support. At the very least, the type system will prevent you from assigning incompatible bitsets.

3

My kingdom for an opaque typedef :)

 

12 hours ago, Ryan_001 said:

Func(SomeEnum::flag_1 | SomeEnum::flag_2 | SomeEnum::flag_3);

but with bitset it would seem tedious to me to have to write:

SomeBitSetTypedef bs;

bs.add(SomeEnum::flag_1);

bs.add(SomeEnum::flag_2);

bs.add(SomeEnum::flag_3);

Func(bs);

 

Well, if you didn't use an enum for your flag collection and instead used a namespace as I described, you could do this


template<int N>
bool Check(const std::bitset<N>& b, int flag)
{
	return (b & std::bitset<N>(flag)).any();
}
  
typedef std::bitset<8> FlagType;

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

void Func(const FlagType& flags)
{
	if (Check(flags, InitFlags::Window))
	{
		// do window stuff
	}
}
  

 

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

Doesn't really matter.  As I wrote above, it is going away, the language committee is dropping the old form and moving to something different that satisfies those requirements.

The current feature work on metaclasses includes a new metaclass called "flag_enum", which should satisfy what you're looking for.  It is planned to be inside the std:: namesapce. It does several things outside what enums do, and does the things people want with the flags mentioned above.  The generated values are constexpr. It respects type safety, prevents mixing with other types including integers, and all the rest.

It doesn't help today, but it is the direction the language is moving.

That's a cool proposal, I knew they were talking about that sort of thing, but the implementation is certainly non-trivial.  I'll be excited to see it added.  That said, it still does not provide proof of your assertions.  In fact quite the opposite:

Quote

There are two common uses of enumerations. First, enum expresses an enumeration that stores exactly one of the enumerators.

...

Second, flag_enum expresses an enumeration that stores values corresponding to bitwise-or’d enumerators.

Even he acknowledges that there is more than one use for an enum.  He is showing examples of how meta classes can provide specialized versions of generalized constructs that already exist in C++.  But read even further and he links to page 22 on https://accu.org/var/uploads/journals/Overload132.pdf which, as luck would have it, has an entire article called "Using enum classes as bitfields".

Now I would normally leave this here, assume you and the others will read the note and the article without bias.  But since you've linked yet another article that does not actually prove what you claim it does (ie. it feels like you've been negligent if not intentionally misleading), I feel I must explain what the 'note' and the SFINAE bit in the paper you linked, and the Overload article are referring to.  Neither the note, nor the article, raise any issue as far as safety, usability, or performance of using flags as enums; rather both the proposal and the article admit that writing the appropriate overload operators is easy, but that it does amount to some boilerplate code repetition:

Quote

The implementation of these operators is trivial, so it is easy to create your own bitmask types, but having to actually define the operators for each bitmask type is undesirable

The proposal suggests that using metaclasses as a solution (which would make sense in a proposal for metaclasses), the Overload article suggests using SFINAE and a policy class, and I suggested a macro.  All 3 solutions work, pick the one you like the best.  It doesn't matter to the user of the enum, only the author.  I personally can't wait for metaclasses and would prefer that method over macros.

Currently enums do all that you ask for already: "The generated values are constexpr. It respects type safety, prevents mixing with other types including integers, and all the rest.".  The new meta classes will be a great addition because it will remove the need for some boilerplate code, but it won't add any features that don't already exist.

I don't understand why there is such a reluctance to at least try this out.  I  have shown repeatedly in the standard where it is safe, and well defined.  I have shown examples, I (and you as well inadvertently) have linked to examples in prominent C++ articles.  It is common, often used, and accepted in practice (you'll find it in many large public code bases including the C++ standard library and boost).  It satisfies ALL the constraints that have been listed here as desirable, safe: easy to use, high performance/low overhead.  Its trivial to implement.

I am flabbergasted by the level of negativity and bullying in this thread.  It seems that every time someone disagree's with the 'clique' irregardless of the accuracy/legitimacy of the post, you guys circle the wagons and try to bully them.  I backed every assertion I made with evidence from the standard, while repeatedly people made assertions that were completely untrue (according to the standard).  If the situation had been reversed I'd have been ridiculed and eventually banned from the forums if I kept pressing the point, for what you guys wrote.  I certainly wouldn't get 'upvoted' for posting things that are factually wrong (now before its said, I don't care about the reputation, but I do care about the attitudes in these forums, or at least I did before this thread).  Rather than have a real and honest discussion as professionals, what we have here is nothing more than high-school level pettiness.  Posting articles that you know are inaccurate in an attempt to 'prove' yourself right?  Quotes taken out of context.  Misleading assertions.  This was not an 'error' or an 'accident', and nothing you wrote was in 'good faith'.  C++ is a massive language, so there's no doubt people will make errors from time to time, heck I even saw SiCrane make an error once (and that guy was a genius when it came to knowing the standard), and I certainly wouldn't hold it against anyone for making an honest error.  But these were not 'honest errors'.  They were not mistakes, they were intention misuses; you knew enough to know it was wrong, and still posted it in an attempt to deceive...

And to what end?  To not say 'hey I never knew that, maybe I'll try that out?'.  Like somehow admitting you don't know every little thing about C++ (hint, neither does Bjarne, and he'll be the first to say so) is bad?  Delete this post, lock it, censor it, delete my account, I don't care anymore.  I will not be a bully.  I will not engage in your silly clique.  I will not stop attempting to learn and better myself, I will not stop questioning or scrutinizing.  You've made it clear time and time again that discussions are not wanted in these forums, that disagree'ing with any of the moderators like Hodgman or Frob is tantamount to blasphemy, and that these forums are nothing more than an industry insider circle-jerk; and I refuse to be part of this toxic environment.

I think the "IT" powers that be failed to establish a standard for online etiquette, resulting in conflict whenever opinions differ in what should be friendly discourse. It's this era's most socially destructive force in my opinion.

C++ invariably seems to draw "expert" opinion even for simple questions; links and references to the standard, quotes from prominent  language experts, clever language magic... 

I think it's been shown that enum bit flags are "normal / conventional".  Surely most of us have seen it? Language debate is interesting but also distracting... also why "C++ is hard."

 

It seems to me that people are talking at cross-purposes here. One camp is insisting that the standard allows for you to use enums for bit flags (and furthermore, that this is conventional) while the other camp is arguing that you shouldn't do that (regardless of whether or not it's conventional) and that the behaviour is only in the standard to support legacy code.

"Can" vs "should."

I don't see anyone denying that enums have been used for bit flags, only that we should use them for bit flags. I certainly don't see any bullying here. Bullying would involve personal attacks, and I haven't seen any so far. Pointing out that someone is doing something practicible, but dangerous or confusing or just non-preferable, is not bullying.

In support of the "you shouldn't do that" camp:

  • Very few compilers adhere precisely to the standard. It's not unheard of for compilers to make non-standard optimizations and subtly different behaviour between compilers is not unheard of even when the compilers aren't optimizing. See Hodgman's examples.
  • Using enumerations for bit flags dilutes their meaning; you're taking something meant for one thing and re-purposing it to do something else. A similar argument has been made against the use of exceptions for control flow (at least in C++).
  • It has been claimed that the use of enums for bit fields is "going out of fashion", meaning later generations of programmers reading your code won't think much of code that does this and may be actively confused by it.
  • In C++, alternatives exist (like std::bitset and constants in a namespace).

It may be true that the standard allows us to use unscoped enums as bit flags, but being allowed to do something isn't an argument for doing it. 

It's not just allowed: It's completely idiomatic. Take for instance std::ios_base::openmode . I just looked at its implementation in gcc 7.1.0 and it's an enum, where a few constants have been defined as powers of 2 and you then use them like this:


  std::ofstream ofs;
  ofs.open ("test.txt", std::ofstream::out | std::ofstream::app);

 

If you are not familiar with this idiom, you just don't know C++.

[EDIT: By the way, in gcc 7.1.0 <bits/ios_base.h> defines the operators &, |, ^, ~, |=, &= and ^= for this type.]

This topic is closed to new replies.

Advertisement