-
Notifications
You must be signed in to change notification settings - Fork 39
Flags, bitwise operators and conversion to flag groups #223
Comments
.oO( Le sigh, C++ is such a terrible language. ) I remember adding FlagsOperators rather hastily, so it's very likely I missed this. |
I have no idea how to write a reasonable unittest for this feature. This is a language issue during compilation. Before the fix the code would simply refuse to compile instead of failing at runtime. |
Well, do you have an example of code that compiles with this change, but doesn't without it? That's your unittest. |
I'll add the test soon.
It will get even better... I encountered another issue after adding the suggested conversion operators. In some driver code I defined two function overloads of which one takes a FlagsGroup and the other one takes a uint8_t. Everything works as intended unless you dare to pass bitwise or-ed flags to it. The compiler complains about the overload being ambiguous although it is clear the flags don't implicitly convert to uint8_t. Here is where the fun part starts. All Flag* classes inherit from Register which defines explicitly deleted int conversion operators to prevent unintentional implicit casts to int types through the bool conversion operator. The overload resolution fails because the compiler sees the deleted uint8_t conversion operator. How can that be? We explicitly told it: Do not convert to uint8_t. C++ wouldn't be C++ if it weren't much more complicated ;-). When you write " = delete" you delete the definition of the operator but not the symbol (see this for more details). During overload resolution the delete isn't even noticed. By prohibiting conversion to a specific type you urge the compiler to try it. Thank you C++. This is kind of a corner case and could be resolved by adding a cast in my code, but in my opinion library code should not behave unexpectedly when it's avoidable. The classic C++03 solution is the rather clumsy safe bool idiom. In C++11 a concept called "contextual conversion" was introduced for types which explicitly convert to bool. It will make an explicit bool conversion operator behave as if it was implemented using the safe bool idiom: It will never convert to int implicitly but will trigger an "implicit explicit cast" when used with if, for, logical operators and so on (See this for a more complete list). Thus it would be possible to make the bool operator explicit and entirely remove the deleted conversion operators. Unfortunately there is one drawback. In return statements of functions returning bool no contextual conversion takes place and an explicit cast is needed. So employing an explicit bool operator would change the external interface of the flags classes. This is not an option since user code may rely on that. Finally I suggest either to use the safe bool idiom in the Register class or to do nothing at all. What do you think? |
This is pretty amazing research, thanks for taking the time! This is a bit shocking to me.
I literally molled (moaned out loud) at this.
I'd be in favour of removing both We used to delete all the casting operators with a template, but it didn't compile on hosted gcc so we stopped. Not sure if that would be helpful here. |
I don't think deleted casting operators are helping here at all. They are used here to heal the insane default behaviour of the non-explicit version of
Please don't do that. If you wanted to check whether a flag is set you would have to write something like It's true bool conversion operators can be harmful when they allow undesired conversions or have confusing semantics. For instance in the C++ standard library input streams have bool operators. A true value means that no error occurred but not that data can be read. It will return true even if the end of a file has been reached. Here both isn't the case. In my opinion it is a key advantage of this flags implementation that it is both type safe and retains the usual semantics of bit operations. If it's acceptable to subtly change the interface of the flags classes I would recommend using the C++11 explicit bool operator. You can completely remove all deleted cast operators and even don't need to define
I'm not aware of any feature that would help here. |
Yeah, good point, that's disgusting.
Yes, let's go for that! |
I added a simple test that fails to compile without the fix. While I was writing the test I found out that the conversion problem is more subtle than I initially expected. At first I combined 2 enum constants directly and the code surprisingly compiled. It compiled because the resulting type is
The change in register.hpp is really trivial but to definitively catch every other line that has to be changed I have to go through all code that uses flags and is not compiled for unit tests or the examples. When I'm ready I'll create a pull request. |
Fixed in #230. |
Instances of FlagsOperators, which are created when Flags are combined using bitwise operators, do not implicitly convert to a FlagsGroup.
However, FlagsOperators convert to Flags which themselves implicitly convert to FlagsGroup. Since the compiler will never apply more than one user-defined conversion automatically, you need an explicit cast.
Adding a FlagsOperators conversion constructor to the FlagsGroup class template should eliminate unnecessary casts without allowing additional undesired conversions.
Here you can find an implementation.
Did I miss some unintended side effects?
The text was updated successfully, but these errors were encountered: