4 ms·
Thank you for your thoughtful response. vector<int> wouldn't work because copy semantics wouldn't apply for a constant type, so mutable would be needed (as you
by compiler-devel 4y ago
Thank you for your thoughtful response. vector<int> wouldn't work because copy semantics wouldn't apply for a constant type, so mutable would be needed (as you rightly pointed out). I'm not sure that vector<int> should work unless the vector container was updated to move its elements by default (another commenter suggested move-by-default rather than copy-by-default as well). I've used RxCpp in the past and know what nightmare awaits should you have to explicitly state lambda captures, yet I've seen too many devs over capture with subtle bugs as a result. Is there a compromise here? I'm not sure that goto is required when one could use do { ... } while(false); with break statements for cases where goto would've been used (not ideal, but again this is an iterative approach). C style casts to void are useful for some memory operations but I'm not sure there's a case where they're required.
If you would, I'd love to hear some of your rules as it's clear you have a lot of C++ experience. Can you send some along? Thanks!
- dataflow 4y ago> I'm not sure that vector<int> should work Well, I think it "should" work in the sense that I shouldn't have to type "vector<mutable int>" just to get a vector of mutable ints. It's just too much typing for zero benefit. How exactly you make that work is a separate question; you can do it at both the the language and library level. A compromise might be to make 'mutable' be a storage class (like 'register', or like how it already is for class members) rather than a type qualifier. Note that even making it a storage class has a downside: 'return v;' will now copy-construct its output instead of moving it. You'd have to mess with the const rules to get around that. It might be possible but I'd need to think through the implications and actually play around with it for a while before I could suggest that it would actually work well. > lambda captures [...] Is there a compromise here? I don't know honestly. One idea could be to see if some dataflow analysis could tell you if the lambda might leak from the scope it's declared in, and you could warn on that. I think there are already tools (like clang-tidy, cppcheck, etc.) that give you warnings of this sort; I'm not sure if they fully handle this case though, you'll have to check and see if those handle the cases you want. It almost certainly won't be something you could whip up in a few hours, in case that's what you were hoping for. > I'm not sure that goto is required when one could use do { ... } while(false); with break statements for cases where goto would've been used (not ideal, but again this is an iterative approach). That's in no way a substitute for a goto. Sometimes you really do need the ability to jump in, not just jump out. Imagine a state machine/coroutine/etc.—it's not impossible to write them without goto, but sometimes you'd have to go through contortions and write unnatural/unmaintainable logic to write them without goto. Yes C++20 has coroutine support now but it's mediocre at best and isn't suitable for every use case. > C style casts to void are useful for some memory operations but I'm not sure there's a case where they're required. Edit: (void) isn't required anywhere I know of, but there are lots of places where it's helpful to have, and completely unhelpful not to have. Here's one: void foo(void *p) { #if NDBUG bar(p); #else (void)p; // suppress "unused parameter" warning #endif } Sure you can do static_cast<void>(p) but that's not buying you anything. It's not the end of the world, but it's just wasting your time and making your code more verbose to read. I don't have a problem with more verbose typing when it actually buys you something, but there are cases where it doesn't, and this is one of them. In fact, a better rule might #5 below. I'm not sure there's a reason to ban the C-style cast entirely; it could be much more useful and safer than it is now. Meta-rule of thumb: you need to make sure you're familiar with the vast array of use cases and scenarios people encounter in real-world C++ before you can come up with rules for other C++ devs to follow. The committee itself has a hard enough time doing this for a good reason—because it's hard! If you are going to propose that some feature is unnecessary, it should be a conclusion you draw after you've already used that feature in its "most useful" context (and found a good alternative)—not before that. Most features have some very compelling use cases, so if you haven't found a compelling use case for a feature ("compelling" assuming you disregard any downsides it might have in other contexts) then there's a good chance you simply haven't come across it yet, rather than it having been unnecessary to begin with. It's usually enlightening (and honestly kind of fun) to try to figure that out before rushing to get rid of it. > I'd love to hear some of your rules Sorry I wrote this comment but forgot to respond to this part. I'd have to sit down and think through a lot of them before I can share them with any confidence honestly. But just going off the top of my head, here might be a few: (1) Conversion operators (like constructors as you mentioned) should probably be explicit by default too (2) Shadowing local variables (or parameters) in a surrounding scope should probably require something like [[shadow]] somewhere to make it abundantly obvious it's intentional (and its use cases would be incredibly rare) (3) Initializing a variable by passing itself as an argument should be disallowed (so struct MyClass { int x; MyClass() : x(x) { } }; should be illegal, i.e. the equivalent of -Werror=init-self should be mandatory) (4) value-initialization should probably be the default, but with a way to override it and perform default-initialization when there's actually a reason to (but perhaps -Wuninitialized should still treat the variable as uninitialized regardless) (5) Perhaps the C-style cast should really be equivalent to a static_cast except in cases where a dynamic_cast/reinterpret_cast/const_cast would also be legal, in which case it should be an error? That would make it safer than static_cast (since it's more restrictive in where it's allowed), rather than more dangerous, and it would require less typing as well.
- compiler-devel 4y agoWow, thank you for this rich and thorough follow up. Interestingly, in the present C++ standard, 'mutable' indeed is a storage class like 'register' while 'const' is a CV-qualifier. I thought it quite odd that 'mutable' isn't a CV-qualifier (the standard leaves this gap open) and for nonmember variables, my patch makes it a CV-qualifier; otherwise it remains a storage class specifier. WRT lambda changes, I have no particular timeline for this project as it's something I took up on the side. Pointers kinda break any data flow analysis that could be done. For example, imagine an object that serializes itself in one translation unit and is deserialized in another using a different class (this is somewhat common in telecommunications code. Imagine a struct Header { ..., void end[0]; }; which is used to handle messages of variable length but with the same Header types). Java does just fine without goto (or have they added that since 2010?). I think code that is intentional is more effective than code that is accidental. That said, I'd rather suppress the unused parameter warning with the '#pragma diagnostic ignored' mechanism than use a cast mechanic that just happens to address a compiler issue. Thank you for the great list of rules! I agree with them all. Minor nit: you can't use static_cast to cast away constness.
- dataflow 4y ago> Java does just fine without goto (or have they added that since 2010?). C++ is quite literally meant for use cases where Java (or Python or C# or Go or pretty much any other language) is not "just fine". And as I mentioned above, you CAN get by without goto. You just have to go through (go to?) contortions in certain cases without it that make the situation worse rather than better. (And there is no reason to believe such use cases are equally common across all languages, so keep that in mind. For example hardware contexts require dealing with explicit state machines a lot more than software contexts do, and C/C++ are used more in those contexts—to name just one example.) Remember Java was doing "just fine" without lambdas, and it's still doing "just fine" without templates, value types, manual memory management, and a million other things you find in C++. Even C was also doing "just fine" without generics and destructors, but then they realized they're missing out and finally added it. You have to realize, goto is basically a religion nowadays. People want to believe goto has no legitimate use cases, because (I can only assume) they're scared someone will use it as an excuse to utilize it irresponsibly outside those contexts. Kind of like why some drugs require prescriptions, I guess. I can't stop people from believing what they want, but as far as facts go, it does have use cases that many people simply don't encounter, and I tried to list some of them in my comments above. > Minor nit: you can't use static_cast to cast away constness. You actually can! Check this out: int const b = 1; int const *p = &b; **static_cast<int **>(static_cast<void *>(static_cast<int const **>(&p))) = 2; assert(p == &b && *p == 2); If this is surprising... I would take it as an indication that it's difficult to foresee what can be done even with the commonplace features in the language (in both good and bad directions), let alone the rare ones (like goto).