[HN Gopher] We used C++20 to eliminate a class of runtime bugs
___________________________________________________________________
We used C++20 to eliminate a class of runtime bugs
Author : maccard
Score : 119 points
Date : 2022-01-14 12:56 UTC (10 hours ago)
(HTM) web link (devblogs.microsoft.com)
(TXT) w3m dump (devblogs.microsoft.com)
| joatmon-snoo wrote:
| I recently bumped my company's fmtlib version to the one that has
| compile-time format strings, and it's been such a load off our
| minds to not have to worry about this anymore. We're reasonably
| early stage (12 people right now) and even with all of us being
| reasonably senior, I still found a few callsites where the
| compile-time format string caught bugs.
|
| (FYI: for anyone holding off on this because of issues with
| spdlog, enough has been fixed in the latest versions of both that
| you can upgrade them together now.)
|
| Also worth noting: the UX of fmtlib's compile-time format strings
| is actually quite interesting. There are error callbacks in the
| consteval calls that take advantage of the context that compilers
| show in compilation failures that detail the mistake being made.
|
| (Aside: I'd also like to note that `absl::StrFormat` has
| supported compile-time format strings for quite a long time.
| https://abseil.io/docs/cpp/guides/format)
| LAC-Tech wrote:
| I haven't used C++ in years. I can remember writing C++11 and
| transitioning to C++14. And also lots and lots of Valgrind :)
|
| What's it like these days? Is tooling pretty much the same -
| downloading packages from linux, some header only libs, and
| makefiles? Has the Language Server Protocol made in roads with
| C++ editors and IDEs?
| ncmncm wrote:
| C++20 is as big an advance as 11 was, and is taking as long to
| penetrate.
|
| Editors all know C++ now. Generic lambdas in C++14 made coding
| fun. Template metaprogramming is hardly ever needed or useful
| anymore. Builds got faster with module support (essentially
| standardized precompiled headers), and also ccache and ninja.
| As Concepts penetrate, error messages get radically better.
|
| Valgrind is increasingly unnecessary, as use of op new
| vanishes.
| flumpcakes wrote:
| One of the most interesting changes to me is how much they
| are pushing metaprogramming into the language itself
| _without_ a lot of the traditional annotations that went with
| TMP before.
|
| For example, annotating a parameter with 'auto' instead of
| the typename T preamble.
| ncmncm wrote:
| Template metaprogramming was always known as a hack, no
| more so than among people obliged to do it. Most of the
| things it was used for are now done with core language
| facilities designed for the job, and coded in compiled C++
| instead of interpreted template flailing, so fast.
| DiabloD3 wrote:
| Imagine what happens when Microsoft moves to Rust, it will be a
| red letter day.
| muricula wrote:
| MSRC is pushing rust internally: https://msrc-
| blog.microsoft.com/2019/11/07/using-rust-in-win...
| pjmlp wrote:
| There is some ongoing Rust adoption on Azure and IoT, but I
| doubt WinDev C++/COM bastion will ever allow anything else,
| given how they manage to keep it going over the years.
| flumpcakes wrote:
| Microsoft have done a lot of working attempting to make every
| windows C/C++ API available for use in Rust. This task is/was
| too big to do by hand (and sensitive to changes) so they have
| automated it. See: https://github.com/microsoft/windows-rs
| scoutt wrote:
| Well... a "Microsoft Visual Rust" (integrated with Visual
| Studio) could be all the motivation I'm looking for to finally
| learn, mmmm, not learn but to finally put Rust at serious use.
|
| With everything integrated like, you know (and this may sound
| super crazy) graphically-mouse-operated "create new project",
| "right click -> add new file to project", resource editor with
| "create a window with a button, double-click the button and
| generate a function to handle the event"-sort of thing. F7 to
| build, F5 to debug. More or less the things we are used to
| since the past 30 years or so!!!
| formerly_proven wrote:
| Maybe they can even recreate that amazing Visual Studio
| project settings dialog for Rust!
| maccard wrote:
| I saw this this morning (and also rusts support for fmt strings).
| Whenever c++ is discussed I often find myself defending modern
| c++ against people who want to write c++03 because it's simpler.
| I like this article because it shows how useful modern features
| really are in practice.
| pjmlp wrote:
| C++ is one of my favourite languages, however there is a big
| difference bettwen being able to write modern C++ in side
| projects, and what most companies consider C++.
|
| Enjoy a sample of AOSP source code,
| https://android.googlesource.com/platform/art/+/refs/heads/m...
| gruez wrote:
| >Enjoy a sample of AOSP source code, https://android.googleso
| urce.com/platform/art/+/refs/heads/m...
|
| What am I looking for? Is it just the use of smart pointers?
| bentcorner wrote:
| +1 it looks ok, although the stylistically I would
| personally prefer to split this file up into smaller parts
| (although maybe it's all related). I'm also not a big fan
| of macros but again without knowing more it's not too
| unreasonable.
|
| Personally I wouldn't like to slog through this on a
| moment's notice but it seems named well and there are a lot
| of comments, and appears to have been through clang-tidy.
| throwawaybbqed wrote:
| Wow ... I want to pull my eyes out now. Is this really some
| uber critical path in the code or did the author(s) have too
| much time on their hands/optimizing some other metric (e.g.
| promotion, proving how smart they are, attempting to get job
| security). I am also astounded by two things related to
| documentation .. it is nearly non-existent, and where it
| exists, seems to provide almost no context to someone other
| than the original authors.
|
| You posted this for a reason .. would be good to get your
| thoughts.
|
| edit: adding one more point. I went to a fairly high-ranked
| university(top-20). We would get graded based on how clear
| our code was, comments (both for functions and inline
| comments). This code would get a zero on those metrics.
| emteycz wrote:
| Hmm... I can't see how the code is unreasonable, given the
| complexity of the task at hand. Could you tell me what
| could be improved about this code (code itself, not docs
| etc)?
| jcelerier wrote:
| just opened a file and...ugh
|
| https://android.googlesource.com/platform/art/+/refs/head
| s/m...
|
| like, just this, basically C: if
| (strncmp(argv[arg_idx], "-XXlib:", strlen("-XXlib:")) ==
| 0) { ...
|
| is just so complicated and error-prone over
| if(std::string_view{argv[arg_idx]}.starts_with("-XXlib:")
| ) ...
|
| (which I guess is the intent)
| jmgao wrote:
| That code is older than C++11, let alone C++17. Also,
| your proposal isn't as efficient, since it calls strlen
| on construction of string_view (not that cost of parsing
| command line arguments is going to be measurable).
| ncmncm wrote:
| The code is ancient, and Google (like most) sees no value
| in modernizing old code, particularly for non-revenue-
| generating product. Furthermore, Google long ago locked
| themselves into an idiotically anti-RAII coding standard.
|
| So, the example is of bad old code, at a place that
| enforces bad coding practice. The intent appears to be to
| suggest this is typical practice, which is not supported.
| [deleted]
| maccard wrote:
| Just because some companies write ancient C++ code doesn't
| mean all companies do. I write mostly C++17 these days for a
| company, and my previous job wasn't exactly afraid of modern
| C++ but they could have embraced more of it. The post I
| shared is from Microsoft about writing modern C++ .
| pjmlp wrote:
| I know, and I can provide similar quality reference from
| Microsoft own samples if you wish.
| maccard wrote:
| I don't get the point you're trying to make, sorry. "Big
| companies sometimes write crappy code" is of course going
| to be true.
| pjmlp wrote:
| Most companies still write C++ crappy code, regardless of
| the advocacy many of us in the community try to make.
|
| In fact, if we focus on traditional enterprise shops they
| are stuck in what I call the C+ mindset.
|
| Those of us that bother to live in HN, Reddit, attend
| CppCon, C++Now, C++ on Sea, ACCU in whatever form, are
| 3lit3. A tiny minority that actually cares about quality.
|
| That is the point.
| flumpcakes wrote:
| It doesn't sound like much of a point. The same thing is
| going to be true for all programming languages widely
| used in industry. It's not a C++ issue.
|
| In fact, see Python 2 vs 3 for probably the most
| impactful case of people being slow to take up new
| language versions/features.
|
| There's going to be shops writing C89. Some reasons might
| be well founded, some reasons might be less well founded.
| maccard wrote:
| The people who write crappy modern C++ will write crappy
| C++03, crappy java and crappy golang.
| bborud wrote:
| It would be interesting to see statistics on use of newer
| C++ in companies known to have relatively stringent
| coding standards and code review regimens. I suspect that
| it (still) isn't so much about crappy code as much of the
| value of C++ being sunk into legacy code that determine
| the constraints put on *new* codebases.
| jjice wrote:
| I don't write C++ for work and I've written very little ever,
| but it seems like C++ and PHP (which I do write daily) have a
| similar trend. They get shit for being old and bad, but people
| haven't seen the myriad of new features, safety, and solid
| ecosystem.
| superkuh wrote:
| C++?? every 3 years isn't nearly as bad as say, the Rust
| situation, but it does mean that a linux desktop distro
| compiler can only compile stuff from "hip" devs writing in C++
| for about 3 years after the distro release. It means if you
| chose to use these language features the vast majority of
| people will not actually be able to compile it.
|
| So balance that. If you're not just distributing binaries, if
| you have geek end users compiling, maybe don't always use the
| latest bleeding edge C++?? features.
| rixed wrote:
| Complexity can solve issues but can also bring new ones.
|
| For instance, I've been bitten recently by the new "universal"
| syntax to write variable definitions with only curly braces.
| #include <iostream> #include <string> #include
| <vector> using namespace std; int main()
| { // Forty-two things: vector<string> v1 { 42,
| "foo" }; vector<int> v2 { 42, 1 };
| vector<char> v3 { 42, 'x' }; cout << v1.size()
| << " " << v2.size() << " " << v3.size() << endl; return
| 0; }
|
| This prints, of course:
|
| > 42 2 2
| edflsafoiewq wrote:
| The committee loves overloading. IMO they overuse it. I think
| once you start thinking "this overload only participates in
| resolution if..." you should start considering different
| functions.
| staticassertion wrote:
| I feel like there was success with overloading `<<`, which
| is weird but very appealing for students IME, and suddenly
| overloading everything was on the table.
| tialaramex wrote:
| Overloading << is part of Stroustrup's iostreams toy and
| thus enjoyed the advantage that the language's creator
| was advocating it from the outset (it pre-dates the C++
| standard library). I doubt that anything so ugly and
| dangerous would be standardised even in C++ today.
|
| Because this is actually still a shift operator, it
| retains all the precedence of the shift operator even
| when tired humans think of it as as a clever new stream
| out operator. So if you're in a situation where shift has
| precedence, the operator fires, and too bad if that
| wasn't what you'd intended nor what makes sense to a
| human reading the code.
|
| Oops.
|
| Some languages let you mint new operators (from some
| limited combinations of symbols) or allow infix function
| calls, or by some other means provide for you to nicely
| extend syntax here, but the overload of unrelated
| operators was IMNSHO an ongoing disaster whose popularity
| with programmers is unexplained, like C's decision to
| treat an array as just the pointer to the front of the
| array.
|
| But I'm a cranky old man, I also don't like the fact that
| Rust's + concatenates Strings (this isn't inherent in the
| language since String isn't a built-in type, in _Linux
| kernel_ Rust nothing like this exists, because Linus
| would throw a fit, but in the out-of-box runtime
| environment Strings implement the Add operator as a
| concatenate operation).
| blindseer wrote:
| Ugh.
|
| I'm firmly of the opinion that if anyone uses a sane
| programming language first, they'll absolutely do anything
| they can to avoid C++.
|
| Corollary being, 1) if C++ is your first language, you'll end
| up learning all these rules and exceptions to the rules and
| assume that is just how it is (this is the way I think about
| Python), and 2) if you use C++ you are aiming for performance
| that alternative languages cannot provide. I'm glad the gap
| between alternatives and C++ is narrowing. If I could, I
| would never write another line of C++ again. Rust and Zig are
| FAAAARRRR better alternatives to me personally, purely
| because there's fewer gotchas I have to keep in my head when
| attempting to do anything with those languages.
| rixed wrote:
| In my case I'm writing C++ because I want to use a library
| implemented in C++.
|
| One of the gripe I have with C++ is that it has replaced C
| as the goto language for general purpose libraries, yet
| it's hard to built bindings for another language (whereas
| bindings to C are trivial).
| jcelerier wrote:
| I'm curious about your use case for calling the
| vector(size_t, value_type) constructor (vector<string> v1 {
| 42, "foo" }).
|
| I'm on a codebase of a few hundred kloc and the need for a
| vector initialized to anything but the default value pretty
| much never came up. It uses boost, Qt, llvm and two dozen
| other libraries, yet there is a grand total of eight
| instantiations of that constructor in it.
|
| When putting a breakpoint, it turns out that all the calls to
| it I could trigger actually just initialize with the default
| value statically (e.g. vector<T*> v{x, nullptr};).
| rixed wrote:
| Easy because since the bite I'm adding some comments after
| those initialisations where I really intend to call a given
| constructor.
|
| Three instances I could grep immediately, all from the same
| file:
| std::vector<std::tuple<std::optional<qreal>, QString,
| QColor>> values( // Not list-init
| numValues, std::make_tuple(std::nullopt, QString(),
| QColor())); (...) std::vector<size_t>
| nextTupleIdx(numValues, 0); // Not list-init here!
| std::vector<bool> done(numValues, false); // Not
| list-init here!
|
| I do not doubt a single second that there are 36 ways to
| write this that are simpler, more efficient and more
| readable. Yet I would like to hear your suggestion.
| LAC-Tech wrote:
| Can someone explains why it prints that?
| CJefferson wrote:
| There are two different features interfering, which were
| added at the same time.
|
| The {42,1} is an "initializer list", which was a way of
| passing a list of things to a constructor.
|
| However, at the same time, something called "uniformed
| initalization" was added, which used {}, to "clean up" how
| constructors work.
|
| However, from looking at a piece of code it's impossible to
| know if it is trying to call "initializer list" or
| "uniformed inialization". The "initalizer list" wins in
| this case, when the items are all things you can put in the
| vector.
| pierrebai wrote:
| Oh how I agree about the uniform initialization! And to
| make matter worse, I now work on a code base where it is
| the mandated form in the coding standard. At least your
| initial example now gives me ammunition against it, but
| too late for this coding standard.
|
| It makes code look silly, too, as now initialization
| looks different from assignment. int i
| { a + 2 }; i = a + 4; int j =
| a + 2; j = a + 4; // ah! consistency!
| ncmncm wrote:
| It is good for initialization to look different. It _is_
| different. Not so much so for int, but maybe hugely
| different for other types.
| tialaramex wrote:
| Also, although this might be more obvious to people
| familiar with any C-like language, C++ considers that
| since 'x' is a character, and its characters are
| fundamentally just an integer type (maybe a signed 8-bit
| integer, it depends) the third list is just two constant
| integers, which are a perfectly reasonable thing to put
| in a list. Even though that's obviously not what a human
| sees.
|
| In a language that isn't just _pretending_ to be strongly
| typed, the compiler would notice that char and whatever-
| integer-type-42-is aren 't the same type, and it would
| _reject_ your program as nonsense.
|
| But that confusion gets much less chance if the language
| explicitly distinguishes initialising collections so that
| "I want N of X" is different from "I want N, and X".
| Compare Rust's vec! macro: let mut v =
| vec!['x'; 42]; // NOTE semi-colon. Compiles, vector of 42
| x characters let mut v = vec!['x', 42]; //
| NOTE comma. Does not compile let mut v =
| vec![b'x', 42]; // NOTE comma. Compiles, vector
| containing 120 (the ASCII code for 'x') and 42 as bytes
| let mut v = vec![b'x'; 420]; // NOTE semi-colon.
| Compiles, vector of 420 bytes with value 120
| let mut v = vec![b'x', 420]; // NOTE comma. Does not
| compile unless you explicitly tell Rust that you mean the
| low 8-bits of 420 if you do this
|
| I'd actually be enthusiastic about a Clippy warning for
| the middle one, because it feels like the odds are better
| than they should be that wasn't what you meant. But on
| the other hand, the odds of you wanting _either_
| possibility are slim, so, not a priority.
| beached_whale wrote:
| Generally in C++ one should use Type name = Type( args... )
| style to be the least surprising. Prior to C++20 that would
| be aggregates. It's really too bad that initializer_list gets
| special treatment, and it's universally wart in C++. The
| alternative would have been vector{ { 1,2,3} } and that would
| have been fine.
| CJefferson wrote:
| This was, in my opinion the single worst thing added to C++
| -- it's what made me quit the C++ standard's committee, as it
| was clear (to me) how much of a mess things were.
|
| Different people wanted "{ } for unified construction" and "{
| } for initalizer lists", and we ended up with this mess which
| made 0 people happy, and ruined unified construction.
| maccard wrote:
| universal initialization is the worst feature added to modern
| c++; these changes are unexcusable IMO.
| kazinator wrote:
| When I was programming C++ regularly, which was C++03, I wrote
| a robust format string system which could catch all mismatches
| against the format string at run-time. Which is good enough in
| practice if you actually test the code paths.
|
| What's valuable is being able to compile format strings into
| faster code, like the formatter macro in Common Lisp.
| kazinator wrote:
| What this is saying to me is that they have untested code paths.
| All the places where error encountered bad parameters are not
| actually covered.
|
| It doesn't seem incredibly valuable to know that some error()
| calls in untested code are well-formed, since that code could be
| broken. The error() could be a false positive, or the compiler
| could crash before reaching the error() call due to some bug. Or
| some of the errors() could be in _de facto_ unreachable code: no
| test case can cause them to be executed.
|
| If you have a test suite which hits all the error() calls, and if
| the formatting system is robust to catch bad arguments at run-
| time, you don't have a problem.
| jokoon wrote:
| I remember using variadic template to implement a print()
| function, to print any type of variable, with a variable amount
| of arguments.
|
| I got help to implement it, it was a bit difficult, but quite
| nice to have.
| ducharmdev wrote:
| As someone coming from a web background with almost no systems
| programming experience, Rust is appealing to me in terms of it's
| language features and support for a functional style of
| programming, as well as the documentation and tooling (among
| other things).
|
| But from the little I've learned about modern C++ and RAII, I
| wonder how systems engineers will respond to Rust in the long-
| term. Memory safety seems to be the primary argument in favor of
| using Rust, but what would be easier - rewriting everything in
| Rust, or refactoring existing code to take advantage of RAII?
| leetcrew wrote:
| I'm not sure I fully understand the question. RAII is powerful,
| but it is just a tool to organize what is still manual resource
| management. it can help with memory/thread safety, but it
| doesn't guarantee those things by itself. ex: it's nice that
| std::vector's memory buffer will be freed when the object goes
| out of scope, but I can still do something stupid like hold
| onto to its .data() pointer and try to dereference later.
|
| to answer the more general question, I think most engineers
| would prefer to refactor a c++ project over rewriting the whole
| thing in rust, and rightly so. you usually don't want to fuck
| around with delicate things that currently work in these sorts
| of projects. perhaps over time you end up with an FFI layer
| over a thoroughly tested c++ core. then new features can be
| written in rust or whatever friendly language is popular at the
| time.
| ducharmdev wrote:
| I appreciate the clarification, my understanding of this
| stuff is pretty superficial.
|
| Putting it that way, I can see why someone would appreciate
| that Rust guarantees safety by default, instead requiring you
| to opt-out explicitly when needed (with `unsafe`), versus the
| opt-in nature of RAII - for greenfield projects, anyway.
| rileymat2 wrote:
| One quibble one of the best uses for RAII is not memory
| management but any external resource like db connections or
| file handles or mutexes.
|
| The built in smart pointers take care of much of memory
| management. (Although they use RAII internally)
| pjmlp wrote:
| RAII was already a thing in C++ compilers for MS-DOS.
| zwieback wrote:
| I used to be a 100% C++ programmer in the olden days but over
| the years C# and Python have pushed my C++ percentage down to
| maybe 10% super time critical code and some embedded.
|
| RAII is the one thing I really really miss from my C++ days.
| From what little I know about Rust it seems to promise a more
| "baked-in" version of RAII so I'm all for it. Haven't found the
| time to experiment but looking forward to eventually dipping my
| feet in.
| pjmlp wrote:
| If you are using a relatively recent version of C#, you can
| get quite close to it, specially now that _using_ can make
| use of structural typing.
|
| And with help of Roslyn, any type with a "destructor" (aka
| Dispose) that gets used without a using declaration can be
| turned into a compiler error.
| cogman10 wrote:
| I'd argue that RAII is pretty well baked into C++.
|
| For Rust, it's the "Drop" trait that you add onto things
| which ultimately causes RAII behavior.
|
| You don't need to do it as much with rust as a lot of the
| reasons for wanting RAII (memory management) are simply
| handled by the language.
|
| For Rust, it's sort of the "Pit of success" the easy thing to
| do is the right thing to do. Whereas in C++, it's pretty dang
| easy to new something up and fail to correctly delete it.
| maccard wrote:
| > Whereas in C++, it's pretty dang easy to new something up
| and fail to correctly delete it.
|
| This is true, but we shouldn't be encouraging people to
| reach for new/delete these days; auto Foo =
| std::make_unique<Foo>(args); does the right thing in a
| surprisngly large number of cases. It's not the only tool
| available, but it's a really damn good one.
| cogman10 wrote:
| The "Pit of success" and "Pit of failure" aren't really
| about what's available in a language, it's about what's
| easy and natural.
|
| I'd argue that in a language with the `new` keyword,
| nothing is more easy or natural than invoking it. `auto
| Foo = std::make_unique<Foo>(args);` is absolutely the
| right thing to do, but would you really be surprised to
| find `auto foo = new Foo(args);`?
|
| I absolutely agree unique pointers are the right call.
| They just aren't necessarily the easy or natural call.
|
| The contrast is rust where all ptrs are, by default,
| unique ptr and the compiler validates that for you.
| HALtheWise wrote:
| I've thought before about making a "dialect" of C++ that
| doesn't add any new fundamental capabilities, but simply
| changes syntax to make the more modern options easy and
| natural and make old C features verbose and difficult.
| For example, using "*" to represent unique_ptr and
| std::legacy_c_ptr<...> to represent "bare" pointers.
| Ideally, such a dialect could be fully include-compatible
| with all existing C++ code (although macros could be
| difficult), similar to how Rust "editions" or go
| "language versions" work. If C++ compilers offered a
| "#pragma modern_defaults" would people be interested in
| using it?
| de_keyboard wrote:
| Can we agree that the new "best default" way of
| instantiating is... pretty damn weird?
| auto foo = std::make_unique<Foo>(args);
|
| Think about explaining this to a new C++ developer.
|
| This will feel much more natural to most developers:
| var foo = new Foo(args);
| not2b wrote:
| The "best default" way of instantiating is
| Foo foo(args);
|
| Sometimes an object has to be on the heap as its own
| separate allocation (rather than on the stack or as a
| data member), but I notice that some new developers who
| come to C++ from some other language use the heap way too
| much.
| criddell wrote:
| > Think about explaining this to a new C++ developer.
|
| How's this:
|
| The code allocates and initializes a new Foo instance
| with args. By using make_unique you guarantee that the
| destructor is called when foo goes out of scope for any
| reason.
| leetcrew wrote:
| in my experience, it's almost inevitable that you end up
| calling some code you don't control that returns raw
| pointers. better hope you have access to the source or at
| least some good docs :)
| maccard wrote:
| Agreed. That problem existed even before unique_ptr
| though; there are still some APIs that I use that hand
| you back pointers that you may or may not need to manage
| yourself. If you do need to, then unique_ptr + custom
| deleter is the way to go!
| CJefferson wrote:
| I've been using RAII in C++ since 2002 or so -- so it's not a
| new thing to do.
|
| Personally, I maintain quite a lot of open source C++ code --
| I'm not rewriting that in Rust, but I'm writing new projects in
| Rust.
| cogman10 wrote:
| > rewriting everything in Rust, or refactoring existing code to
| take advantage of RAII?
|
| Oh, refactoring existing C++ code is easier, hands down.
|
| You can, for the most part, keep your C++ algorithms exactly as
| is with little rewriting. Rewriting in rust may force you to
| totally rethink your approach (or use a lot of unsafe, or write
| really inefficient code).
___________________________________________________________________
(page generated 2022-01-14 23:01 UTC)