HN Theater @HNTheaterMonth

The best talks and videos of Hacker News.

Hacker News Comments on
How to Adopt Modern C++17 into Your C++ Code : Build 2018

Microsoft Developer · Youtube · 225 HN points · 0 HN comments
HN Theater has aggregated all Hacker News stories and comments that mention Microsoft Developer's video "How to Adopt Modern C++17 into Your C++ Code : Build 2018".
Youtube Summary
Just how different is "Modern C++" from "legacy C++"? Is my codebase ready for C++17? Do I need a full rewrite of my app to modernize my code? If you're looking for answers to some of these questions, join us for a session on how to effectively leverage modern C++17 features in your existing C++ projects; and no, you don't need to rewrite your app.
HN Theater Rankings

Hacker News Stories and Comments

All the comments and stories posted to Hacker News that reference this video.
May 10, 2018 · 225 points, 121 comments · submitted by pjmlp
zengid
::Somewhat tangential anecdote and plea for advice::

I just finished my third semester of using C++ (with Data structures 1) and we use NO modern stuff in class [1]. Raw pointers. New and delete. No Lambdas (although we learned about function pointers, though not function objects). I completely understand why (we're learning low level stuff and it's worked for the last 30 years so whatever), but now I'm trying to ramp up and learn modern C++ for a personal project I'd like to start working on (basically an audio plugin using JUCE).

Here's what I've been doing to try and catch up (I have two weeks off before my internship starts). I try to watch Bjarne and Herb's keynotes but they often are speaking to an audience already familiar to modern C++, so this posts video is great! I've discovered the brilliance of Sean Parent, so I'm watching all of his talks. I've ordered Scott Meyers Effective Modern C++ (and hopefully he uses the money for a new haircut /s). I've browsed through the C++ Core Guidelines [2]. I'm reading the JUCE tutorials (and source code) which is beautifully written.

What are other good resources? I'm particularly interested in developing GUI's. I'd like to see some tutorials written like Herbs talk here: for people new to C++ and new to modern C++.

[1] This is our textbook: https://www.amazon.com/Programming-Program-Design-Including-...

[2] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#...

proverbialbunny
The largest change is ownership semantics. If you can learn or have learned ownership semantics in such a way that ties that abstract concept to concrete real life examples, most of your work will be done.

Modern day C++ has two ways to do most of the things you want to do: The C way, and the C++ way. eg, c array[] vs std::array<>, or char* vs std::string and the like. However, original C++ without C is still there and not updated. Eg, inheritance is a C++98 feature, not a C feature, so there is only one way to do inheritance for the most part.

It takes little time to identify what features are C features and what features are C++ features. From there you'll know when to look up alternatives and when not to, giving you an easy upgrade map.

Also, some C features are more powerful, so it is a matter of choosing the right tool for the job. For example, looping through a data structure and adding elements. Doing so invalidates iterators, but works will with an index, so it might be ideal to use the old C way in situations like that. This also adds explicitness to code. If you're working in a modern code base and in the rare spots where the C way is done, you can get an idea why and from that what the code is doing without having to read all of it. This makes reading code easier once both C and modern C++ have been learned.

bluGill
Herb and Bjarne's keynotes from 2011. They are always pushing what is new and exciting, while the rest of us are just catching up.

Don't forget the other information that you can get from speakers who are not as good. New and shiny is often easy to abuse simply because nobody is sure if it is a good idea or not. Not everything stands the test of time. Other things are great and critical in their niche but create an awful mess when abused to apply elsewhere.

blub
Bancila's Modern C++ Programming Cookbook is surprisingly good. There's also a video by Vittorio Romeo called "Mastering C++ Standard Library Features". Both of those are from Packt, which have a pretty poor reputation, but don't let that hold you back in this case.

Josuttis' The C++ Standard Library is the classic reference.

sacado2
> I completely understand why (we're learning low level stuff and it's worked for the last 30 years so whatever)

Are you sure it's not just because your teachers are not (enough) aware of those new features ? Because I work in a University and know way too many colleagues that still use new/delete on a daily basis.

zengid
I believe you are correct, but even the textbook ignores the new stuff except for nullptr and briefly mentioning range based for loops.

The professor who taught my Data Structures class (who is an exceptionally great teacher BTW) actually uses C in most of his own work, so we even learned a little bit about how to simulate recursive calls using a void* stack and goto. I hope my peers don't go out and write code like that in the wild, but that's another story..

palisade
Learning how it used to be done isn't a bad thing, though. Because when you need to maintain code that existed before modern C++, of which there is much, you'll be ready.
monocasa
FWIW, I used to be the lead of a C++14 RTOS. You can be low level and still use a lot fo these techniques.

And that void*/goto hack sounds bad, even in C land. That's basically only acceptable in a byte code interpreter.

