[HN Gopher] Show HN: Modifying Clang for a Safer, More Explicit C++
___________________________________________________________________
Show HN: Modifying Clang for a Safer, More Explicit C++
Modified C++ Inspired by the paper "Some Were Meant for C" by
Stephen Kell, I decided to show that it's possible to iterate C++
to be safer, more explicit, and less error-prone. Here's a
possible starting point: I didn't invent a new language or
compiler, but took the world's best compiler, clang, and modified
it to begin iterating towards a new furture of C++. Naming things
is hard, so I call this 'Modified C++'. Some of the following could
be implemented as tooling in a linter or checker, but the idea is
to update the compiler directly. I also wanted to learn more about
clang. This compiler needs a flag to enable/disable this
functionality so that existing library code can be used with a
'diagnostic ignored' pragma. You can build clang using the normal
non-bootstrap process and you'll be left with a clang that compiles
C++ but with the following modifications: - All
basic types (excluding pointers and references) are const by
default and may be marked 'mutable' to allow them to be changed
after declaration - Lambda capture lists must be
explicit (no [&] or [=], by themselves) - Braces are
required for conditional statements, case and default
statements within switches, and loops - Implicit
conversions to bool are prohibited (e.g., pointers must be
compared against nullptr/NULL) - No goto support
- Explicit 'rule of six' for classes must be programmer-implemented
(default, copy, and move c'tors, copy and move assignment, d'tor)
- No C style casts Here's an example program that's
valid in Modified C++: mutable int main(int,
char**) { mutable int x = 0; return x;
} Here's another that will fail to compile:
mutable int main(int, char**) { int x = 1;
x = 0; // x is constant return x; }
I'd like your feedback. Future changes I'm thinking about are:
- feature flag for modified c++ to enable/disable with 'diagnostic
ignored' pragma, to support existing headers and libraries
- support enum classes only - constructor declarations are
explicit by default - namespaces within classes -
normalize lambda and free function syntax - your ideas
here
Author : compiler-devel
Score : 48 points
Date : 2022-08-17 19:21 UTC (2 days ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| usefulcat wrote:
| All non-void return values should be [[nodiscard]] by default. Of
| course then you will need something else ([[discardable]]?) to
| indicate the ones that may safely be ignored.
| tyfighter wrote:
| I really do not understand the Rust-esque love of the "mutable"
| keyword in rebellion of "const". They are most often attached to
| variables. The definition of the word variable is "subject to
| variation or changes". By definition, variables change. Constants
| do not change. I understand that the semantics here are
| historical, but it's very much like "Automated ATM Machine".
| Maybe I just don't like the word mutable, and would prefer "var"
| or "varying".
| ithkuil wrote:
| "const" historically means "compile-time constant".
|
| A "mutable" variable is contrasted to an "immutable" variable,
| not to a "constant".
|
| You may not like the name "variable" for something that cannot
| be changed within a given scope, but it's still something that
| can take multiple values during the execution of a program.
| tyfighter wrote:
| I think you missed the point I was trying to make. C/C++
| currently: 1.) "variable" -> something that is subject to
| change 2.) "const variable" -> an unchanging thing that is
| subject to change (I guess you could say it only changes
| once).
|
| This thing and Rust: "constant" -> something unable to change
| "mutable constant" -> a changeable constant...what? Even
| "mutable variable" -> a changeable thing that is a thing that
| is subject change doesn't make much more sense.
|
| It is fine for "things" to be immutable by default, and in
| fact I think they should be. I just think "mutable" is
| keyword smell similar to "decltype" because type wasn't
| keyworded from the start.
| viktorcode wrote:
| I'm not sure about explicit braces for cases in `switch`. I think
| what Swift does is pretty neat: each case breaks by default, so
| you don't have to write `break;`, instead you have to write
| `fallthrough` to explicitly allow them falling through.
| _kst_ wrote:
| One suggestion: In documentation and comments, distinguish
| clearly between "const" and "constant".
|
| "const" means "read-only", and probably should have been spelled
| "readonly".
|
| "constant", as in "constant expression", means evaluated at
| compile time.
|
| For example: `const int r = rand();` is perfectly valid: r can't
| be computed until run time (it's not constant), but it can't be
| modified after its initialization (it is const/readonly).
| vlovich123 wrote:
| > - All basic types (excluding pointers and references) are const
| by default and may be marked 'mutable' to allow them to be
| changed after declaration
|
| If you're not changing how const works, then this has limited
| utility in C++ because C++ const has all sorts of problems (e.g.
| not transitive). Also, what does the "mutable" annotation for a
| free function (i.e. main) mean? That just seems weird.
|
| > - Lambda capture lists must be explicit (no [&] or [=], by
| themselves)
|
| [&] is pretty valuable in cases where you do something like
| invokeSynchronously([&] {...})
|
| I don't know that your changes will ever see much adoption
| because it won't be able to compile anything more complex than a
| "hello world" program as all the things you disallow are used &
| the porting effort is not cosmetic. Additionally, you're not
| actually fixing any of the problems people have with C++. So:
|
| 1. Consider fixing const semantics if you're going down the path
| of defining a new language
|
| 2. Think about how to fix memory safety and issues around UB
| which are the #1 sharp edges for C++
|
| I don't know if you're achieving the goal of a safer, less error-
| prone language with the changes outlined. Have you looked at the
| things Carbon [1] is doing? I'd say that's an attempt to define a
| spiritual successor to C++, one that can easily interoperate with
| C++ but when you stay within the language it's safer.
|
| [1] https://github.com/carbon-language/carbon-lang
| Findecanor wrote:
| Just don't fall into the trap of believing that only making
| constness transitive would make it fool-proof.
|
| A non-"pure" const member function with const parameters could
| still call some other function with access to a "mutable" alias
| to what you have a const reference to. You would need something
| more, such as an ownership system to make the compiler make
| that impossible (as in Rust) or to detect it (the topic of
| Master's thesis, BTW ) ... but then it would no longer be
| something resembling C++.
| compiler-devel wrote:
| Thank you for this great feedback. I'll do my best to respond
| to each of your points:
|
| WRT const, you're correct and I'd need to go further in
| updating const behaviors in the language. I stole this idea
| from Rust (sort of) in that variable declarations in that
| language are const by default. Essentially, I wanted to 'flip'
| the semantics in C++ to match, and use mutable to allow
| variables to change after their declaration. I could go further
| to enforce transitivity (e.g. so you can't do something like
| mutable x = y; where y is const).
|
| [&] is handy indeed yet this was motivated by my experience in
| legacy heavy codebases where there are often many variables in
| scope and some with external consequences (e.g. file
| descriptors, sockets). I don't want these accidentally captured
| if the lambda invocation site has lifetime implications beyond
| those resources.
|
| I think I'm achieving the goal of a safer, less error-prone
| language because these changes could've prevented the 2014
| 'goto fail' from happening (and not just because the keyword
| goto would be omitted but because there was a conditional
| without braces in the affected source making the code less
| explicit and less clear).
| kllrnohj wrote:
| > [&] is handy indeed yet this was motivated by my experience
| in legacy heavy codebases where there are often many
| variables in scope and some with external consequences (e.g.
| file descriptors, sockets). I don't want these accidentally
| captured if the lambda invocation site has lifetime
| implications beyond those resources.
|
| I think your solution to that problem goes the wrong way,
| though. The problem is whether or not a lambda can survive
| past the immediate usage, not what it captures. Listing those
| resources explicitly still gives you the same bug, banning
| [&] didn't avoid it.
|
| I'd suggest instead an approach where a template taking a
| callable annotates whether or not it's "inline". If it is
| inline, then [&] should just be the default even. If it's
| not, _then_ ban [ &]. Possibly ban taking anything by
| reference if it's not a synchronously-used lambda even.
|
| (inline / non-inline terms here cribbed from Kotlin
| https://kotlinlang.org/docs/inline-functions.html - probably
| there's a better word for it, but whatever)
| diegoperini wrote:
| Swift calls them Escaping Closures.
|
| https://docs.swift.org/swift-
| book/LanguageGuide/Closures.htm...
| compiler-devel wrote:
| https://www.synopsys.com/blogs/software-
| security/understandi...
| enedil wrote:
| mutable x = y in my opinion should be allowed. After all the
| new variable is just a copy of old one. Why shouldn't you be
| able to modify it?
| _kst_ wrote:
| Agreed, just like mutable x = 42 should be allowed.
| P5fRxh5kUvp2th wrote:
| chakkepolja wrote:
| Apart from the mutable keyword, can't these be implemented as a
| clang diagnostic plugin? Then it can be used to enforce a
| stricter style guide. As another commenter pointed, mutable will
| be probably of limited use anyway.
| compiler-devel wrote:
| Yes, some (maybe most) could be implemented in a plugin. I
| wanted to make these changes in part to better understand the
| clang internals and also show that rather than use external
| tooling, the language itself can (should?) be changed.
| cweagans wrote:
| I would submit that C++ has enough inertia (as a language and
| as an ecosystem) that changing the language itself would be
| difficult.
|
| However, C++ is a _huge_ language and if there 's a way to
| enforce safety by using only a subset of the language +
| tooling to help you do that, your improvements could be
| adopted piecemeal by teams looking to level up their codebase
| a bit.
|
| Many languages have a way to opt in to e.g. strict type
| checking on a per-file basis. It would be really cool to see
| these improvements implemented in such a way that existing
| codebases could gradually adopt them.
| nextaccountic wrote:
| This is a really great idea, specially if you can write a
| transpiler from general C++ into modified C++ (it can error out
| on corner cases and ask for manual intervention, but trivial
| stuff like adding missing braces can and should be done by an
| automatic tool, like rustfix is used to migrate between Rust
| editions https://github.com/rust-lang/rustfix)
|
| But here you didn't tackle the main thing: a plan to make simple
| business logic not corrupt memory and cause havok with UB!
| Dereferencing arbitrary pointers is a dangerous operation that
| shouldn't be done in everyday code. If I'm writing data
| structures I'm willing to think about UB, but if I'm choosing the
| color of a widget I'm less so. I'm not expecting you solve this
| hard problem, but at least a general direction or a half solution
| that works for a % of the cases would be cool (or at least state
| this is a long term goal).
|
| And of course there's the comparison to Rust, but Rust is
| actually just a data point in this solution space and perhaps new
| languages can afford to try new approaches
| wjko21 wrote:
| what's the motivation for removing `goto`, is this something that
| you find being abused? I code in c++ for work, and I almost never
| see anyone using it without a good reason.
| gautamcgoel wrote:
| Yeah, I'm curious about this as well. I know the Go and Lua
| creators explicitly included goto in their languages, saying it
| can be useful if used carefully.
| Findecanor wrote:
| My personal opinion is that a programming language should
| instead of 'goto', have explicit constructs for those things
| that 'goto' is most often used to emulate:
|
| * Breaking out of nested loops
|
| * Clause after loop that has run to its end-condition without a
| break, return or throw. Python allows an 'else'-clause after a
| loop, but IMHO "default" would be a better keyword.
|
| * Error handling (C++ has exception handling already, but there
| are alternatives)
| leni536 wrote:
| const-by-default is definitely nice. Does this extend to both
| sides of a pointer type? Does int * refer to int const * const?
|
| There is nothing wrong with [&] for short-lifetime lambdas.
| Lambdas passed to std algorithms or immediately invoked lambdas
| come to mind.
|
| edit:
|
| Are data members also const by default? How do I declare a non-
| const data member that is const when accessed within a const
| member function? (so non-const non-mutable in original c++)
| bartwe wrote:
| Ok some more suggestions: - Pointers aren't arrays. - no implicit
| conversions at all. - require fields to be initialized before
| use/end of constructor
| jesse__ wrote:
| > require fields to be initialized before use
|
| Good idea. That would catch annoying bugs I rarely, but
| occasionally, have.
| compiler-devel wrote:
| Some implicit conversions are okay, like type promotion from
| int to double. Some type coercions are fraught, like char to
| int or back again. I agree that array decay to pointer could be
| explicit, and pointers shouldn't cast to arrays.
| favorited wrote:
| I'd love to see integer promotion die in a fire, to prevent
| this:
| https://twitter.com/stephentyrone/status/1410636445593837569
| nextaccountic wrote:
| implicit int to double is really, really bad! it can silently
| truncate - double can only store 53 bits integers so for
| large integers the result will not be an integer!
|
| in general, lossy conversions should never, ever be implicit
| compiler-devel wrote:
| Great point, I was thinking of ints as 32 bits. You're
| absolutely correct for 64 bit ints!
| dataflow wrote:
| I would instead say int should be guaranteed to fit in a
| double. I feel like there's no reason to introduce a
| pitfall in > 99.999% of use cases just because there might
| be some obscure architecture where int is 64-bit and its
| programmers cannot be bothered with the extra keystroke for
| 'long'.
| dataflow wrote:
| 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 wrote:
| 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 wrote:
| > 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.
| nyanpasu64 wrote:
| How about making atomics mutable through const&, adding move-by-
| default, and marking all constructors (value, conversion, and
| copy) as explicit aside from move, and probably add explicit copy
| assignment as well?
| jesse__ wrote:
| > All basic types (excluding pointers and references) are const
| by default and may be marked 'mutable' to allow them to be
| changed after declaration
|
| FWIW, for me, this is an anti-feature, and I would not use this
| language because of it. The net effect of this would be that I
| type "mutable" all over the place and get very little for my
| effort.
|
| I've spent a significant amount of time understanding what the
| high-consequence programming errors that I make are, and "oops, I
| mutated that thing that I could have marked const" is a class of
| error that consumes a vanishingly small amount of my debugging
| time.
|
| The errors I make that account for a large portion of my
| debugging time are errors related to semantics of my program.
| Things that, in C++, are typically only detectable at runtime,
| but with a better type system could be detected at compile time.
| The first step for this might be type annotations that specify
| valid values. For example, being able to annotate whether an
| argument is or is not allowed to be null, and having that
| enforced at call sites.
|
| (NOTE: I also don't spend a meaningful amount of time debugging
| accidental nullptr values, but that's a good first step towards
| the type annotations I _do_ want)
| nwallin wrote:
| I think this is an interesting idea but I also think it will
| never gain adoption.
|
| Move constructors/Move assignment should be noexcept by default.
| It's not entirely clear to me what a program ought to do if a
| move constructor/assignment operator throws an exception. In a
| general sense you cannot 'trust' the old object to not have been
| modified.
|
| "All basic types (excluding pointers and references) are const by
| default" -- why the exception?
|
| The rule of zero should be acceptable in addition to the rule of
| 6. Also, the rule of 5 is acceptable in many circumstances; lots
| of classes should _not_ have default constructors. I agree that
| having 1,2,3, or 4 are bad, but 0,5,6 are acceptable.
| leni536 wrote:
| Many standard containers don't have noexcept move operations in
| Microsoft's implementation. It is conforming. Having a throwing
| move doesn't mean that you can't have exception guarantees.
| Just do the throwing operations before you modify the source or
| target objects.
| overgard wrote:
| Couldn't most of these be covered by a linter? I'm not sure you
| really need a new language for this. Even right now in Visual
| Studio resharper is constantly telling me about things that can
| be constexpr or const and a lot of the other things you mention
| here.
| nlewycky wrote:
| IMO it would help adoption if you supply a clang-powered rewriter
| into and out of your language variant. It allays the fear of
| losing your codebase if the compiler project dies.
|
| Reverse the default for typename. Currently some_class<T>::thing
| is assumed to be an expression where 'thing' is a variable, when
| we don't know which template pattern to use because there may be
| an explicit specialization on the T that the user chooses. Hence,
| we have to say "typename std::vector<T>::iterator it;" instead of
| just saying "std::vector<T>::iterator it;". Instead, reverse that
| and assume it's a type by default unless shown that it's an
| expression. You'll need a new keyword for that, replacing
| "typename".
|
| Remove the promotion-to-int rules. Currently in C (and in C++)
| unsigned short test(unsigned short a, unsigned short b, unsigned
| short c) { unsigned short x = a * b * c; return
| x; }
|
| can have UB as signed integer overflow because any math done on
| an object smaller than int gets promoted to int. (No, you can't
| fix this with "(((unsigned short)x) * ((unsigned short)y))" the
| promotion happens on _' s LHS and RHS, if those have types
| smaller than int.) Beyond this, people seem to expect that the
| type of the variable declaration will appertains to the
| calculation on the right, but it doesn't. For instance people
| seem to think "float f = a + b;" can't overflow where 'a' and 'b'
| are ints, because the assignment is going into a float.
|
| I haven't thought this idea through completely yet. Extend
| pointer types to include a static allocation identity as part of
| the type. Address-of local variable or global variable should
| produce one of these pointers. A "static allocation identity" is
| a special-typed zero-size variable, so you can stick it in code
| or as a class member. You could have pointers that were
| guaranteed to be allocated by THIS allocation point, instead of
| pointing to every possible T_ in the program. I'll fake up a
| syntax, "tree_node ^ tree::node_alloc _". It 's known not to
| alias any other TreeNode_ the program might have, it has to be
| attached to the allocation point owned by that specific
| "node_alloc" in that object. (Let me phrase it differently. A
| tree in C or C++ has pointers which can point anywhere as long as
| it's another tree node type. That could be pointing to a
| different tree, it could be a self-pointer, it could be pointing
| up the tree, and so on. If your tree_node class has an allocation
| root, you can say that the pointers are things allocated through
| this allocation root. They can not outlive the allocation root.
| They are distinct from the things allocated by other allocation
| roots, which are the same tree_node types, but different
| tree_node objects. The node's list of children is
| std::vector<std::unique_ptr<tree_node ^ node_alloc _> > so it
| clearly only holds pointers it allocated itself.)
|
| There's another problem with pointer related to the above. Some
| code I saw used a "T &get_or_default<K, V>(Container &c, K key, V
| &default);" and the problem was that people would call it with a
| temporary for the default, like "Value &x = get_or_default(mymap,
| key, Value());" and they'd be holding a dangling reference. If
| you could make that an error, that'd be great. Maybe we use a
| trick like the "allocation root" above and treat pointers or
| references to temporaries have different type from the local
| variable. Then get_or_default takes and returns a reference-to-
| temporary and attempting to assign that to a reference in a
| variable declaration fails. Unlike the previous "allocation root"
| idea where you indicate the only thing you accept, this would be
| a case where you accept all allocation roots except one, the
| "temporaries" allocation root.
|
| As far as I know, no compiler takes advantage of the freedom of
| the order of operations except in the most trivial ways. Everyone
| knows that in "f() _ g() + h()" that * must happen before +, but
| people think this means that f() and g() must happen before h().
| No, they may happen in any order at all. I had to fix a lot of
| code that did "Print(stream.read(), stream.size())" where "read"
| updates the pointer and leaves size == 0: gcc ran stream.size()
| first and clang ran stream.read() first, setting the subsequent
| size to zero. Similar issue with "expr1() = expr2();"
| expressions.
|
| Extend switch() and case to work on objects with any operator==
| defined. Add a statement for fallthrough and default to 'break;'
| before the start of the next case-label. Give each case label its
| own scope so I can declare variables in there without adding my
| own curly-braces. (Bonus 1 can you design a way to ensure that
| case labels are not overlapping? May require something other than
| operator==. Bonus 2 can you allow cases to be structured binding
| matches, similar to Rust?)
|
| Speaking of structured binding, it's great but doesn't allow
| nesting. This
| std::vector<std::unordered_map<std::string, std::pair<int, int>>>
| v; for (auto [name, [lhsid, rhsid]] : v) {
|
| is code I actually wanted to write in the past week yet that's a
| syntax error.
|
| Add the ability to declare object inheritance ("class Derived :
| Base;") so that I can cast between them before writing out the
| body of the derived class. Also allow me to write out the entire
| class tree with no possibility for extension in another
| translation unit. The "final" keyword states that a class may not
| be derived from, but I usually have a Base class which does have
| subclasses, but a known list of subclasses that will never grow
| without recompiling the whole project. Currently the compiler has
| to assume I could write a new subclass and compile it into a
| shared object that the existing program dlopen's and the existing
| program will work. It's crazy. No, I have the final tree not just
| some leaf classes, please devirtualize the whole thing for me.
|
| Are ABI changes on the table? Explicit template instantiations
| and explicit specializations should mangle differently. See my
| comment elsewhere:
| https://github.com/dealii/dealii/issues/3705#issuecomment-11...
|
| If I think of some more, I'll reply to myself.
| archi42 wrote:
| - define the evaluation order for function parameters, e.g.
| f(a(), b()) [or is that well defined in modern C++?]
|
| - allow for named arguments. E.g. let's say for the definition
| f(int a = 12, int b = 42), one might call f(b: 1337) or f(12,
| 1337). Not allowing mixing of named & positional is probably a
| good idea.
|
| - take a look at static verification and remove language features
| that make static verification more difficult and think about how
| you could replace them (or remove; but e.g. function pointers &
| similar stuff fall in that category and are probably to powerful
| to be sacrificed this way)
|
| //Edit: as others have said, try to whack as much undefined
| behaviour as possible (and in case you can't, don't accept the
| input program).
| wyldfire wrote:
| Rather than build a new compiler, I wonder if this might be
| easier to integrate as a static checker. IMO clang static checks
| are not that difficult to write. The hardest thing can be the
| query to find the interesting elements. But you're
| banning/requiring fairly high-level language elements so they
| should be pretty easy queries to write.
| compiler-devel wrote:
| Agreed, that's why I started by modifying clang. I think we can
| start dropping some of the crufty legacy in the C++ language
| without throwing it all out and starting again. While clang
| tidy could be used to check for a lot of these, I wanted to
| show that we could change the language directly and what that
| could look like.
| arinlen wrote:
| > _I think we can start dropping some of the crufty legacy in
| the C++ language (...)_
|
| Do you have any concrete example of what you perceive as
| being "crufty legacy"?
| VadimPR wrote:
| I think it's a great experiment, keep doing what you're doing.
| Removing the footguns from C++ without wildly changing the syntax
| up is a solid idea.
| compiler-devel wrote:
| Thanks! Aside from the default const/mutable change, this was
| my approach. To improve adoption, it would be easy to add a
| feature flag for this set of changes which could be applied on
| a per file basis.
| tlb wrote:
| How hard would it be to automatically convert some existing C++
| into the new language? It seems like your compiler can diagnose
| the errors, so inserting `mutable` and `bool(...)` should be
| possible.
|
| It might be interesting to do this on an existing codebase just
| to see where mutable is needed.
| compiler-devel wrote:
| No C style casts allowed, so maybe static_cast<bool>(...) ;-)
___________________________________________________________________
(page generated 2022-08-19 23:02 UTC)