-
Notifications
You must be signed in to change notification settings - Fork 3k
Add enum class flag definition to platform #12772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Some questions I had while writing this PR out:
|
@pea-pod, thank you for your changes. |
Thanks for tackling this. If we're going to start having something this, I'd like it to be as good as possible, and if there is such a solution already existing, we may as well pick it up, license permitting. From the other PR, I found this implementation, which has a number of extra benefits. Given
The patterns used there also mirror what the most recent updates to the standard C++ library do, in particular the "opt-in" mechanism for user types via the template specialization of Now, that code is BSD licensed, so any objection to just adopting it? @bulislaw, @0xc0170? The only thing I'd think I'd do to it is name/namespace adjustments. Something like:
The usage model for that would be:
Main annoyance of that enable form there is that the "enable" has to be placed at top level, not in any namespace your enum is defined in. But if you get that wrong, it is a compile error, not silent. On the other hand, separate enabling gives you the ability to specify the underlying type manually with standard syntax ( Some general answers to a subset of your questions: \1. For C++ stuff, if's CamelCase. Could be any of |
This is still quite annoying. C++20 will eventually come to the rescue with:
|
Oops, some corrections to my understanding of that other implementation. Its extra safety relies on everyone being clear on the "enum" versus "bitmask" distinction, so the usage model would actually need
Using just Still, it makes sense to me. It addresses the complaints of anyone who finds it weird to store values that weren't explicitly enumerated in an enum - you're not doing that any more, the raw |
Sorry for the long delay. This is important to me, just not the most important thing right now. In any case, I have a few follow up questions from what you have written, @kjbracey-arm. First, I am no lawyer. Mr. Aubut-Lussier's code is BSD licensed, but his license calls out attribution requirements. If I cut and paste, how would I attribute? Also, if I modify it somewhat (which I believe would be useful to make it prefixed with "MBED_", and possibly a slight change here or there) how would the license at the top of the .h file read? My second question concerns the implementation. In my other code, the enum is stored in at least a 16-bit backing field (and I believe would be turned into a 32-bit word). The LSB is the read/write permissions, and the next byte is the opening method. The bits are orthogonal to each other. So were I to bitwise-or the Do we really want to restrict the comparison of masks and enums with the equality operator? Is this even that meaningful? In other words, I have not seen this sort of restriction in the other languages which include an enum of some sort (not considering C for this). Is this restriction overly strong?
This is not a "deal breaker", as the kids say, but it seems the strongest argument against it. Are you saying that the enable macro would always have to be called outside of a namespace or is that just to wrap an extant enum class? This seems a tad bid cumbersome: namespace Gamgee {
enum class PoTayToes {
BoilEm = (1 << 0),
MashEm = (1 << 1),
StickEmInAStew = (1 << 2)
};
}
MBED_ENABLE_ENUM_CLASS_BITMASK(PoTayToes); but maybe I'm being too picky. Finally, he has several structs defined in his code. I am a little concerned about adding more structs because C++ structs are classes and classes can grow. Also, would not the 4 different structs grow the required ROM or RAM in some respect? I suppose it could all be optimized to generate the same amount of bytecode as if it were the simple overrides I have MACRO'd up, but it is not clear to me that it would. That would be one advantage of my method: it would be a 4 byte chunk of data (although presumably it could possibly be an 8 byte in the worst case for So those are my questions. I am happy to defer, although I would like to a. get something like this included for my own usage and have something consistent across the platform. That was my main purpose for attempting this in the first place in that I knew this could see use more in the whole mbed suite. I accidentally edited this, rather than replying to it. Think I've put it back. Doesn't seem to be an easy "undo" in GitHub. --kjbracey-arm |
Maybe @0xc0170 comment on the licensing?
For both types of
I agree with the author that But on the other hand, it means we're producing something stricter than the C++ BitmaskType concept. I'm not 100% sold on it.
namespace Gamgee {
enum class PoTayToes {
BoilEm = (1 << 0),
MashEm = (1 << 1),
StickEmInAStew = (1 << 2)
};
}
MBED_ENABLE_ENUM_CLASS_BITMASK(PoTayToes);
You could maybe have an all-in one-shorthand that does that as well as the separate enable, but the existing shorthand form didn't handle Last time I tackled something like this was Only thing is you couldn't produce that enable with simple macros, due to the namespace. That enable form in global namespace appears to be the one that the C++ committee have settled on for their own extensions, so I'm inclined to use it. There was a suggestion in the comments of the Anthony Williams article that you could also allow enabling via a
It should all be optimised. Each value is only one type at a time, so only occupies one struct space. And all the operations will be inlined except in -O0 builds. There won't be a stack of different function definitions for each type. However there is one issue in the ARM ABI, in that structure return is less efficient than scalar return. Just putting an I noticed this while working on Chrono - returning I think that may be the one final argument that dissuades me from going down that deeper route. But that does rule out the automatic bool conversion too. You can't define Given all that, you're basically back to something like Anthony Williams simpler template party, or your macros, and they're basically equivalent, both being BitmaskTypes. I'm personally inclined to the template form, but it's no big deal, and I bet others would prefer macros. So I think we can proceed with your basic form here, but you will need some variant to handle underlying type specification, and make sure you can enable separately in some way. I'll review in more detail later. |
Regarding the license, the file in question is licensed under "BSD 2-Clause "Simplified" License". just keep the license. You are allowed to make modifications. Once this is ready we can review the license in the file in question in this PR and update it accordingly. Will help with that. |
First, I apologize for the huge delay. My intentions did not fit my actions well enough. I could work out the macros myself, but I have much less experience with the templates. Could you either point to an example or give me one of such things? The only thing I could come up with seemed to end up giving bitflag powers to all enum classes everywhere. Obviously, that's not ideal. Could you possibly provide me with some breadcrumbs toward how a template implementation might work? |
In case my last comment wasn't clear, I'm fine with a macro implementation:
At the minute your macros don't let you choose the underlying type when defining the enum, and don't let you add the methods for an already-defined enum. |
Ok. No problem. I have an easier time understanding macros themselves, but I know that the compiler is supposed to give you better error messages when a template barfs. I will noodle it only a little more and then push an update shortly. Thanks again for your help. |
0eb26a6
to
2010349
Compare
Well, alright @kjbracey-arm, I believe I have satisfied your requirements. It did turn into a macro a-splosion a little bit there, but I do not believe I repeated myself once. I wish I had remembered to astyle it first, but oh well. Let me know if you have any questions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I quite like this. Just needs a few tweaks. There are fair few style issues - satisfy astyle first, then I'll see if there's anything left.
So, @kjbracey-arm, is there anything else remaining before acceptance? I believe I have made all of your suggested changes. However, if I have missed something, please let me know. |
platform/mbed_enum_flags.h
Outdated
#include <type_traits> | ||
|
||
#define ENUM_FLAG_OPERATOR(T, OP) \ | ||
inline constexpr T operator OP( T lhs, T rhs) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems astyle is being lenient with you, presumably because of the macros. Quite a few excess spaces inside parentheses and <>
. Here and in some other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just making sure the macro expansion actually expands macros correctly. I will remove every space to make it virtually the same as a normal function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't think of any way spaces and macros interact. Tokenisation happens before macro substitution of tokens. There's no way tokens can get "accidentally" pasted together.
platform/mbed_enum_flags.h
Outdated
#define MBED_ENUM_FLAG_APPLY_OPERATORS(T) \ | ||
inline constexpr T operator ~( T lhs) \ | ||
{ \ | ||
return ( T ) (~static_cast<std::underlying_type_t< T > >(lhs)) ; \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't match the constructor form of the binary op now. More excess spaces. (Don't need spaces to separate >>
any more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't match the constructor form of the binary op now.
Oops!
More excess spaces. (Don't need spaces to separate
>>
any more.
I was being paranoid. I figured working was better than pretty. I was not sure I would not cause it to implode if I removed spaces. I was sure if I did have spaces and it worked with gcc, it should work for each supported compiler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
>>
is special - there needs to be a magic "tokensiation" rule that gets it treated as > >
in templates. That was added as a bit of a hack in C++11, but it's standard.
platform/mbed_enum_flags.h
Outdated
* #include "mbed_enum_flags.h" | ||
* #include "external_enum.h" | ||
* | ||
* MBED_ENUM_FLAG_APPLY_OPERATORS(SpokenLanguages) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs an extra ;
now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
platform/mbed_enum_flags.h
Outdated
ENUM_FLAG_OPERATOR(T, |) \ | ||
ENUM_FLAG_OPERATOR(T, ^) \ | ||
ENUM_FLAG_OPERATOR(T, &) \ | ||
ENUM_FLAG_OPERATOR_LHS_REFERNCE(T, |=) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REFERENCE
. Slightly indirect name though? The reference isn't the point. From standard terminology they could bee"binary operator" and "compound assignment operator".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, with this one, I was thinking of it not that in such a way as it only applied to enum classes that we wanted to make into bitflags. If I wanted to define the addition operator or the comma operator, I could do that with this same macro, as the signatures (with the exception of the exact symbol) are identical, and the name would still fit.
So in this case, I had ENUM_FLAG
as a prefix, and OPERATOR
and OPERATOR_LHS_REFERENCE
as suffixes. But I do not mind narrowing down their usage, as this really is an internal macro anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I see how you were thinking. Not that bothered :)
platform/mbed_enum_flags.h
Outdated
|
||
|
||
#define ENUM_FLAG_OPERATOR_LHS_REFERNCE(T, OP) \ | ||
inline constexpr T & operator OP (T & a, T b) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No space between &
and variable name. Can probably have one before operator
if you like.
I looked at the build log and it showed a failure for Digging through the error log shows this as I have a Nucleo-F767ZI and a Nucleo-F429ZI. If this is a true issue (and not with the CI environment) is there a way to short-circuit the mass of tests and just do the specific DNS timeout one before moving to doing all tests on my own computers? |
@0xc0170, could you let me know if my code is truly in error or of so, how to execute this one test (or if not possible to narrow it down to the Also, for everyone, regarding my earlier comment about |
Failure can't be related to this PR - I'm guessing it's some sort of race condition in socket closure on Will retrigger the tests. |
Test run: SUCCESSSummary: 6 of 6 test jobs passed |
Re-running CI as this passed 7 days ago |
Test run: FAILEDSummary: 1 of 6 test jobs failed Failed test jobs:
|
CI restarted |
Test run: SUCCESSSummary: 6 of 6 test jobs passed |
Summary of changes
Add a macro for adding bitwise operators to
enum class
types.This addition is necessary to use bitwise operators on
enum class
(scoped enums) introduced in C++11.Without this macro, either the whole boilerplate would have to be written to take advantage of C++ namespaces and scope, or a C style, visible-to-the-world enum would need to be used.
This:
becomes
Now, using the
enum class
allows for scope and bitwise operation:Impact of changes
The only impact is if the macro is used. If the macro is unused, standard
enum class
definitions are unaffected by the bitwise operators. If the macro is used, the implementing code becomes more concise (and hopefully readable), and the DRY principle is realized.Regarding code size, the operations should be no different than if written out with
static_cast
calls.In the future, standard C
enum
bit flags could be converted over toenum class
for better scoping and collision avoidance.Migration actions required
If a scoped enum bit flag is desired, include the header and use. Otherwise, none.
Documentation
Documentation including examples are in the file itself.
Pull request type
Test results
Reviewers
Per @0xc0170's request in #12759, this was created as a seperate PR. Also @kjbracey-arm, @VeijoPesonen, and @SeppoTakalo commented, and may be interested as well.