gpderetta
well, if you have to implement a non tail-recursive function and want to have control of the max recursion depth (i.e. not get killed because you have exhausted the stack) it is an option.
monocasa
There you should probably pass a iteration count into the function rather than some weird goto hack.
monocasa
Effective Modern C++ is a great book designed for "I learned pre C++11, but now I want to get with the times, and specifically want to know the idiomatic ways to apply it".
TomVDB
Is Effective Modern C++ good enough for those who learned C++ in the early 2000, still use those idioms and never upgraded to C++11 ?
monocasa
I'm not quite sure what you're asking.

Is it a useful book if you plan on keeping a C++03/C++98 codebase? Probably not, Effective C++ is the better book for that world.

Is it a useful book for migrating from premodern to modern C++, even iteratively? I'd say yes, that's one of the book's explicit design goals.

TomVDB
The second part: I want to bring my practical C++ knowledge up to date and I don't know C++ 11.
blub
I'd recommend Bancila's Modern C++ Cookbook instead, it takes you through the new features and explains how to use them.

Effective Modern C++ is part language tutorial, part series of tips, part philosophical discussion on how one should use various features. It's not appropriate as an intro to modern C++.

monocasa
> Effective Modern C++ is part language tutorial, part series of tips, part philosophical discussion on how one should use various features. It's not appropriate as an intro to modern C++.

See, I take the opposite stance. There's enough moving parts here that it's nice to get some background into what's actually happening, and then read more cookbook style stuff when you understand the underlying context.

dkersten
Thanks for the recommendation! I’ve wanted to improve my C++11 and beyond knowledge. Just picked it up on kindle and started reading, really insightful so far.
saagarjha
Great talk, but I still have a couple questions that I'm sure someone here can answer:

1. I didn't see what the smart pointer stuff had to do with C++17. Aren't these all available from C++11? Are these something new that I missed, or are they just comments on how to avoid bugs, based on Herb Sutter's personal experience?

2. Should I std::string_view everywhere I used to use std::string now? The examples given seemed to lean heavily towards using std::string_view as parameters instead of std::string, but should I use them as a drop-in replacement everywhere?

3. The std::optional example looked really clunky, relying on checking an exception. I see there are a bunch of nice operators and utility functions on std::optional that cover some use cases, like coalescing, but will we get something that allows for optional chaining?

humanrebar
1. In the introduction, Herb Sutter mentions this talk is also for people that might not know about C++11 features.

2. Use std::string for when you need to own a string. Use std::string_view when you need to reference a string [a]. Generally you want std::string_view for parameters and std::string for member variables. Return values and local variables can be either, but if you're not sure, std::string is safer though slower.

3. You chain optional, but only with nested functions, and you code starts looking more and more like lisp. If you are a fan of a chained syntax, you might be interested in libraries that support "ranges". boost already has ranges and there are proposals to add them to C++20.

[a] If you want more specifics and detail, the CppCoreGuidelines have a section on when to use which string. https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC...

saagarjha
TIL about std::byte; I should have probably used it instead of char in a recent project of mine…
pdpi
std::string_view is, as the name implies, a view into a std::string.

If you own the actual string, use std::string — there is no other string for you to view.

If somebody else owns it and you're working on the whole string `const std::string&` is probably your best bet — you're holding a reference to the string as a whole — but std::string_view also works and might make more sense in context.

If you're working with a substring of somebody else's string, std::string_view is the natural choice.

The key idea here is that you want to reference, rather than copy, some underlying string. If you're working on something where your code needs to own a deep copy of the original string for some reason, by all means continue using std::string!

criddell
Whenever I'm working with substrings, I'm always nervous about bugs resulting from different encodings. I'm never comfortable with search for substrings or applying regular expressions or iterating over a string.

Encodings in general a bit of a headache. I work on Windows and it wants UTF-16 internally and everything external wants UTF-8.

humanrebar
> If somebody else owns it and you're working on the whole string `const std::string&` is probably your best bet

Actually string_view is more general. You can build one out of a string literal, a vector<char>, some memory from a memmap call, and an unbounded list of other things. If an interface uses a `const std::string&` and you don't have one, you have to, implicitly or explicitly, copy the data into a `std::string` to be able to use that interface.

stochastic_monk
Is it just me, or does it seem like std::string_view is just an alternative in semantics to a `char *` plus a size? It doesn't null-terminate (which would require copying or mutating). Is there some extra safety checking this provides?
humanrebar
There are mild benefits to having one value that represents both a size and an address. So you're passing around a pair of things that go together instead of just using convention.

But the real benefit is in providing it as a "vocabulary type". Once C++ code starts using string_view everywhere, you don't need to massage your char sequences this way and that [1] to pass it from the top of your application all the way down into the low-level libraries the application relies on. And you don't incur the overhead (both in lines of code and in copies) of all that massaging.

