4 ms·
I think you might be onto something with regards to the general idea, but most of your particular rules I disagree with. vector<mutable int> for example is very
by dataflow 4y ago
I think you might be onto something with regards to the general idea, but most of your particular rules I disagree with. vector<mutable int> for example is very strange; there's no reason vector<int> shouldn't work. With respect to lambda captures always being explicit, it's a far heavier restriction than you (and many) people realize—sometimes you literally cannot know what's inside the lambda to be able to capture it (look up the SCOPE_EXIT macro as just one example), and even when you can, listing all of them is sometimes far more harmful to readability than helpful—it depends strongly on the situation. Goto is absolutely necessary in certain rare but practical cases too—like when converting a recursive algorithm to an iterative one without breaking git blame. C-style casts to (void) are pretty useful, so you'd need at least an exception for that.
Constructors being explicit by default I 100% agree with, and there are other rules I could come up with too, but in general, you need to realize that a lot of the features in the language have legitimate use cases that you might simply have a hard time imagining. Therefore, coming up with useful rules without hampering useful functionality requires both (a) experience & playing around with the language to a greater extent than you might at your job, and (b) a great deal of thought on top of that.
- compiler-devel 4y agoThank 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.