Learn Modern C++
learnmoderncpp.com
learnmoderncpp.com
cout << "Hello, World!" << '\n';
But the most "modern" way to write to the console is: std::println("Hello World!");I imagine targeting C++17 or 20 for 'modern' would be practical enough considering thats what you are most likely to run into in a professional setting.
std::println is, yes.
> I wonder how available this is within compilers
https://en.cppreference.com/w/cpp/compiler_support says clang, gcc, and msvc all support it, though I don't know how recent those versions are off the top of my head.
In my understanding, with this specific feature, if you want a polyfill for older compilers, or to use some more cutting-edge features that haven't been standardized yet, https://github.com/fmtlib/fmt is available to you.
Where did you find it? Search by println yields no results.
Formatted output library <print>gcc 14 has not yet been released; going by past years, 14.1 should come out around the start of May (2024). clang 17 was released September (2023); note that you need to use libc++ (stlib=libc++), not libstdc++. VS 2022 17.7 has been out since August.
So iostreams were a mistake, they shouldn’t have happened in the first place. And given that they happened, std::format should have been added 20 years ago, or at least in C++11.
The problem is that many professional shops never used iostreams, and instead hand rolled their own format library. Now, they don’t really care about std::format, and probably will never use it, because it would involve migrating off of their hand rolled solution which is working great.
On the other hand, for small shops and hobbyists that don’t have the resources to reimplement std, this is a huge quality of life improvement. Meanwhile, more modern languages like Rust are encroaching.
And this is all just about how to print “Hello World!”. This situation is really emblematic of the challenges faced by the C++ committee. It is really impossible to please everyone, or even a majority, since the community is so fractured.
I was reading a lot of c++ core guidelines and modern c++ blogs and books while writing a little game a couple of years ago and std::println was definitely not the common suggestion for modern c++. I see it's probably because it wasn't even available at the time. Now I feel like I'd have to review everything that I thought was modern two years ago to confirm that it is still the modern way.
In C you control this with setvbuf, and of course, in C++ with iostreams it's a huge mess of rdbuf spaghetti and probably involving std::ios_base::sync_with_stdio as well.
You can observe this by printing out tens of thousands of lines with vs without explicit flushing, there will be a big performance difference.
I've tested that I'm correct on glibc, where the behavior is documented here: https://www.gnu.org/software/libc/manual/html_node/Buffering...
I can't remember using an alternative platform that behaves differently.
A better test than the one you ran is to look at the resulting syscalls from a loop of `fputs("a line of output.\n", stdout);`, vs. `fputs("a bit of output. ", stdout);`. Buffering accumulates strings in memory before an eventual write syscall, so looking at syscalls is an easy way to see the difference: a write per-'\n' means line buffering.
Compare the syscalls of both using strace. I did so, and when stdout is to the terminal, I see lots of calls to `write(1, "a line of output\n", 17)` compared to `write(1, "a bit of output. a bit of output"..., 1024)`. (To confirm, manually insert an `fflush(stdout);` in the "a bit of output. " version, and you'll see lots of `write(1, "a bit of output. ", 17)`)
Then compare both when redirecting stdout to a file, and you'll see both cases make large write-s of 4KB. Only adding explicit fflush-es will make either version go back to small write-s of 17B.
Compiled the trivial test programs with `cc -O2 ./main.c -o ./main`
So I'm correct on those targets as well.
Looking for write syscalls is not a valid way to figure out where buffering is happening.
Write by default is famously buffered in Linux, and stdio/iostresm functions map closely to it. So, if you call these C functions N times, you'll see write being called N times.
Flushing calls fsync or some ioctl depending on the file descriptor is, iirc.
I meant to reply much sooner, with more info from empirical tests. But now I'm happy to leave it at just the write stuff and the note about the meaning of stdio/iostream "flushing".
I think you have an incorrect or esoteric understanding of what the "buffering" in question is, but it doesn't matter to me. My argument is about what syscalls happen, and I don't care if you disagree with the (I think, standard) descriptor/terminology I'm using.
Probably because it has only been available since C++23: https://en.cppreference.com/w/cpp/io/println.
The only significant difference I see is that std::println automatically includes the newline which may or may not be desired behavior.
Accepted paper here: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p20...
> The proposed std::print function improves usability, avoids allocating a temporary std::string object and calling operator<< which performs formatted I/O on text that is already formatted. The number of function calls is reduced to one which, together with std::vformat-like type erasure, results in much smaller binary code (see § 13 Binary code).
Additionally,
> Another problem is formatting of Unicode text:
> std::cout << "Привет, κόσμος!";
> If the source and execution encoding is UTF-8 this will produce the expected output on most GNU/Linux and macOS systems. Unfortunately on Windows it is almost guaranteed to produce mojibake despite the fact that the system is fully capable of printing Unicode
https://stackoverflow.com/a/4854358/24817 is the canonical explanation for why >> and <<, apparently. Nearby, this is also mentioned:
> C’s printf family of functions is an effective and often convenient I/O mechanism. It is not, however, type safe or extensible to user− defined types (classes). Consequently, I started looking for a type safe, terse, extensible, and efficient alternative to the printf family. Part of the inspiration came from the last page and a half of the Ada Rationale [Ichbiah,1979], which is an argument that you cannot have a terse and type− safe I/O library without special language features to support it. I took that as a challenge. The result was the stream I/O library that was first implemented in 1984 and presented in [Stroustrup,1985].
and
> The stream I/O facility is implemented exclusively using language features available to every C++ programmer. Like C, C++ does not have any I/O facilities built into the language.
The language simply didn't have the advanced tools that ended up being used in more modern solutions yet. It was too early.
As usual, C++'s priorities are wrong for anyone who's not writing an OS, web browser, game engine, or otherwise rare type of project.
I think calling formatted printing “cognitive load” is a bit much though. If anything this is easier to understand and use than stdio and iostreams. It’s closer to other languages in this area. I probably would have voted yes on this if I were on the relevant committee.
1. Incompatible with dynamically-generated format strings, in which the order of arguments is different. Example:
std::cout << month << '/' << day << '/' << year
... but you now want to adapt that for a non-US locale, e.g. a European one where it's std::cout << day << '/' << month << '/' << year
with iostreams, you have to change the code. With C-style printf, you don't (but it's not typeafe and not flexible. But with std::print it could be: std::printf(my_format_str, day, month, year);
and `my_format_str` could be either "{0}/{1}/{2}" or "{1}/{0}/{2}".2. The string allocations which other have mentioned.
3. A lot more typing that is easy to confuse: " << ".
4. The iostreams implementation is super-baroque, with buffer classes nobody uses.
and maybe I'm forgetting things.
Std:seems std:confusing
You can answer your own question if you google "println cppreference".
More seriously, though - the language is changing gradually: Features are introduced (and rarely, deprecated); and the missing bits of the standard library are added. C++11 was a _very_ significant change - but not to how you print output. C++20 and C++23 introduced `std::format` and `std::print`.
Many people can already write "import <std>;" instead of many lines of "#include <...>".
Type detection, formatting options, positional-arguments, custom formatters for custom types and probably more are all supported.
Most existing code probably uses output streams with << operators, it’s good to know what that does.
Indeed, as mentioned [0] at cppcon 2022 :)
[0]: https://youtu.be/eD-ceG-oByA?list=PLHTh1InhhwT6c2JNtUiJkaH8Y...
As for the code duplication, that's partially alleviated by a language feature introduced in C++23:
deducing `this` https://youtu.be/eD-ceG-oByA?si=L5XIOpsQLYVT-laP&t=1045
and another part of it is addressed by adhering to the "rule of zero":
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines...
struct Foo
{
int Bar(const Baz& b1, const Baz& b2) const;
// ...
};
The compiler can't reasonably do anything helpful with it _because_ of the above rules. Once you peek behind that curtain, it kind of crumbles.> As for the code duplication, that's partially alleviated by a language feature introduced in C++23:
Agreed about deducing this, it's a bit awkward but solves a real pain point.
Well, you can delegate work to a function with `__restrict` on its parameters, and then the compiler _can_ help you. Though you would probably need to check that the _restrict_ is valid. Still, classes should probably not do the performance heavy-lifting.
... but then again on the other hand, `__restrict` is a compiler intrinsic, not really part of C++. That's a gaping hole right there if you ask me.
Func( foo, foo);
I don't want foo to be loaded twice.Theres also the `const` on the function itself. If I do:
struct Foo {
int GetVal() const {
return Val;
}
int Val
};
void Bar(const Foo& f){
int val1 = f.GetVal();
int val2 = f.GetVal();
assert(val1 == val2);
}
This is not guaranteed for a variety of reasons. This means that there are many optimisations that are just unavailable because of const, and there are guarantees that on first glance you expect to be true, but arent.I understand why, we don't need to go into it, but its a mess that const doesn't actually mean constant.
It just means you've opted into coloured functions [0]. It's not quite as painful as async functions in js, but all the same arguments apply.
[0] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... t
Change: Previously valid identifiers containing characters not present in UAX #44 properties XID_Start or XID_Continue, or not in Normalization Form C, are now rejected.
Rationale: Prevent confusing characters in identifiers. Requiring normalization of names ensures consistent linker behavior.
Effect on original feature: Some identifiers are no longer well-formed. ⴵ ⵛ ꘜ ⵣ ꕤ ꖜ
ꘖ ꧮ ⴲ ꘖ Ⰴ Ⰺhttps://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
Compilers, games, all of the backends of the current LLM revolution, control systems for interesting hardware.
You would sabotage yourself if you choose not to learn how to use a possibly suboptimal tool that is an industry standard.
I'm currently doing a C++ audio plugin with the Juce framework.
This website has been a good resource, alongside https://www.learncpp.com
But I was actually close to give up before using those two things:
- https://github.com/nlohmann/json : my plugin use a json api backend and the Juce json implementation is atrocious (apparently because of being born with a previous C++ version), but this library is GREAT.
- ChatGPT 4. I'm not sure I would have "succeeded" without it, at least not in a reasonable time frame. ChatGPT 3.5 is slow and does not give good results for my use case but 4 is impressive. And I use in a very dumb way, just posing question in the web UI. I probably could have it directly in MSVC?
Also I must say, for all its flaws, I have a renewed appreciation for doing UI on the web ;)
I’d love to be in a position to work on a new project using Rust or Haskell instead.
Are string literals only 8 bit chars aka no real Unicode? The description danced around it so much it sounds like obfuscation
[1] https://en.cppreference.com/w/c/language/string_literal
[2] https://en.cppreference.com/w/cpp/language/string_literal
Today there are so many IDE's and online coding environments, godbolt, Swift playgrounds, golang interactive tutorials, etc. It would be lovely to see a C++ tutorial join that trend.
I find the tutorial unsettling from the first words: `original, self-contained`. My first step in writing a tutorial would be to link the current state of the art, and the last would be a section pointing users to further resources, if my goal were not to trap readers but to help them. (Originality is a tall claim as the internet spawns LLMs.)
Without any orientation to the landscape, the tutorial proceeds step-by-step with topologically-sorted vignettes that explain themselves verbosely.
hello-world is prefixed by a long explanation why people start with hello-world, and following by 16 paragraphs of (to me) excruciating hand-holding and suggested experiments, without re-quoting the code. It's like pair-programming with someone who tells you what to type.
The original K&R book was breathtakingly brief, mainly just showing you how to do things. Effective C++ neatly crystallized specific problems and solutions. Both benefited most by what they left out.
Outside of an interactive tutorial for newbie's, what's needed for "modern" C++ is an origin story for each feature: what motivated it, how backwards-compatibility shaped it, what design decisions were made, how well it has been implemented and used -- ideally with bonus comparisons how Rust and Swift and Go managed the same issues. I think that would help people remember the complex issues and the syntax, and how to use it.
To me most of the discussion from the C++ originators is more expository than explanatory: `for each opinion, explain in detail with cross-references to other opinions` - the political template. But readers only need to know the distinctions that make a difference in when and how they use a feature.
Actual users are busy and paid only to get things done, not sling words. Users who value their time will pay for a good resource.
C++ posts on HN generate drama because of C++ People bickering with C++ People.
Do I contradict myself?
Very well then I contradict myself,
(I am large, I contain multitudes.)
As someone familiar with nearly a dozen scripting languages, I keep trying for something low level like C++, but it seems like these languages are impenetrable at times. It's hard enough to learn the machine stuff and manual memory management, but having the syntax change more often than a toddler's favorite color just makes it all seem impossible.
An actual engineer or carpenter doesn't master every conceivable tool before beginning their work, they can't, there's too many.
Say what you will about C++ programmers, but at least they are free from the burden of trying to use a perfect language and can focus on more important things.
As a C++ dev - If you don’t need C++ then choose whatever other modern language.
We keep up with C++ because of specific constraints other languages don’t fill.
People use Java because they work in a Java shop. Similarly, COBOL.
I would say most programmers, in any language, are free from that burden: once they picked their language they can focus on those important things.
After 35 years of C++ on and off, I have pretty much given up it. I don't have the mental energy to deal with its complexity anymore. I will deal with parts of C++11 or C++14, mostly so that I can read other people's Arduino code.
I think I would rather learn a new language (Rust?) than deal with the hairball that is called "modern C++".
The vast majority of programmers' lives is spent reading code. We need to be able to interpret, repair and extend the work of others.
I often hear this defense of C++, that you only need to know certain parts and the rest are for "library writers". This is an absurd argument, but not exactly surprising from the core C++ cult(and I say this with 26 years as a C/C++ dev). It's almost as if there are two separate camps, one writing and maintaining production software, and another group which only works on language internals and is becoming further divorced from reality.
But many offer better ways to do familiar things, that make the overall programming experience more pleasant. Use those as soon as the compiler you use gets support for them. Often they make bugs harder to write, and correct code easier to write. Now we can write
for (auto const& [key, value] : map) { ... }
instead of for (Map::const_iterator_type it = map.cbegin; it != map.cend; ++it) {
Map::key_type const& key = it->first;
Map::mapped_type const& value = it->second;
...
}
It is just better in every way.Another example is extension of "constexpr" to more and more of the language; each eliminates the need for some range of template metaprogramming. Nowadays you can ignore almost everything written about template metaprogramming, because there are good, straightforward ways to achieve its aims with ordinary language features.
I learn something new all the time when I explore new codebases or even less beaten corners of existing codebases. Only a tiny fraction of the time it is a new facet of the language.
And C++ is explicitly a multi-paradigm language so there can be affordances for multiple approaches. You can even use it as an OOP language if you want!
As far as syntax changes go, mostly they are in the direction of simplification and/or unification intended to make programming easier in the long run. So, for example, you can now in many cases write template generics without the elaborate template syntax.
But the C++ is also pretty unwilling to break old code, so old syntax and obsolete semantics are still available should you so choose. This is confusing to people who come to the language later on.
(Note that above I wrote “intended to make programming easier”. The case I cited of generics does make some things easier for me, but of course de gustibus and all that.)
You can write an incorrect program in any language. Some kind of self-discipline is important and necessary.
Try: "g++ -std=c++20 lb.cc && ./a.out iodhuamplrnc words".
#include <array>
#include <bit> // popcount
#include <iostream>
#include <utility>
#include <string_view>
#include <vector>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct Word {
std::string_view str;
unsigned bits{};
static unsigned index(unsigned c) { return c - 'a'; }; // 'a'..'z' -> 0..25; others -> >25
Word(std::string_view s, unsigned accept, long long sides) : str(s) {
long long prev_side{-1}, side; // Values are 0..3. First letter gets a free pass.
for (char c: s) {
const auto ix = index(c);
if (ix < 26 && (accept & (1u << ix)) && (side = ((sides >> 2*ix) & 0x3)) != prev_side) {
bits |= (1u << ix), prev_side = side;
} else { str = {}; return; }}}
};
int main(int ac, char** av) {
auto usage = [av](){ std::cerr << "usage: " << av[0] << " abcdefghijkl [<wordlist>]\n"; };
if (!(ac == 2 || ac == 3)) { return usage(), 1; }
const std::array prefixes = { "", "/usr/share/dict/", "/usr/share/dict/american-english-" };
const auto name = (ac == 3) ? av[2] : prefixes.back() + std::string{"large"};
std::string_view file;
for (auto pfx = 0u; pfx != prefixes.size(); ++pfx) {
int fd = ::open((prefixes[pfx] + name).c_str(), O_RDONLY);
std::size_t file_size = ::lseek(fd, 0, SEEK_END);
void const* addr = ::mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0);
if (addr != MAP_FAILED) { file = {static_cast<char const*>(addr), file_size}; break; }
}
if (file.empty()) { return std::cerr << av[0] << ": cannot read " << name << '\n', 3; }
auto target_sum = 0u; auto sides_sum = 0ll;
for (unsigned i{}, ix{}; i < 12 && (ix = Word::index(av[1][i])) < 26; ++i)
{ target_sum |= (1u << ix), sides_sum |= (i/3ll << 2*ix); }
if (std::popcount(target_sum) != 12 || av[1][12] != '\0') { return usage(), 3; }
const auto target = target_sum; const auto sides = sides_sum;
const auto candidates = [target,sides,file] { std::array<std::vector<Word>, 26> candidates;
for (std::string_view in = file, next; !(next = in.substr(0, in.find('\n'))).empty();
in.remove_prefix(next.size() + (next.size() < in.size()))) { // Skip past '\n' if present
if (const Word word{next, target, sides}; word.str.size() >= 3)
{ candidates.at(Word::index(word.str.front())).push_back(word); }
}
return candidates;
}();
std::array<std::vector<std::array<std::string_view, 2>>, 100> answers; // radix sort by length
for (auto const& firsts: candidates) for (const auto first: firsts) {
for (const auto second: candidates.at(Word::index(first.str.back()))) {
if ((first.bits | second.bits) == target) {
answers.at(first.str.size() + second.str.size()).push_back({first.str, second.str}); }}
}
for (auto const& of_len_N: answers) for (const auto answer: of_len_N)
{ std::cout << answer[0] << ' ' << answer[1] << '\n'; }
}std::string_view lets you have some of the bugs that are caused by misusing pointers.
I have seen them used in the PIMPL idiom, which is a rather controlled case.
Instead, start using the most modern language your compiler supports. Everything you write will come much easier, with much less opportunity for mistakes, and be much more fun. You will hardly ever find yourself using pointers, and never allocate or free memory.
I would be very careful with that approach. C is broken beyond repair, and C++ didn't fix it. The only sane way forward is a clean break with a cleaner syntax (or at least one that is truly context free, dammit), and fewer undefined behaviours. Much fewer.
And of course, GC and RC aren't fixes, they can't apply in the performance constrained settings C and C++ typically are used for (tiny embedded chips, video games, video encoding…).
Also there's no way I'll even look at a new language without some form of generics. They're just too damn useful. Sure we could try the Go approach and special case generics for a few core data structures, but I believe a general purpose language needs a way to add custom ones. Heck, even Go fixed its mistakes and added generics after all.
But I (mistakenly) thought we were talking about designing a language. And for this I have a point: coming up with something like C in 2023 would be unacceptable. We know better now.
Sorry about the non-sequitur.
Herb Sutter has been working on it for awhile. My only exposure to it has been watching this recent talk:
The answer is: Effectively not hard, because:
1. When you write complicated software - especially with older-standard-version C++ - you often have the thought of "Oh, if I could only do X more easily" or "wouldn't it be good if we could skip the tedious coding to make the compiler do Y?" - and then you either grit your teeth and write something ugly which works. But then, a few years later, you hear it mentioned that X' or Y' have been standardized, and then you know "Oh, good, now I can do that more easily".
2. Stuff that you _don't_ know about, that gets introduced - you typically don't have to know about. Either it's not relevant to you, or it is, but you're just writing old-style code.
3. The "junk" gradually makes you be able to write the code you want more easily. So, unless you want to do deep guru things - you can gradually even _forget_ things you used to know how to do, since you no longer need to know about them.
I think that Ken Thompson nailed it on C++: https://en.wikiquote.org/wiki/Ken_Thompson#%22Coders_At_Work...
Also Linus Torvalds is a huge detractor of C++: http://harmful.cat-v.org/software/c++/linus
But in the end everyone should pick whatever programming language feel comfortable with. Sometimes if you're pursuing a career in certain directions (e.g: game development) you have literally no choice but to knee down and learn whatever the industry is pushing to use.
The clang++ compiler, much more modern and built on the LLVM inftrastrucure, has much better error reporting. Occasionally, in desperation, I've compiled something with clang++ just to see the error message in cases where g++ was just too unhelpful.
Linux Torvalds has not looked at C++ since the '90s. C++ is a wholly different language now. The experience coding in it now much more fun, and it is easy now to write code that works the first time it is run (as is also seen with Rust).
Take TypeScript, for example: if you bugger up a type definition or function call in one file, you'll have to wade through the errors of everything depending on it. Oftentimes you can go from 30 errors to just 2 or 3 with one simple change.
The alternative is they fail at the first error but you don't get the root cause and end up fixing shit one by one if the problem isn't obvious.
Especially with a new project in an unfamiliar language - start small and compile small, incremental changes. You won't end up with something you can't fix, which can't be said for installing dozens of dependencies up front and hoping for the best.
Programmers like really neat logical explanations for everything, but the real world is much messier. People do not acquire human language skills by learning a formal grammar, learning all the edge cases, and then finally speaking: they attempt to communicate as best they understand, and then refine their knowledge over time.
The same works with computer languages. Not just C++. You don't generally learn every single detail before you do meaningful work. You just kinda try stuff and figure things out. Sure, you may practice specific skills, but that's just not the primary method of learning. And even in those cases, you don't necessarily learn everything before you return to practicing what you've learned: it's a process where the two activities feed into each other.
You mostly just go "damn I wish there was a better way to do X" and then find out there's a paper someone wrote, and then you pay attention to its progress. Or you just keep doing what you're doing, and at some point, a blog post comes out that's a summary of what's new, and you go "ooh that sounds interesting, I should look into that." But for every feature like that, there's also features which you go "ehh I don't need to care." And that's generally fine.
> having the syntax change more often than a toddler's favorite color
No real syntax is different here, this is a function call like any other. And C++ changes on a three year timescale. Many scripting languages release new versions far faster. It's just stuff that's less familiar to you, and so it seems harder, but it's learnable.
> It's hard enough to learn the machine stuff and manual memory management
This is the bigger struggle with learning, moreso than "syntax changes." If you haven't done lower level stuff before, you also have to recon with that while learning. That does make things harder the first time you learn a new paradigm, just like if you'd never used a functional language before, you'd be learning about those concepts as well as the syntax and specific language semantics.
Every new Standard has some features few people will use. Those are easy to identify and ignore if they don't solve a problem you have.
Isn’t this the epitome of C++, though? It literally is the LTS language
I never said you had to. The question that was asked is "how hard is it to" not "should I."
> A professional computer language should always have a ”LTS” mode where you can compile a decade old code without modifications. Otherwise the language is a toy.
I don't personally feel that this is a useful distinction because it means the vast majority of language implementations are a "toy." Heck, C++ might be one of the few non-dead languages where this is true! You can add new things while staying backwards compatible. "I want to keep up with the language" and "I want ten year old code to work" isn't inherently at odds.
C++ is a weird combination of pragmatic features, close to the metal performance (which you can junk if you don’t actually understand what is happening on machine kevel), universally abysmal tooling (compared to other current languages), huge ecosystem of super-usefull super-nontrivial libraries… a combination of super good and super bad.
If you don’t need the super good stuff (which you don’t need unless you know you need it) all you are left with is the bad stuff.
The C++11 is okay. C++ committee is a weird combination of clown-car non-savant idiots and super talented pros.
- No standard / user friendly tooling: there is no package manager, getting dependencies "as easy as copying a header from this github repo" is messy, is a liability, generates toil, etc.
- The language is very complex, i.e. there are 7 different ways just to declare a variable (I'm not even discussing templates, optionals and what not) and more often than not C++ projects spans years or decades, there is a natural mix of styles from people across different generations on what is "modern" and it's a sore to my eyes. At every release rarely things are removed from the language, but there is always something added.
At every company, the subset of what features are reasonable to use, understood and well maintained can change drastically. That might be a company problem, not a language one, but it's something sistematic enough that I would say it's a language thing.
- Every language has its gotchas, but with beginners interested in using fancy features of the language, the surface area is just too big.
I've read once on the blog of a former member of the C++ technical steering committee, that at his best, when actively working with the committee and focused on this, he maybe knew 50% of the language spec. What are the odds of the common folk knowing the pitfalls and good practices of the language and that staying relevant in 5-10 years?
Even though C++ is the language programmed the most in my career and I can appreciate how nice it is at the right conditions, I've come to accept that the tenets of the language, the way the TSC sees it (stability, backward compatibility, performance) no longer resonate with me and I come to appreciate the consistency, simplicity and ease of use of other projects, there is a bigger world out there and the pond of C++ issues is a place I would rather leave behind.
This isn't relevant. The standard is for compiler writers and library writers who use advanced meta-programming.
"What are the odds of the common folk knowing the pitfalls and good practices of the language and that staying relevant in 5-10 years?"
That depends. Some pitfalls just disappear like std::auto_ptr but I doubt anyone complains. Big changes came with c++11 and c++20, that is once in 10 years. While c++11 added move semantics and new initialization syntax that are somewhat complicated and hard to ignore, c++20's additions only make you glad that your life has become easier.
It is. It is a metric of how ungodly complex the language is. You can infer by this that having a comprehensive mental model of the language is difficult, do's and don'ts are all over the place and even a thing "simple" as a cast can lead to UB, but this flies under the radar of a lot of developers. You don't need to be a compiler engineer to make good use of such information.
"That depends. Some pitfalls just disappear like std::auto_ptr but I doubt anyone complains. Big changes came with c++11 and c++20, that is once in 10 years. While c++11 added move semantics and new initialization syntax that are somewhat complicated and hard to ignore, c++20's additions only make you glad that your life has become easier."
True, but it takes time to be proeficient with the changesets, for the projects to be transitioned to newer versions, if they are at all, and then to map what is valid in which version if there are reworks everywhere (e.g. I'm involved with some that are still c++14, "Do we have structured bindings available here? Oh, no, too bad.").
I appreciate thing gettings better/easier, but c++ seems to be all over the place (just checked the compiler support for c++20 and there is still partial support for some features in major compilers, even though work on c++23 has already started).
"do's and don'ts are all over the place and even a thing "simple" as a cast can lead to UB"
And most of these things were present in the first revision of the C++ standard or even in C.
"You don't need to be a compiler engineer to make good use of such information."
Why do you think that a programmer has to learn C++ by reading the standard? One does need to have a good mental model of what's happening but it's enough to have a much much simpler mental model than the standard.
"and then to map what is valid in which version"
That's where skills acquired 5-10 years ago are most relevant)
"Why do you think that a programmer has to learn C++ by reading the standard?"
If your application seg faults due to an ill type deduction caused by `auto`, why did it fail? What kind of type deductions auto covers? Experience by trial and error will hardly help you here.
End user developers will acquire that knowledge probably by reading digested material from experts either focused on language tidbits or working closely to the standard (Effective C++, Effective Modern C++, blogs, etc.), the root source is still the spec, the more complex the spec is, by transitivity, the more complex is anything that comes out of it.
I guess the bottomline is that we agree the language is complex and I'll leave it at that.
Yeah, that's why so many languages don't have spec at all.
C++ completed a cycle of related versions with C++11/14/17 so a lot of firms have settled on C++17 for now. C++20 introduced a LOT of new language features, some of which are still being explored and implemented and is still considered bleeding edge. C++23 has been finalised but is still going through the final ratification as far as I know.
It is absolutely fine to be a few years behind the curve, as unlike other languages (typically controlled by one company) where a new version ships at the same time as compiler/library support, for C++ the spec is released first and then the many different vendors implement it (in some cases approved features are implemented ahead of release - typically Microsoft gets library features implemented quick while other compilers get language features quick. Modules are the notable exception to this rule).
(not my favorite)
struct foo;
struct foo *foo_alloc();
void foo_do_something(struct foo *p, ...);
This gives a simple to understand API with clearly defined boundary, has a stable ABI, preserves fast compilation times, avoids instruction bloat, etc. One could do this in C++ too, but what would be the point of using C++ then... struct private_impl_type;
std::unique_ptr<private_impl_type> impl;
If you choose this, you trade off optimization opportunities. Your choice.