[1] Maybe your request handler has to convert from char* and size to a string to call your business logic, then your string has to convert from a string to a char pointer and and int length to call saveName. saveName might actually use std::copy under the hood to put the data into a chunk of memory, so it turns the pointer and length into two pointers.

gpderetta
- It has range semantics, so you can use it with standard algos or range for.

- It encodes the invariant in a type (char * points to at least size bytes, no expectation of null termination, no ambiguous definition of an empty range)

- The size always travel with the pointer so no chance that it gets separated.

- syntactically simple conversion from string (or other string-like entities) to string view reduces clutter in the code.

I consider lack of null termination a plus.

how_gauche
Under the hood it's just "std::pair<const char, std::size_t>". It's like an abstract data type or a newtype in Haskell: construction of the aliased type is restricted to the functions that are provided.

You are correct, there is no safety --- you can call the constructor std::string_view(const char, size_t) and stuff anything you want in there. The main reason string_view is nice is the implicit conversions you get from the other string types. Pass in a "const char*", it finds the terminating null for you. Pass in a std::string, no problem. No boilerplate.

alexhutcheson
This is true as long as your implementation doesn't call any functions that take a `const string&`. If you need to call such a function, then you first have to make a copy of your string_view into a new string object before you call it.
saagarjha
Is there a way to do this without involving the overhead of a full copy, though?
alexhutcheson
Not that I'm aware of - a const std::string& parameter needs to be a reference to a std::string object, and there's no guarantee that a given string_view object is pointing to a std::string.
masklinn
> 3. The std::optional example looked really clunky, relying on checking an exception. I see there are a bunch of nice operators and utility functions on std::optional that cover some use cases, like coalescing, but will we get something that allows for optional chaining?

Nope. It must be understood that std::optional is not an Option type, its purpose is not to have a way to express "missing" items in a type-safe manner but rather to have pointer-type semantics without needing to heap-allocate. So you can just deref' an std::optional, and it's UB if they're empty. Don't think of it as Haskell's Maybe, think of it as a stack-allocated version of std::unique_ptr.

humanrebar
> ...its purpose is not to have a way to express "missing" items in a type-safe manner...

Except that's exactly why you would take an `optional<int>` as a parameter. To avoid the ambiguity and possible bugs of taking an `int*`.

tzahola
You mean int*, right?
humanrebar
Yes. Fixed! Thanks.
masklinn
> Except that's exactly why you would take an `optional<int>` as a parameter.

Except for the type-safe part which is the important bit (hence the emphasis on it), and which std::optional does not provide.

> To avoid the ambiguity and possible bugs of taking an `int*`.

The only ambiguity and possible bug (singular) you're avoiding using std::optional is the question of ownership. Because as emphasised in the original comment std::optional is no more type-safe than a raw pointer.

humanrebar
Clarity about ownership is enough. But there's also ambiguity because pointers are sometimes used to pass around arrays, especially in the case of 'char*'. And a dev also has to go out of his way to create a dangling optional<int>.

Even if we consider just ambiguous ownership, there are a whole host of behaviors that could result from that single design mistake, including undefined behavior, which could include trashing your production database, firing the missiles, or behaving as expected until an unrelated piece of code changes a year later.

saagarjha
> It must be understood that std::optional is not an Option type, its purpose is not to have a way to express "missing" items in a type-safe manner but rather to have pointer-type semantics without needing to heap-allocate.

I don't understand what you mean here. I don't have to heap-allocate to get a pointer to something; I can just take the address of something on the stack…

svalorzen
The purpose is exactly to express possibly missing return values, which is exactly why it is called "optional", rather than "pointer_type_semantics<T>".

I also don't see why you would ever use it in the way you're describing. Even if you had template code, either you manipulate objects/references, or pointers. And worst case, you usually try to handle references, rather than trying to risk UB.

masklinn
> The purpose is exactly to express possibly missing return values

Operative word: type-safe. std::optional is not that.

> I also don't see why you would ever use it in the way you're describing.

I'm literally just describing the semantics of std::optional.

jcelerier
Regarding 2., in general, if you have a function that takes a `const std::string&` as parameter, you can replace it by a `std::string_view`. If you have stuff like `constexpr const char* whatever = "blah blah blah";` you can also replace it by a `std::string_view`. If you have a `std::string` member in a class, then you must not replace it by a `std::string_view`.
stochastic_monk
Only if you don't need null-termination. I suppose, should you decide to make direct fwrite/write/memcpy calls using its size you can avoid even needing the null terminus, but a lot of code requires a null-terminated string. I think a std::string_view would bring in more opportunities to break code than make things easier.
beojan
2: You can't use them everywhere. They're a view so you can't use them to actually store a string.

