-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
ES.79 is a terrible advice and should be dropped #2163
Comments
Why? The enforcement section says (emphasis mine): Flag So if your local coding standards say that all switches must cover all enumerators, and your code already does that, then there's no need to add a
lol, good one. Do you have any proper rationale for breaking millions of lines of working code? Rationale for not doing that would be that it makes it impossible to use a switch(b) {
case 0xfe:
case 0xff:
return std::make_error_code(illegal_byte_sequence);
case 0x0:
return finish();
default:
process(b);
} Without |
Your example actually demonstrates perfectly why |
Imagine there's another non-default case that doesn't return. In any case, Regarding the guidelines and actual constructive feedback, what part of ES.79 says you have to use |
lol... ensuring all your cases are properly handled is actually good coding practices.
This is rather absurd. using a I'll re-iterate @jwakely and quote from ES.79 itself:
Emphasis mine. |
@BenjamenMeyer Nobody argues against handling errors. My problem really is that What really confuses me is your talk about performance. First you introduce the So what was the purpose of your comment, other than insulting me? |
@jwakely I'd call such inconsistency pretty terrible code. It wouldn't pass my code review if I see it. I'd only accept such if there are unexpected, invalid states if it makes sense to handle them consistently to out of domain values. Tools can catch such bad style.
Is "this is silly" a valid and rational argument these days?
You start with this: enum class Result { Success, FileError, FormatError };
std::tuple<Result, Model> processData();
void something()
{
auto [state, model] = processData();
switch (state) {
case Result::Success:
useModel(std::move(model));
break;
default:
showError();
break;
}
} Month, years after writing the code above someone introduces the new state Other example: // Identifiers of some automotive hardware's fictional UDS/TP interface
enum class DataIdentifier {
Gear,
Speed,
Handbrake,
Brake,
// ...
ContainerHookLocked,
UnderRunProtectionInstalled,
UnderRunProtectionActive,
UnderRunProtectionPosition,
UnderRunProtectionFoldLimit,
UnderRunProtectionUnfoldLimit,
// ... some 100 other parameters
};
// Terrible, fictional example.
// Fictional story: Updating data identifiers is expensive for automotive reasons.
// Therefore we only refetch these identifiers that are relevant for the next operation.
constexpr bool affectsMoveContainerArm(DataIdentifier id)
{
switch (id) {
case DataIdentifier::Speed:
case DataIdentifier::Gear:
case DataIdentifier::Handbrake:
case DataIdentifier::ContainerHookLocked:
case DataIdentifier::UnderRunProtectionActive:
return true;
default: // I've delete the full list: was too much work to add new identifiers and ES.79 encourages this
return false;
} Now after years successful of operation product management introduces a new truck body that replaces the single container hook by two hooks for... "reasons". They report via the newly introduced identifiers UDS Right now we have banned |
You've shown why you don't want to use I'm still not sure if you actually understand the guideline or you're just angry with |
In the Enforcement section: IMO, it is better to avoid enum E { a, b, c, d };
void f1(E x)
{
switch (x) {
case a:
do_something();
break;
case b:
do_something_else();
break;
default:
take_the_default_action();
break;
}
} "Here it is clear that there is a default action and that cases a and b are special." But it isn't clear that It would be better instead to suggest adding the missing cases explicitly: switch (x) {
case a:
do_something();
break;
case b:
do_something_else();
break;
case c:
case d:
take_the_default_action();
break;
} This makes it clear that Ideally we'd have strict enums, that is enums that cannot hold a value other than its specified enumerators without invoking UB, and maybe even case ranges (i.e. GCC's But short of that, non-swallowing |
No, they're not saying that. You can add cases for the missing enumerators instead. I don't know why you think it's saying otherwise. It says to add an empty
So add the missing cases for the other enumerators then.
So don't add a default case, and when you add new enumerators, add cases for those.
So do that then. It will avoid the condition that the enforcement talks about, so would be following the guideline. I don't understand why people keep interpreting this guideline to mean something it doesn't say. |
N.B. if you have 30 enumerators and you only want to handle 5 of them in the switch, you're saying it would be better to do: switch (e) {
case e1:
blah1();
break;
case e2:
blah2();
break;
case e3:
blah3();
break;
case e4:
blah4();
break;
case e5:
blah5();
break;
case e6:
case e7:
case e8:
case e9:
case e10:
case e11:
case e12:
case e13:
case e14:
case e15:
case e16:
case e17:
case e18:
case e19:
case e20:
etc etc
default:
} Rather than just: switch (e) {
case e1:
blah1();
break;
case e2:
blah2();
break;
case e3:
blah3();
break;
case e4:
blah4();
break;
case e5:
blah5();
break;
default:
// nothing to do for all other values.
} That seems pretty dumb to me. In a case like this, adding the default case makes a lot of sense. If you don't have so many enumerators, or you like the first example above, feel free to add them explicitly. The guidelines allow either form, so you can choose. |
The title of the guideline even reinforces it: "Use And if you want to be explicit about "all of these should be treated as the everything else case" then you can do that. |
Because that's what it's saying. In the section titled, "Use default to handle common cases (only)", enforcement is described as "Flag switch-statements over an enumeration that don’t handle all enumerators and do not have a default". I don't know how else to read this other than "if you don't specify all enumerators and don't have a default case, add or suggest to add a default case." I'm apparently not the only one that interprets it this way. If the purpose of the "(only)" is to basically say "(but not always)" and warn people away from doing it, after realizing it wasn't so good of an idea to do by default, that further questions its inclusion as a core guideline.
But a Even if it is for a common case, seeing a
Which should be the suggested rule/guideline IMO, contrary to ES.79. To prefer specifying missing enumerators instead of using
That's where case ranges and enumerator markers would be helpful. enum Enum { e0, e1, BeginGroupFoo, e2=BeginGroupFoo, e3, /*...*/, e20, EndGroupFoo=e20 };
...
switch(e)
{
case e0:
blah0();
break;
case e1:
blah1();
break;
case BeginGroupFoo ... EndGroupFoo:
etc etc
break;
} This more clearly indicates e0 and e1 are handled special, and everything in "GroupFoo" is handled with the same path. If another enumerator is added inside of GroupFoo, it's handled the same as all the others in the group as one would expect, whereas if another enumerator is added outside of it, you get a warning and avoid a bug early. In contrast, a |
That does not say "add a default". It says when static analysis tools should complain. If you add cases to handle all enumerators, the tools should not complain. So adding cases to handle all enumerators is a valid way to follow this guideline. The rest of your answer is just repeating "default bad default bad" again. I get it, you've made your point. I don't agree default cases are always bad, but I get it. It's not hard to understand your position, you don't need to keep repeating it. But the guideline does not say "always have a default case". It says "do not have unhandled cases". It's really that simple. If you don't want to use Maybe the title or summary of ES.79 needs to be clarified for people misreading it but the issue says it's "terrible advice and should be dropped", based on ... something it doesn't actually say. "Do not have unhandled cases in a switch" is not terrible advice, and should not be dropped. |
At least in my case, it's both the title and provided examples, along with reasoning. The title sets the context, so the section is interpreted in that context. Here, the title is "Use default to handle common cases (only)", so the explanation of missing cases, the reasoning of code clarity and safety, and what to do is going to be read in the light of "Use default to handle common cases" (and the added "(only)" at the end is confusing, like it's an incomplete aside statement; "only ... what?"). It harps on using default to prevent problems, how the first example is "clear that there is a default action and that cases The section just reads as "use |
ES.79 is a terrible advice that will cause unreliably code and serious bugs once project evolve and new enum values get added. Adding a default case to switch statements blinds compilers regarding new enum values (unless the proper compiler with the proper magic switches is used). By adding a default case to a switch statement one gives up the compiler's ability to support us to write correct code without good reason. This rule should be dropped, or even reverted into "do not ever use default: and lobby the ISO consortium to remove it form the language".
The text was updated successfully, but these errors were encountered: