8 ms·
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
- usefulcat 4y agoAll 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.
- compiler-devel 4y agoInteresting idea!
- mxmlnkn 4y agoCouldn't [[maybe_unused]] fill the role of [[discardable]]?
- tlb 4y agoHow 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 4y agoNo C style casts allowed, so maybe static_cast<bool>(...) ;-)
- vlovich123 4y ago> - 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 https://github.com/carbon-language/carbon-lang
- compiler-devel 4y agoThank 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).
- chakkepolja 4y agoApart 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 4y agoYes, 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 4y agoI 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.
- compiler-devel 4y agoThank you, this indeed is the approach that I'd like to take. Like I mentioned in the commit message on the patch, one of my goals is to show that we can iterate the language (and our codebases) gradually. I'll add a feature flag to my patch to selectively enable and disable these changes.
- bartwe 4y agoOk some more suggestions: - Pointers aren't arrays. - no implicit conversions at all. - require fields to be initialized before use/end of constructor
- compiler-devel 4y agoSome 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.
- nextaccountic 4y agoimplicit 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 4y agoGreat point, I was thinking of ints as 32 bits. You're absolutely correct for 64 bit ints!
- dataflow 4y agoI 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'.
- favorited 4y agoI'd love to see integer promotion die in a fire, to prevent this: https://twitter.com/stephentyrone/status/1410636445593837569 https://twitter.com/stephentyrone/status/1410636445593837569
- jesse__ 4y ago
- wyldfire 4y agoRather 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 4y agoAgreed, 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 4y ago> 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"?
- sanxiyn 4y agoC style casts, already prohibited by this patch, seems to be a good example.
- arinlen 4y ago> C style casts (...) Those are pretty much irrelevant since at least C++98, specially as not only are they used voluntarily but also under the hood they are already handled with explicit casts. Is this the best argument there is to break backwards compatibility?
- archi42 4y agoI hate writing proper C++ casts because C style is just shorter (e.g. (T)(foobar)), and have to force myself to write the whole invocation. So yeah, not even having the lazy option wouldn't be too bad :)
- VadimPR 4y agoI 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 4y agoThanks! 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.
- dataflow 4y agoI 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!
- 4y ago
- nyanpasu64 4y agoHow 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?
- overgard 4y agoCouldn'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.
- kevin_thibedeau 4y agoYou don't even need a linter for most of this. Just turn on modern compiler diagnostics and you'll have common footguns flagged.
- wjko21 4y agowhat'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.
- compiler-devel 4y ago> I almost never see anyone using it without a good reason In any of those cases, could the code have been rewritten without goto?
- Findecanor 4y agoMy 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)
- compiler-devel 4y agoThis is interesting, are there languages in use today with these constructs?
- gautamcgoel 4y agoYeah, 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.
- nlewycky 4y agoIMO 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-1175596077 https://github.com/dealii/dealii/issues/3705#issuecomment-11... If I think of some more, I'll reply to myself.
- leni536 4y agoconst-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++)
- compiler-devel 4y agoIn my first release I didn't address pointers or references and need to think on them further. I'll release updates over time that address these as well as many of the great ideas that I've received from other commenters.
- nextaccountic 4y agoThis 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 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
- compiler-devel 4y agoGreat suggestions all around. UB is a beast of a problem and compilers take advantage of UB for optimizations (some going so far as to break programmers' code).
- mxmlnkn 4y agoClang-tidy can detect some of the "Modified C++" constraints like no implicit conversions to bool and can even suggest automatic fixes and also apply them with clang-apply-replacements. Clang-tidy is already a transpiler for "Modified C++".
- compiler-devel 4y agoIndeed. I conceded in my commit message that a linter or checker (such as clang-tidy) could be used to implement some or most of what I suggested (but not the mutable/const change of course). Aside from learning more about how clang works, I wanted to show that we can (and should?) modify the language. There appears to be inertia to drop legacy at the committee level. C++ needs a Snow Leopard release to do some of what I've done here.
- jesse__ 4y ago> 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)
- ebingdom 4y ago> The net effect of this would be that I type "mutable" all over the place You might want to consider adopting a more modern programming style for the benefit of your coworkers (and possibly yourself). Mutability all over the place is a nightmare, speaking as someone who currently has to work in a large codebase written like that. It's hard to predict what value a variable is going to have at any particular point in your code, since instead of only having to check where the variable is defined, you have to audit all the code between the definition and the use. For the same reason, it's hard to guarantee that your invariants are maintained, since there is a much larger surface area to check.
- jesse__ 4y ago> You might want to consider adopting a more modern programming style Just because some people think a particular style is useful doesn't mean everyone does. You might want to consider checking your biases before making comments like this. I understand the (many) arguments for using `const`, and I've concluded (for myself) that it's not a useful construct. Read and internalize the rest of my previous comment for more information.
- archi42 4y ago- 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).
- compiler-devel 4y agoCompletely agree! Thank you for these suggestions.
- archi42 4y agoBtw, a lot of bad things are already present as compile time warnings. Using -Wall -Werror (or similar) should already the default for new projects for well over a decade. You probably know that, but "stealing" stuff from there is also a good idea. Plus, iirc, there are several additional warning options that are not contained in -Wall. Maybe you want to add some of these, too.
- compiler-devel 4y agoGreat suggestion.
- tyfighter 4y agoI 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 4y ago"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 4y agoI 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.
- tialaramex 4y ago> This thing and Rust: "constant" -> something unable to change "mutable constant" -> a changeable constant...what? "What?" indeed. Rust doesn't have "mutable constant". Rust's "const" is actually a constant, unlike in C where "const" means "Sort of immutable, although maybe not". I guess maybe you've been told something about Rust like "let is kinda like C++ const" and so you've come to the erroneous conclusion that somehow "let mut" means "mutable constant" but that's just because you didn't really understand, blame either your attention or the poor explanation, it's surely nothing to do with Rust which has never said this is "mutable constant" since that's nonsense.
- viktorcode 4y agoI'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.
- compiler-devel 4y agoThat is neat, I'll look further into it.
- nwallin 4y agoI 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 4y agoMany 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.
- _kst_ 4y agoOne 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).
- beached_whale 4y agoAdd something like a break_n/break_label that allows for leaving a loop from a switch statement. Or allow goto in that limited case.
- mjan22640 4y agoCould you please write a translator, that would convert into the modified syntax instead of emitting an error?
- rurban 4y agoWithout an Issue tracker on the GH fork it will be hard to add ideas. I would add: removing Unicode identifiers, because identifiers are meant to be identifiable.
- compiler-devel 4y agoGreat suggestion, I'll set up an issue tracker. A few people have ventured into the code to leave some comments inline, I welcome that.
- grandinj 4y agoIn the libreoffice project, we have implemented some of this eg. no c style casts, using clang plugins, which avoids needing a custom build of clang. But always good to see people experimenting in this space!
- compiler-devel 4y agoThat's great to hear! I'm sure I could find it, but are the libreoffice coding standards written down (and could you send them my way)? On the topic of coding standards, there's an excellent github repo https://github.com/isocpp/CppCoreGuidelines https://github.com/isocpp/CppCoreGuidelines which even quotes Bjarne Stroustrup as saying "Within C++ is a smaller, simpler, safer language struggling to get out." There are hundreds of recommendations, like 'ES.31: Don't use macros for constants or "functions"'.
- grandinj 4y agoSo we don't really have strong coding standards (kind of tricky when you inherit a 10 million LOC codebase written over ~20 years), we just try to be pragmatic and improve the code where we can. So we have a collection of plugins, see here: https://cgit.freedesktop.org/libreoffice/core/tree/compilerplugins https://cgit.freedesktop.org/libreoffice/core/tree/compilerp... Which verify a variety of things. We focus on 2 things: finding dodgy code and using APIs correctly. We don't try to modify the C++ language, just restrict accidentally straying into some of the really nasty corners. But I like to keep an eye on experiments like yours for ideas :-) e.g. no c-style casts: https://cgit.freedesktop.org/libreoffice/core/tree/compilerplugins/clang/cstylecast.cxx https://cgit.freedesktop.org/libreoffice/core/tree/compilerp... use the comma-operator sparingly: https://cgit.freedesktop.org/libreoffice/core/tree/compilerplugins/clang/commaoperator.cxx https://cgit.freedesktop.org/libreoffice/core/tree/compilerp... is your loop variable really big enough: https://cgit.freedesktop.org/libreoffice/core/tree/compilerplugins/clang/loopvartoosmall.cxx https://cgit.freedesktop.org/libreoffice/core/tree/compilerp... calling virtual methods from destructors is dodgy: https://cgit.freedesktop.org/libreoffice/core/tree/compilerplugins/clang/fragiledestructor.cxx https://cgit.freedesktop.org/libreoffice/core/tree/compilerp...