3: Hopefully someday we'll get a proper monadic interface in C++. Until that day, I'll keep using exceptions.

proverbialbunny
For 3, std::optional has the has_value() method.

std::optional<int> a = foo(); if(!a.has_value()) { return; } bar(a);

is pretty standard.

Though, with exception handling you get function chaining which can be rather nice.

saagarjha
I'm look for something like Swift's optional chaining, á la:

  std::optional<std::string> a = foo();
  auto size = a?.size() // size is a std::optional<int>
alexhutcheson
re: string_view, this is a really good summary: https://abseil.io/tips/1
pjmlp
Smart pointers have been an evolution, C++11 deprecated auto_ptr (gone in C++14), make_unique only came up C++14 due to an oversight on C++11, C++17 constructor type deduction makes everything more productive.

Regarding memory and performance improvements std::string_view should better than using std::string, but don't go overboard replacing all code. It depends what kind of application you are writing, the age of existing code, or sometimes a plain old const reference might be enough.

There are some ongoing efforts for such chaining

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p079...

https://github.com/TartanLlama/optional

EDIT: corrected the make_unique/make_shared misinformation.

humanrebar
> ...make_shared only came up C++14 due to missing deadline on C++11...

You might have a typo here. make_shared was available in C++11. make_unique was left out from C++11 as an oversight and added in C++14.

pjmlp
Yes you are right, my mistake.
alexeiz
I just checked how many times I use explicit delete in my moderately sized 7K LOC C++ project: just once. In code that interfaces with an old library. Modern C++ is not only practical. It's easy.
thekingofh
You using smart pointers from C++11 or later?
alexeiz
Yes. But surprisingly not as often as I thought I would. Move semantics and guaranteed copy elision allows to avoid pointers in many common situations.
vvanders
It's also better in that you can avoid cycle-leaking from your smart pointers.
thekingofh
I'm still having trouble seeing how move semantics and copy elision help me get rid of smart pointers. I read up on it after reading these comments and just still am not following. I see how move semantics avoid a copy, but if I'm dealing with cases where multiple objects need to use or hold a reference to the same object, how can move semantics help me do this? Seems good for ensuring only one object contains the reference.
alexeiz
Notice that I didn't say all cases. Just the most common ones. When you require shared ownership of an object, of course, you'd use an appropriate smart pointer (shared_ptr). But shared ownership is not very common (well, at least in the systems that I design). However before C++11/14, you had to use pointers and heap allocated objects in cases that have nothing to do with shared ownership because passing objects by value would incur a substantial overhead. Now that we have move, copy elision and library classes like std::optional (which I forgot to mention earlier) it's not the case any more.
thekingofh
Ah I'm following now. Transfer of stack references in cases where it's unnecessary to share objects. Actually interesting that it raises the bar for the need to allocate on the stack.
None
None
nebgnahz
I have struggled in the past to learn modern C++. I think the language is pretty cool, but couldn't find good guide in learning about them.

Having used Rust for a year or so, now I look back at these features and they seem quite natural. I have to say the documentations and tutorials from Rust community is great. It might be a detour, but now I feel much more comfortable reading modern C++ blogs and watch this video!

partycoder
I think it would be good to break backwards compatibility and start off clean with what we know now in the form of a new language.
geokon
Link for those that can't use Youtube: https://channel9.msdn.com/Events/Build/2018/BRK2146

(also has download links)

ddtaylor
Sanity check, does gcc support all of these?
jchb
Yes, but the final version of the C++17 standard was released as late as December 2017, so support may not be "production ready", neither in gcc nor in other compilers.

You can the summary of the gcc c++ standard support here: https://gcc.gnu.org/projects/cxx-status.html#cxx17. Full support for all features in the c++17 standard, but the support is declared "experimental".

Similarly here for clang: https://clang.llvm.org/cxx_status.html

And Visual studio: https://blogs.msdn.microsoft.com/vcblog/2018/04/26/announcin... (link to latest blog post, perhaps there is a status page that gets updated, but could not find it).

baking
Maybe I'm missing something, but http://en.cppreference.com/w/cpp/compiler_support seems to have it all in one place.
pjmlp
It misses most of the compilers on the embedded space.

For example, TI still has some compilers stuck on C++98 and C++03.

http://processors.wiki.ti.com/index.php/TI_Compilers_and_Ind...

how_gauche
Martin Moene (my favorite C++ person in the entire universe right now) wrote a drop-in replacement for older standard versions: https://github.com/martinmoene/string-view-lite. He's also backported optional, variant, and expected, and wrote my favorite unit testing library called "lest".

This guy saved me so much hassle this year and it's causing a complete sea change in the way that we work. I really really want to live in a world of value-typedness and it's finally possible to do it. The simple and natural idioms are now MORE efficient than doing it the complicated way: the average function I'm writing in 2018 is significantly less convoluted and allocates on the heap far less than the same function I'd be writing five or six years ago.

rehemiau
Can I cheaply "pattern match" on variant type without involving exceptions or dynamic cast etc?
en4bz
Variant uses a compile time jump table when you use std::visit afaik.
dkersten
It looks like it essentially uses function overloading (multiple overloads of operator()) to do it. Since function overloading compiles to a call of a single function, based on the argument types, this results in a single runtime call. Nice trick!
mellery451
I think visit is what you want (http://en.cppreference.com/w/cpp/utility/variant/visit), unless I misunderstand what you mean by "pattern match"
ninkendo
I used to keep pretty good track of what's been happening in c++, at least as C++11 was being formalized, and I've read a good fraction of Effective Modern C++, but...

I can't make heads or tails out of half of the code in that example. C++ has become extremely hard to grok.

I mean, take these two lines:

    template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
    template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
What's even going on there? Inheriting from a variadic list of classes? `using` a variadic list of parent functions, I guess? The second line looks like it's defining a constructor function for the overloaded() struct, but it's not implemented anywhere?

In the end there's magic code that looks like pattern matching, but I have no idea how they got there.

dkersten
The key here is that a parameter pack T... can be actually unpack into expressions, for example:

    template<class ...Args>
        void g(Args... args) {
            f(const_cast<const Args*>(&args)...); 
     // const_cast<const Args*>(&args) is the pattern, it expands two packs
     // (Args and args) simultaneously
     
            f(h(args...) + args...); // Nested pack expansion:
       // inner pack expansion is "args...", it is expanded first
       // outer pack expansion is h(E1, E2, E3) + args..., it is expanded
       // second (as h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3)
    }
Taken from: http://en.cppreference.com/w/cpp/language/parameter_pack

So for

    template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }
if you used this like

    overloaded<X, Y, Z> foo;
it would expand to

    struct overloaded : X, Y, Z {
        using X::operator(), Y::operator(), Z::operator();
    } foo;
EDIT: Removed stuff about the function since I was only guessing and Alexeiz answered it. The only thing I’ll add is that you can construct structs like this:

    struct Foo { int x; float y; };
    Foo foo {1, 2.3};
overloaded is constructed from the three lambdas in the same way. I guess the deduction guide is needed because the compiler cannot deduce the template parameters from that initializer form, so the deduction guide tells it to use the types of the lambdas for the types of the template parameters.

More information on deduction guides: http://en.cppreference.com/w/cpp/language/class_template_arg... (specifically, look for “Class template argument deduction of aggregates typically requires deduction guide” in the notes section for an example very like the overloaded one)

alexeiz
> What's even going on there?

The first line defines 'struct overloaded' derived from all of its template parameters. Template parameters are supposed to be functors (lambdas), so we use their operator()'s in 'overloaded'.

The second line defines a template deduction guide for the 'overloaded' constructor, which says: take types from the constructor arguments and use them as the class template parameters.

The idea is to be able to construct 'overloaded' like this:

    overloaded{[](ArgT1 arg){/*lambda1*/},
               [](ArgT2 arg){/*lambda2*/}, ...};
wpdev_63
People who are looking for a modern C++ should take a serious look at Rust. It has all the things you want in modern C++ while getting rid of the cruft of the language.
pjmlp
I do like playing with Rust, but it still does miss a few things from C++.

First of all, cargo does not understand binary dependencies, compiles everything from source.

Second, my only use case for C++ is for native code when integrating system code with Java, .NET, UWP, Swift, Objective-C.

Third, my C++ code can also be used on the GPGPU, with nice debugging tools from all vendors.

There Rust misses the integrated workflow and IDE debugging experience that I have with C++ on those environments.

No doubt Rust will get there, by then I would gladly use it as well, but it still needs to mature a bit more.

Today's Rust release also had many goodies.

alexeiz
I'd love to. But seeing how Rust adopters struggle with something as trivial as a linked list (https://news.ycombinator.com/item?id=16442743), doesn't inspire me.
kllrnohj
On the other hand should it be trivial to implement a linked list? They are an extremely bad data structure for modern CPUs, after all.

Worth noting that it's doubly linked lists specifically that are hard. So cyclic structures.

Retra
Linked lists are easy. People are struggling with safe linked lists, because they think they should be as easy to write as an unsafe ones.

But if you're already willing to use C or C++, you've got no reason to avoid unsafe.

krona
> It has all the things you want in modern C++

Variadic templates?

con22
Rust is your silver bullet?
TillE
Rust doesn't even have default arguments, a feature of C++ since forever. I've written actual code in both languages, and I still find C++ much more usable.
cominous
C++17 is definitely going in the right direction for most applications. But I have the feeling, that the compiler implementations cannot catch up with the modernization speed.

We are using C++ for embedded devices and recognize a steady code bloat with every release since C++11 (especially with C++17) without using any of the new features (with gcc/clang). This is a trust-killer and actually the reason we stay on C++11 for embedded development.

dvfjsdhgfv
Just curious: why did you choose C++ instead of C for embedded? Most shops I know chose C just because of code bloat.
pjmlp
Modern C++ compilers do pretty well on a Commodore 64, let alone many of the typical embedded deployments outside pico-controllers.

CppCon 2016: Jason Turner “Rich Code for Tiny Computers: A Simple Commodore 64 Game in C++17”

https://www.youtube.com/watch?v=zBkNBP00wJE

Most of the time is either religion against C++ or lack of modern tooling, given that most embedded toolchains are stuck with C90 and C++98.

abiox
> religion against C++

possibly. but given the how often i've seen c++ users treat c users like idiot savages or heathens that need conversion ("have you heard the good word of our lord and savior, c++?"), i could understand a negative sentiment.

pjmlp
Maybe if C developers wouldn't be ignoring Lint since 1979, and better type systems, we would be having better conversations.

"Although the first edition of K&R described most of the rules that brought C's type structure to its present form, many programs written in the older, more relaxed style persisted, and so did compilers that tolerated it. To encourage people to pay more attention to the official language rules, to detect legal but suspicious constructions, and to help find interface mismatches undetectable with simple mechanisms for separate compilation, Steve Johnson adapted his pcc compiler to produce lint [Johnson 79b], which scanned a set of files and remarked on dubious constructions."

Dennis M. Ritchie -- https://www.bell-labs.com/usr/dmr/www/chist.html

I would like the stack underlying my computing needs not to look like a Swiss cheese.

And yes, C++ is also not the ultimate solution for that as it is tainted by its C compatibility.

jack_pp
Probably because it is much safer, faster to develop and easier to maintain.
kdickens
If a new feature does not play well with an entrenched object hierarchy, one can as well do a rewrite. This has happened at a shop where I worked and the rewrite was in C, which turned out to have a faster development time and the result was more flexible.

Now, I'm aware that you can restrict yourself to "almost C" in C++, but no one ever seems to be doing that. A litte str::string here, a tiny std::vector there, so exceptions are already in, so why not go all the way.

orbifold
You can write modern C++ with no overhead on a system with just 16kB of scratchpad memory. It is much nicer to use than C (namespaces, auto, templates and lambdas alone).
monocasa
And RAII! That's so nice in an RTOS. Never again forget to drop priorities or reenable interrupts just because you went down a not as well trodden failure path. You can even return the lock_guard, and the caller can continue to do work in the same atomically locked context, but if they choose not to, they just drop the return value and everything works as expected.
CyberDildonics
If there is a modern C++ compiler available for a platform, it is almost irresponsible to use straight C. That doesn't mean that everything needs to be pure idiomatic C++17, but destructors, templates, iteration, operator overloading and move semantics are still indispensable in non trivial programs.

Avoiding bloat and performance hits are trivial in comparison to structural and architectural benefits in the modern language.

zwieback
You're overstating a bit but there's a kernel of truth. However, it depends on your viewpoint. If you have a functional codebase in C and size is an important factor you may be reluctant to trade up. Bloat avoidance is not trivial, in my opinion. Performance is probably less of a factor, most of the additional functionality generated by the C++ compiler has to be written by the C coder in the end.
CyberDildonics
> If you have a functional codebase in C

Rewriting something that already works would be silly of course.

> and size is an important factor you may be reluctant to trade up. Bloat avoidance is not trivial, in my opinion

I don't agree with this in comparison to C. C is still there, but you have destructors and ownership semantics on top of it. Even templates can be used for things like type checking in debug builds.

cominous
I have to admit, that C++ is still not the industry "Go-To" language for embedded. But if you apply modern C++ correctly, there is very few overhead compared to C and the software is much easier to maintain.

The performance of embedded MCU's are continuously rising over the years and that little overhead is buying development speed.

Not to mention smart pointers, templates and constexpr making my life easier.

The only real issue with C++ is, that as soon as you get into serious embedded applications, you have restrictions when it comes to heap usage e.g. in medical devices using the heap is forbidden. So you cant use the STL.

There is a promising embedded STL project, but it's not there yet: https://www.etlcpp.com

Const-me
> So you cant use the STL.

std::array is in there since C++ 11 and it doesn’t use heap.

And/or you can use STL with custom allocators that work without heap. We did something similar developing for Nintendo Wii console. There was a heap but we didn’t want to use it to avoid memory fragmentation. AFAIR we used two arenas (essentially stacks), one very small for temporary data cleaned up at the start of each frame, and a large one cleaned up when a level is unloaded.

However, I don’t have hands on experience developing firmware for medical devices, so I’m not sure it’ll work for them.

None
None
gpderetta
Also, STL is data structures + algorithms. Even if you can't use the datastructures out of the box you might be able to use the algos. Boost.Intrusive provides STL compatibe datastructures with full control of allocation (and more).
AnimalMuppet
The problem for medical devices isn't so much whether custom allocators will work. The problem is whether the FDA will freak out because you're not following industry-best-practice coding guidelines.
gowld
Arenas/Pools/Planks have been industry best-practice for my entire career.
ska
That's really not how it works. The FDA is fundamentally concerned about two things, safety and efficacy. You need a plan to demonstrate the latter, and you need your quality system, SDP, etc. to demonstrate how you approach the former. This is about good engineering practices, not particular implementation techniques.

So you can do things many different ways. If you do say "we do this like X, which is industry standard, just like T, U, and V do" it's a simpler argument than "we do this like Y. Lots of people do X, but here is how we have demonstrated Y is better for us...". But this can be fine too, just possibly more work.

Also worth noting (a) there is no industry-wide best practices agreement (b) there is no FDA wide agreement on what should be done (different device types are reviewed by different panels (c) the FDA doesn't understand software development deeply across it's panels, but it is catching up.

AnimalMuppet
Sure, that's all true. I'm looking at the "simpler argument" part.

It's especially true if you're saying "This new device is just like our previous device, with these few small changes". (I forget what that's called, but you can do a lot less paperwork if that's true.) But if you start doing memory allocations where you never did before, they're probably going to want to apply higher scrutiny to your entire software. That's... painful.

ska
You are possibly thinking about note-to-file (which isn’t quite the process any more, but similar)

But it is worth pointing out, FDA (or other national body) isn’t doing code reviews. They are mostly interested in process , and how you follow it. The place where this really interacts s is hazard analysis, and then it’s just up to you to demonstrate your checks and balances.

paulirwin
Out of curiosity, what is the rationale for not using the heap with medical devices? Resource constraints are one thing but that is not limited to medical nor is that entirely solved with preventing heap use. If it's for runtime safety to avoid raw pointers, has anyone done an analysis to determine if smart pointers (unique_ptr, shared_ptr), combined with diligent static code analysis diagnostics to avoid the kinds of issues Herb raises in the OP video, could reduce the risk to an acceptable level?
ska

   Out of curiosity, what is the rationale for not using the heap with medical devices?
Avoiding heap allocation is not at all a general constraint for medical devices. For certain types of components (think safety-critical real-time sub systems, for example) they are going to be very interested in your hazard analysis and the mitigating approaches to possible issues.

So if there is a way to say: we don't have to worry about [class of error X] because we don't ever do Y, that's a straightforward way to sort out those components. If you have a compelling tech reason to do Y, better start thinking about all the controls you'll put on it.

Think about it this way: What's the worse thing that can happen if your code causes an OOM error? If the answer includes things like "somebody dies if it happens at the wrong time", you'll want to be really careful to prove (prove, not just test out) that can't happen.

TickleSteve
Safety critical software (or even mission critical software) should not be using dynamic allocation for a few reasons.

- Fragmentation.

- Non-deterministic runtime (in the real-time cases).

- Insufficient analysis of worst-case conditions (i.e. you haven't worked out what your worst case RAM usage is, otherwise you would have statically allocated it).

IMO, the worst is the final case as it shows a lack of thoroughness in the design as a whole and brings the rest of the code into suspicion. Fragmentation can be worked around, but not the others.

stephanimal
I have not worked in medical (my experience is in games) but my best guess is its more about reliability and predictability. Using a heap suffers from the fact that you can run out of memory to satisfy a malloc/new request (either due to system memory limit or due to fragmentation).

With static memory techniques you can "prove" the system has enough memory to work in all modes, i.e. device consumes 20 readings per second, keeps them in a ring buffer backed by a static fixed array, that buffer is large enough to satisfy processing rate.

zwieback
When I was using C++ for embedded, which is admittedly over 15 years ago, I had to switch off RTTI and exception handling to get compact binaries. Basically just using classes and surface language features. We did use templates but only very selectively.

Is it still possible today to pare down the compiler output like that? I imagine a lot of modern C++ just doesn't work unless everything is enabled.

pjmlp
Yes it is possible.

Check this talk about fitting C++17 on a C64.

CppCon 2016: Jason Turner “Rich Code for Tiny Computers: A Simple Commodore 64 Game in C++17”

https://www.youtube.com/watch?v=zBkNBP00wJE

Also be aware that embedded devices like Arduino and Cortex-M (with Mbed OS) do use C++ toolchains.

lmitchell
Shameless plug: At EA we've open-sourced our C++ standard library implementation that focuses on games. While it's not necessarily focused on embedded development, you might be interested in checking it out - it provides a number of fixed_* containers (fixed_vector, fixed_set, etc) which can be configured to use stack allocations only, among some other things you might find interesting :)

https://github.com/electronicarts/EASTL

(In case it wasn't obvious, disclaimer: I work at EA, and frequently do work on EASTL, though I'm not the primary maintainer.)

w8rbt
Good that you can specify -std=c++11 to prevent the bloat. The more recent 14 and 17 standards don't seem as groundbreaking and practical (both at the same time) as 11 was and, as you note, do seem to bring bloat.
dkersten
I use C++17 in my toy code (which is for a mixture of fun and learning modern C++, so...) and the main reasons (besides to learn) I use C++17 over C++11 is std::variant and (from C++14) make_unique. C++11 has a lot of compelling stuff but 14 and 17 seem to be relatively minor improvements over 11. It’s a pity they introduce bloat... definitely makes them much less compelling for use cases where it matters.

Nested namespace definitions and structured binding declarations are also nice.

stochastic_monk
Which features do you see causing problems? If constexpr has been fantastic, and outside of that, I mostly benefit from broader metaprogramming power.
monocasa
Are you pulling in the standard library, or is this in just normal code?
snowAbstraction
I would be really interested if you can shed a little light on the following related questions:

1. Do you see the bloat even if you don't use post C++11 features but compile using the C++17 standard?

2. Do you think it is mostly the compiler that is causing the bloat alone? Or is it stuff from the standard library header files that some how gets linked in (and are not used or needed by your software)?

bluGill
I haven't tried c++17 year, but c++98->c++11 did bring about code bloat. However even though we were building the same code base for two different systems, one without a C++11 compiler (thus we could not use c++11 features): it is incorrect to say we were not using C++11.

Just turning on C++11 in the compiler brings move to all standard library containers. The header files were not just "somehow" linked in and not used, the additional code was used in many places behind the scene. I haven't done benchmarks on my code, but the general report of those who have is that in exchange for the extra binary size the code runtime was 5% faster. For most people these days that expense is worth it.

thekingofh
What do you mean by code bloat?
bluGill
Binary size after compiled. Also time to build - just turning on C++11 more than doubles the time to run the processor. (I hope C++20 modules fixes this)
cominous
1. Do you see the bloat even if you don't use post C++11 features but compile using the C++17 standard?

Yes, actually I tried using various C++ snippets and even reported that to the GCC compiler team. It happens with simple stuff like std::string and std::vector. The response was something like, that there really seems to be a bloat, but no performance impact and I guess most users outside of embedded don't care too much about the size of the compiled binary.

2. Do you think it is mostly the compiler that is causing the bloat alone? Or is it stuff from the standard library header files that some how gets linked in (and are not used or needed by your software)?

That's actually a very good question I cant give an answer to - meaning I haven't looked specifically into that.

As C++17 came to GCC I played with the compiler explorer and observed this by just switching gcc/clang version and -std flag. Actually, you can try it yourself: https://godbolt.org/

conradev
To answer #2, you can try enabling LTO and see if that helps with binary size
jchb
Did you try with optimisations enabled? Here is a (trivial) program comparing gcc output between -std=c++14 and -std=c++17: https://godbolt.org/g/VLDhYf. Note that the output code size with -std=c++17 is significantly larger without optimisations (default), but it is identical with optimisations turned on!
cominous
I used -Os as for any embedded code where I don't care too much about the performance and rather need a compact binary.

I would love to post my code snippets from back then, but Im not home currently and the time Im back home I guess nobody will care about this anymore :D. Maybe I put it into an article.

alexeiz
-Os is pretty much useless. For comparison, I compiled my (reasonably sized) project with -Os and got a 12MB statically linked binary. But with -O3, the binary size is only 4.2MB. Not only -O3 produces faster code but it's also smaller.
msla
In modern systems with caches and a typical penalty for going out to RAM, is it still possible for larger code to be faster?
gpderetta
Yes. Caches actually work. And work quite well for code. Of course there are pathological cases.

Also, static size does not mean anything. The only thing that matter is the dynamic size (i.e. the instructions that are actually fetched at runtime: code that isn't run or run rarely doesn't matter (then again, such code is a prime candidate to be compiled with -Os).

gpderetta
If I had to guess, -Os prevents most inlining and in inlining is is pretty much required to remove most of the pure compile time abstraction and indirection that is used even on most trivial C++ libraries. Very likely libstdc++ make significant use of that.

The intermediate inline stages greatly expand the code size, until the point level where all the abstraction can be compiled away. I guess that -Os simply settles for a local optimum and gives up inlining early.

HN Theater is an independent project and is not operated by Y Combinator or any of the video hosting platforms linked to on this site.
~ yaj@
;laksdfhjdhksalkfj more things
yahnd.com ~ Privacy Policy ~
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.