I'm glad this hasn't turned into (so far) the usual "c++ is dumb" flame fest.
I've really enjoyed programming in c++17 for the last three years (I had the luxury of starting from an empty buffer) and find the language pretty expressive. If you are able to use it as a "new" language, ignoring its C roots and features that exist pretty much just for back compatibility, it's really quite expressive, powerful, and orthogonal.
I'm actually pretty glad the c++ committee has been willing to acknowledge and deprecate mistakes (e.g. auto_ptr), to squeeze out special cases (e.g. comma in subscripting) and to attempt to maintain generality. Conservatism on things like the graphics standard is reducing the chance of the auto_ptr or std::map mistakes.
> it's really quite expressive, powerful, and orthogonal
Expressive and powerful are definitely true, but I'm not so sure about 'orthogonal'.
Most C++ features seem to depend heavily on each other, and library decisions can impose restrictions/boilerplate on application code in many areas.
Especially as the language has evolved, features like exceptions, destructors, copy and move constructors, and operator overloading have become more and more indispensable. Template functions are still required for any kind of variance (e.g. you still can't have a non-template function that works on a const std::shared_ptr<const *base_type> or any derived type).
None of these is unbearable, but I do believe that even the modern subset of C++ is still the least orthogonal mainstream language in use (and definitely the most complicated by far). To be fair, it's also likely the most expressive mainstream language too, and still the most performant.
Oops, I picked a bad example it seems... Apparently `shared_ptr` does some magic to simulate covariance through type conversion. It still breaks in other cases (a function which returns a `shared_ptr<derived>` can't override a function which returns a `shared_ptr<base>`, unlike with derived* and base* ), but it still probably covers most real-world cases.
If I had stuck with std::vector my point would have been better. Even though theoretically a `const std::vector<derived* >` IS-A `const std::vector<base* >`, that is, a read-only view of a collection of derived elements behaves the same as a read-only view of a collection of base elements, you can't pass the first to a function expecting the second in C++, unless you make your own function a template.
In that case, you have a point. The closest you can get to covariance and contravariance is simulating it with implicit conversion operators/constructors (which then often need to be templates).
But I don't see how this could be implemented without completely changing large parts of the language.
In the one case of covariance that actually exists, overriding `virtual base* factory()` with `derived* factory() override`, the overridden function knows that the function it overrides returns a `base* `, so it can just return a `base* `, too, and callers through the subclass pointer know that they can adjust the return value back to a `derived* `.
But even the closest cousin, argument contravariance (i.e. overriding `virtual foo(derived* )` with `foo(base* ) override`) doesn't seem feasible. Callers through the base pointer will pass a `derived* `, so the overridden function has to expect to be passed a `derived* `, and there is no adjustment that can be made to a `base* ` to turn it into a valid `derived* ` statically.
The vector example is even more impossible. Since the function expects all members of the `std::vector<base* > const` argument to be `base* `, a caller with a `std::vector<derived* >` would have to allocate a new vector and adjust each individual pointer, which would be a terrible hidden runtime cost. (If they had added a constructor to accept a range to complement the one that takes an iterator pair, that's exactly what would have happened, though.)
> Since the function expects all members of the `std::vector<base* > const` argument to be `base* `, a caller with a `std::vector<derived* >` would have to allocate a new vector and adjust each individual pointer, which would be a terrible hidden runtime cost.
I don't think I understand your point. A function which expects a `base* ` can already be passed a `derived* ` without any problems. Doing the same for a collection of `base* ` to a collection of `derived* ` is not fundamentally different, but it does come with significant complications. For one, the language would have to be sure that the collection is read-only, since a function which wants to add something to the collection can't be called with a collection of a different type.
More significantly, the language itself has no idea that `std::vector<T>` is a collection of Ts, while `std::function<T>` is not a collection. In principle, it may be possible that it could look at all of the members and decide each template argument's variance. Even if that is theoretically possible (I'm not sure it is), it is certainly far too complicated to be a useful feature.
That leaves us with the option of some kind of annotation for variance in template parameters (or at use-time, as in Java).
> A function which expects a `base* ` can already be passed a `derived* ` without any problems. Doing the same for a collection of `base* ` to a collection of `derived* ` is not fundamentally different
Since C++ has multiple inheritance, derived* differs from base* not just by reinterpretation, the pointer value could be different.
A single pointer can trivially be adjusted, but a heap-allocated, potentially huge array of pointers would require an equally huge heap allocation to store the adjusted pointers.
Got it, thanks for bearing with me! I wasn't aware of this complexity of multiple inheritance (I guess this is another example of how orthogonal the language is :) ).
It could potentially use const-ness for these use cases, though I'm not sure that would be tractable.
More realistically, it could allow the programmer to express it explicitly, as in C# (C<in T, out V> is covariant in T and contravariant in V) or Java (C<? extends T,? super V> is covariant in the first parameter, contravariant in the second). This would not solve the problem that the co-/contra-variance of some types may depend on their const-ness, but it would allow at least a covariant, always read-only, std::vector_view<T> (similar to IEnumerable<in T> in C#, which can also be implemented by the invariant List<T>/std::vector<T>).
The thing that has really kept me from getting behind updates to the C++ universe is the lack of progress on improving the state of build tooling. It is miserably underengineered for modern, dependency-heavy environments. C++20 does introduce modules, which is a good push towards correcting the problem, but I'm still going to be "wait and see" on whether the actual implementation pans out.
Well, there's Conan, which helps a bit, but these days what I simply do is use CMake to download a package from GitHub or wherever and build it.
Sadly the C++ ABIs are standardized the way that C ABIs are (I'm OK with why but it's unfortunate in that it creates a barrier) so you have to have separate libraries compiled with g++ and clang++ if you use both on your platform (we use both because they catch different bugs, and for that matter exhibit different bugs). But it means you can't simply install, say, fmt in any system-wide directory like /usr/lib or /usr/local/lib
Just as an amusing side note: Common Lisp used to be criticized for the massive size of its libraries and later likewise C++. It was true they were quite large. Now both are criticized for their tiny libraries. Which by today's standards they are.
you can definitely use c++ libraries compiled with clang++ when your code is compiled with g++ and vice versa. It only gets funky when one uses libc++ and one libstdc++
No, GCC and clang are fully ABI compatible (modulo bugs of course). Libstc++ and libc++ are not, so use whatever is the standard library of your platform (i.e the default for calng and GCC) and things work fine.
Yes, gcc and clang use the same platform API so if they use the same headers (libstc++ or libc++) then they will indeed use identical structure layout etc.
I meant a "gcc-native" toolchain (gcc + libstdc++) vs "llvm native" (clang++ + libc++) having different layout (and there is even some interoperability between them thanks to work by the llvm team). I realize my need to do this (to try to minimize opting bugs) is a special case, and probably unusual.
There is not really anything more “native” about using libc++ with clang as opposed to libstdc++ other than the fact that they happened to be developed by the same project. Using clang with libstdc++ is extremely mainstream and normal.
Actually I would bet that even among clang users, libstdc++ is used more commonly on GNU/Linux (IDK for sure, but it’s my hunch).
> Just as an amusing side note: Common Lisp used to be criticized for the massive size of its libraries and later likewise C++.
Part of "size of libraries" is "mental size of libraries".
And C++ and Lisp and have very large mental spaces for their main core libraries. A "String", for example, carries a huge amount of mental baggage in those languages. In most other languages, a string is extremely straightforward because it was designed into the language from the start.
It's possible I'm just used to it, but I've never found std::string more complicated than, say, python (what's up with unicode in 2 vs 3?) or JavaScript (UTF-16 = surrogate pair pain).
It's essentially a std::vector<char> with a few random convenience features bolted on.
I guess some of the confusing points are: not unicode aware, string literals aren't std::strings by default, c_str returns a pointer to a buffer with length one greater than the string length, and the usual C++ quirks like why is there both data and c_str?
One of the things I like about C++ is that there is the std::string for common uses, but then you can design your own string classes with defined conversions to std::string. Qt adds QString with lots of nice utility methods, UnrealEngine adds optimized string types, etc. So you can have custom tailored classes for the task at hand, but easy to convert around to the different types with std::string conversions defined.
One of the things I dislike about C++ is that any large project will have lots of code converting between a dozen custom and gratuitously different string types.
This is the #1 thing I love about C++ compared with Rust — I don’t want it to be easy to depend on thousands of things. I would rather use a small, curated, relatively standard set of libraries provided by my OS vendor or trusted third parties.
“Modern, dependency-heavy environments” are a symptom of the fact that certain ecosystems make it easy to get addicted to dependencies; I don’t think they’re a goal to strive towards.
That's throwing the baby out with the bathwater. Building a C++ project with even a few critical dependencies (e.g. graphics libraries, font libraries, a client for a database or other complex network protocol) is a massive hassle. Sometimes those are curated well, sometimes they're not--but they remain essential for many projects. By taking a hard line against the hypothetical explosion of low-quality dependencies, you compromise the ability to use even the "small, curated, relatively standard set" of dependencies that are genuinely essential.
It's not about the ease of use (you need just a couple of lines in CMake to trigger the pkg-config scripts that every lib in the distribution has). It's about the people who work hard on maintaining the distributions. That's where the quality comes from.
Yes Node is a nightmare, however, you don't need to use public repositories, that much is a choice.
Similarly, Cargo in Rust is an absolute dream to work with, you just run `cargo new <project>` and then `cargo build`. That's it. If you want to add a dependency you can do so from the public crates.io repository or a sub-path or a git repository.
No language should be without this core feature, so it'd be great to see C++ get it too.
That's not the same thing at all. Different versions, sources, etc. Should they really be he job of the OS package manager even when statically linked?
What does this feature have to do with programming languages? Why do I need one dependency tracking tool for Rust, a separate one for Go, a separate one for JavaScript, etc?
I already have a dependency management tool for C++; it is called the Ubuntu package repositories. I don’t need another one baked into the language.
Ubuntu and other OS package managers are years out of date and they are at the wrong layer for serious projects because they are OS layer so not good for hermetic builds on build servers.
OS distributions have package managers and package repositories that have maintainers who are mostly decoupled from the developers. So that takes care of the quality/security problems that arise in ecosystems like Node.js.
There is also C. The tooling and the "package manager for C++" would be expected to seamlessly work for C and be expected to be accepted and used by the C community.
Although I agree with your point, cmake+vcpkg goes a long way for hobby projects, cmake with a bit of custom scripting goes a long way for larger scale projects.
The cmake language might not be beautiful, but it does allow for simple sub-projects/library scripts once the main cmake script is set up properly.
> build tooling...is miserably underengineered for modern, dependency-heavy environments.
My advice about build tooling is to use buck (from Facebook) or bazel (from Google). If you have an ex-Googler nearby, use Bazel. If you have an ex-Facebooker nearby, use buck. Otherwise flip a coin.
No other installation necessary. If someone has a C++ compiler and Bazel installed that will allow you to build against Boost deps (e.g. "@boost//:callable_traits"). No downloading boost by hand, no building boost by hand, no managing it's build, bazel does all of that.
Bazel does have a dependency ecosystem, it's based off of hashed versions of publicly available git repos and http archives (and anything else you want really). Which means any code anywhere can be a dependency while still being reproducible. Additionally you can provide local build instructions for the external code, so you don't even need to rely on their build. The better way is to find someone who maintains a BUILD file for a repo (or mirror and maintain it yourself) but still.
Boost may have been a bad example, since it is a common dependency, without any sub-dependencies.
To beetwenty's point, the C++ ecosystem in general lacks the the level of support for dependency ecosystems that you find in Maven, pip, gem, npm, etc.
Bazel external dependencies are in fact a real pain point. See the 3+ year old Bazel Recursive WORKSPACE proposal. [1]
I was at the first Bazel Conf in 2017, and the external dependencies meeting was quite the row.
I feel like that's a misunderstanding of the issue. Recursive dependencies are orthogonal to the issue of having an ecosystem of dependencies. There is a pattern for recursive dependency management (did you notice the `boost_deps` recursive dependency call?) that is currently fine for production that recursive workspaces will make better.
Also as demonstrated Boost is way easier to include in bazel than make which was the original issue under discussion.
I make a library that depends on boost, here is how you use it:
The trick is that now one will depend on somebody who is not boost for these nice mappings. I brought such a dependency for gRPC once and now it doesn't work with bazel 1.0. I either have to find the author, understand all the bazel trickery, or switch to something else, because most of these bindings depend on many bazel features.
So, bringing such multiple third party bandaid is currently not such a great idea. It would be a bit better if boost itself provided it.
I have seen a library with a copy of boost in the include folder. Client code is forced to use this outdated version and must avoid transitive dependencies to boost.
Please don't do that.
What does orthogonal mean as an adjective for a programming language? I've never heard that word outside of math and I always get excited by a word that might expand my ability to think about code.
Every feature controls one "dimension" of a language, and can be freely combined with other features that control other "dimensions" without worrying about how they interact.
I am quoting the word "dimension", because applying a precisely defined mathematics concept to something messier (like programming) will require a certain amount of hand waving and squinting.
I first came across the notion of orthogonality for programming languages in Algol 68.
Perhaps it's easiest to give examples of what it's not. At first in C you could only assign scalars; to assign a struct or union you had to call memcpy() or something like it. Eventually you could assign aggregates, but you still can't assign arrays unless you hide them in structs. It took a while before you could pass or return an aggregate.
"In computer engineering, an orthogonal instruction set is an instruction set architecture where all instruction types can use all addressing modes. It is "orthogonal" in the sense that the instruction type and the addressing mode vary independently. An orthogonal instruction set does not impose a limitation that requires a certain instruction to use a specific register[1] so there is little overlapping of instruction functionality.[2]"
Its use here is more general, but I hope the specific example helps.
Honestly I think it's a silly word to use for features. In math orthogonal means roughly "at right angles" or "dot product equals zero". When two things are orthogonal it means that they are unrelated so you can deal with them independently which makes things simpler.
In describing features orthogonal is basically jargon for unrelated and adds no additional clarity imo.
Orthogonal means that aspect are independent of other aspects.
I.e. you can understand one aspect of a programming language (memory allocation, generic programming, standard library, mutability, control flow, pattern matching, etc.) without understand every aspect.
As a counterexample, if a language has generics, but only generics for a standard array type, that lacks orthogonality.
Imagine a programming language that gives you three different ways to add 2 numbers:
1 + 2;
std::add(1, 2);
std::int(1).add(std::int(2));
That's is not orthogonal at all, since we have 3 different ways to do the same thing for no discernible reason.
An orthogonal interface is one where there are fewer reasonable ways to do something, and each function/operation has it's role clearly defined, it doesn't try to do things unrelated to its main role, and the interface doesn't mix different layers of abstraction (like intertwining OOP and arithmetic in my 3rd example, when there already are native arithmetic operations).
Largest changes (most from C++11, some from C++14 and C++17) are:
- std::unique_ptr and std::shared_ptr (your code should not have any new/delete). It's easy to write code with clearly defined ownership semantics, and I've yet to create a memory leak.
- Proper memory model so that multithreading has defined semantics.
- Lots of syntactic sugar (auto, range-for, lambdas, structured bindings, if constexpr, etc.).
- More metaprogramming tools/fun.
C++11 alone is a very different world compared to what you are probably used to.
> - std::unique_ptr and std::shared_ptr (your code should not have any new/delete). It's easy to write code with clearly defined ownership semantics, and I've yet to create a memory leak.
I cannot begin to describe how huge a thing this was for me. I came to C++-17 (just about) from C++-98 -- a language I hated so much, I vowed I would never, ever write anything in it on purpose.
It is a massively different beast today from what it was, but the biggest thing of all is this change in how you deal with memory. It was like going from a particularly annoying and verbose aberation of C to a particularly fast and expressive version of Python.
YMMV, of course. In fact, it may vary to the point of becoming absolutely unrecognizable if you're dealing with a massive legacy codebase. But I have to say, I've become a very big fan in the last few years.
It's strange to even call them the same language, it's even stranger when you consider the same compiler will deal with both subsets of the language. It's at the point where someone tells you something is in C++ and you can't even be sure what that means until you look at their code.
I keep my head down in language war discussions, but when people start the usual round of complaints and ridicule of C++, this is exactly what I think: I'm not sure we're actually talking about the same language.
I believe even with C++98 you could have used boost::shared_ptr and boost::scoped_ptr. The addition to the STL effectively didn't change that much. It's just that when you used C++ back then, they probably weren't considered best practice yet.
More important in my opinion are changes to the language, like lambdas or auto.
Not sure how to understand your concern. Yes, it's not 05AB1E. Yes, namespaces are necessary if you have code bases with millions of LOC. But for the most part, C++ is as verbose as you make your type and variable names.
Do you actually find it verbose to read or also verbose to type? I don't consider the latter an issue since 1. you normally spend much more time thinking than typing while programming, and 2. code is read much more often than it is written.
* Hash maps in the standard library (every so often I have to go and look it up to remind myself that no, I’m not imagining things, C++03 really didn’t have hash maps in the standard library, as ludicrous as that sounds)
C++ doesn't like making arbitrary choices in standard libraries, and there are lots of performance tradeoffs and API shape choices to make in a language like C++ that refuses to make programers pay for anything they don't want.
I don't know if you mean formatting, in which case I don't believe anything has changed (pretty much anything goes) but yes, you can now do for (auto [x, y] : list_of_coordinates()) ... and things like that.
This is not even remotely true. Let's say you have:
std::unique_ptr<foo> m_foo;
How do you pass that into a function that can receive a pointer to foo, but does not require it? The only way to do so is to declare the function to take a foo* and pass it m_foo.get()
In summary, * will never be deprecated until C++ has support for something along the lines of Rust's borrowing and lifetimes.
As you can see, there's a tradeoff involved. On the one hand, you get crystal clear, descriptive type: std::optional<std::reference_wrapper<foo>> is clearly an optional reference to foo.
On the other hand, using it is absolutely atrocious: you have to write borrowed->get().thingamajig as opposed to borrowed->thingamajig, and std::make_optional(std::ref(*(owned.get()))) as opposed to owned.get()
Does it work? Absolutely. Is it crystal clear? Indisputably so. Will it deprecate raw pointers? I really, really doubt it, but that's just my opinion.
> How do you pass that into a function that can receive a pointer to foo, but does not require it?
Same as you would have a function that can receive an `int` but does not require it. Or a receive a `std::vector` but does not require it.
I surmise that you mean "how do you pass any value (pointer or otherwise) into a function that can receive a value but does require it". And I surmise you have before used pointer indirection to pass that value because it has a conventional sentinel value of 0/NULL/nullptr.
You are correct in that optional values did not have a standardized solution, until C++17 std::optional.
If you don't have C++17, I recommend one of the equivalent third-party implementations:
> In any case, you can use std::optional<std::reference_wrapper<MyVeryLargeType>>.
I would frankly give a negative code review to anyone who would do that.
You go from e.g.
void my_function(T* foo, T* bar, int baz)
{
// ...
foo->stuff(bar, baz);
}
to
void my_function(std::optional<std::reference_wrapper<T>> foo, std::optional<std::reference_wrapper<T>> bar, int baz)
{
// ...
foo->get().call(&bar->get(), z);
}
this also has more overhead (sizeof(std::optional<std::reference_wrapper<T>>) is twice the size of T* ),
and let's not even start talking about calling conventions and the compile time cost of having to include both <functional> and <optional> everywhere.
heh, to say that I originally wrote if(foo) and then replaced it by //... because that was not the point.
Also, dereferencing an unset optional is also UB, so it would segfault all the same given the same preconditions.
> The first has no such indication, except hopefully some human-readable comment that the pointer may be NULL.
I don't understand. If the parameter was not meant to be sometimes null it would be a reference; not a pointer.
The fact that a pointer is used is the "this may be null" indication.
Not formally, but as you were responding to a comment on style: indeed, you don't really need * that often; there are more powerful, safer options in most cases.
But C++ is a systems programming language and * is still quite useful.
> I had the luxury of starting from an empty buffer
Let's assume that I'm willing to flush the cache. Are there and C++17 learning resources that you might recommend? My day-to-day languages are Python and scripting languages.
Not sure I'd recommend the C++ standard for learning the language for a couple of reasons:
* It's gosh darn huge
* While not the hardest standard to read, it is still a standard, so has to be focused on really really low level detains and precise complete explanations instead of being a good learning resource
That said it is a great resource if you're ever confused about some corner of the language -- a lot of articles explain things slightly incorrectly.
As in: recompile and works? For sure, see for example a lot of the HEP/NP software which runs typically on Linux, BSDs (incl. MacOS) and I think even Windows.
As in: have the same binary? You could probably use the same object files, but shared libraries and executables probably won't work (without trickery), just because the file format is different.
> Last time I checked QT depended on a signal/slot mechanism that was not C++ compatible.
what do you mean by "not C++ compatible" ? Qt is 100% C++ code. The few macros you see (signals:, slots:, emit) are #defined to nothing or public: / private: in a Qt header
In commercial settings, I encounter several barriers to using C++ versions newer than 2011:
(1) Most C++ code I encounter is C++11, and I deal with lots of projects. It's rarely sensible to change the language version of a large code base without a very good reason.
(2) Many developers are well-familiar with C++11.
(3) There's no widespread perception that C++14 or later bring compelling language improvements.
(4) Some (most?) C++11 developers are already uneasy with the complexity of C++11, and aren't eager to incur the learning curve and (perhaps) new set of pitfalls associated with newer C++ versions.
(5) Some C++11 developers look at the language trajectory, especially in terms of complexity, and have decided that their next language(s) will be e.g. Rust or Julia instead of C++14, C++17, etc.
I suspect these factors all contribute to the momentum that C++11 seems to have.
I don't encounter these barriers in commercial settings. The biggest barrier is compiler support if you target environments with different compiler toolchains. Each new version of C++ brings features and bug fixes that make code simpler. It actually reduces complexity from the perspective of the developer. The forcing function for upgrading C++ versions in my teams is always that it would greatly simplify some immediate problem or allow us to remove a gross (but necessary) hack due to the limitations of prior versions. At least with the LLVM toolchain, upgrades have been nearly issue-free so there is little to recommend not doing it. C++17 is a big upgrade over C++11 functionally. Developers that do not see compelling improvements probably weren't using most of the functionality of C++11 anyway.
While the number of features and quirks in standard C++ grows with each version, the subset that a developer needs to remember to write correct idiomatic code is shrinking with each new version as capabilities become more general and the expression of those capabilities simpler. There is a new, simpler, and vastly more capable language evolving out of the old one and you can increasingly live almost entirely within this new language as a developer. I hated classic C++ but I actually really like this new C++, and it allows me to write far less code for the same or better result.
There are still many things that C++ can express simply and efficiently that Rust, Julia, etc cannot. If you work on software that can take advantage of that expressiveness (e.g. databases in my case), the cost of changing languages is quite large.
> There is a new, simpler, and vastly more capable language evolving out of the old one and you can increasingly live almost entirely within this new language as a developer.
How is that true in practice? I would expect that once you have a codebase big enough, instead of living only with the new language you still have to deal with (or at least read) code written in previous versions of the language. And so instead of having to know only the modern subset you would have to keep in mind all the potential quirks from previous versions.
Sure, you will have code written in an older idiomatic style but there is some continuity, most C++17 programmers used to be C++11/14 programmers. Upgrading code from e.g. C++11 to C++17 is relatively incremental, typically easily recognized, and can be done opportunistically. Obviously going from C++03 to C++17 would be much more expensive, so that is a bit of a different case. Rapid improvements in code analysis often breaks previously clean builds in newer compilers, so you often find yourself doing hygienic work on old C++ code anyway.
This does imply some ongoing maintenance work related solely to keeping the code base consistently "modern". This almost always makes the C++ simpler and more maintainable and often reduces the line count, so it is worth the nominal investment. The massive improvement in template code readability is worth the price of admission all by itself.
Here is a proposal (from CppCon 2019) to create C++ epochs to drop support for old misfeatures in new code (inspired by Rust's epochs). Code written in the new epoch would still be fully backwards-compatible with older C++ versions and compilers.
So you'll not only need to know all these sublanguages (epochs) but also how they interact with each other, especially when you need to refactor code that needs to cross these "epoch boundaries" (refactor the functionality not just to "upgrade" to a different epoch). Who comes up with that stuff? :-/
This has been my experience also. Of course you can't forget the old stuff if you're maintaining an old codebase. But, I'm grateful to use lambdas, std algorithm, if constexpr, and fold expressions instead of boilerplate classes, bespoke loops, SFINAE hackery, and recursive variadic functions. There is less wizardry necessary than in the past.
On the other hand, there aren't really any changes in 14 and 17 that break compatibility with code written in 11, at least that I'm aware of.
In the examples I've seen it seems to mostly come down to compiler, toolchain and 3rd party binary availability for the new versions, like with any major language version.
If for example you are using stock gcc from Ubuntu 16.04 for your builds, you can't use C++17.
For big (legacy) code-bases, just upgrading the compiler is a project by itself.
All 3rd party C++ libs need to be upgraded, all internal projects need to upgrade in an orderly fashion.
On top of that, just the change of the compiler will expose some pre-existing bugs.The bugs might have been there, but are not a problem for the business if they currently don't show.
I've seen projects with a core team of half a dozen people, supported by the other SW engineers working for 6 months just to upgrade to a newer version of windows and the compiler. This was mostly due to unwise SW-design decisions accumulated over 15+ years.
You raise a good point about newer compilers doing much deeper analysis of the code than older compilers. I have experienced the horror of compiling an old, nominally clean C++11 code base with the latest version of clang++ and seeing a small avalanche of errors and warnings that weren't picked up by the compiler when that code was written.
It creates a lot of work, and frequently the nature of the bugs are not material (i.e. in context they may not produce a runtime fault), but I do find it to be good hygiene. I used to do builds with two different compilers for the same purpose, though that found compiler bugs as often as it found issues in the code.
I totally agree, updating compilers is painful, and can be hard even for smaller projects. But for projects that went through that and actually got the newer compiler working with the C++11 code, I haven't yet seen anything that breaks compilation when then switching the compiler flag to C++14 mode.
Plus, a lot of new library and language features are simply standardized versions of features that mature codebases had their own solutions for years, so "upgrading" to a newer C++ may also imply a long process of deprecating these alternatives in favor of standard ones, and the subtle differences between the two can expose new bugs and issues.
Here is a list of some real-world examples of bugs that Mozilla had to fix when upgrading from Firefox from C++11 to C++14 to C++17 (still in progress). Many of these are compiler issues from upgrading to clang or gcc versions recent enough to support the new C++ versions, not C++ language changes.
> On the other hand, there aren't really any changes in 14 and 17 that break compatibility with code written in 11, at least that I'm aware of.
auto_ptr has thankfully be removed, but if you have legacy code that uses it it won't compile under C++17. Of course if you have code that uses auto_ptr you should fix it, but realistically nobody has the time for that.
Depends on how much it was used. But I was part of the effort to convert to C++17 for our (substantial) C++ code base of ~30 years and it was not painful at all.
I also ran up against the deprecation of std::mem_fun and std::mem_fun_ref in a recent migration. Easy enough to replace with lambda expressions, but one more hurdle to running C++17 code.
all compilers have ways to reinstate them to make porting easier, e.g. /D_HAS_AUTO_PTR_ETC=1 on msvc, -D_LIBCPP_ENABLE_CXX17_REMOVED_FEATURES=1 on clang...
I mean, if tricky usage of auto_ptr is stopping you, you could probably use a drop-in replacement. But the 'and this adds to the sprint tasks how?' question still isn't answered in a good way
ABI stability of the STL is the biggest issue I've seen. Upgrading from C++11 to C++17 becomes a bust if any of your 3rd party libraries want to pass even a basic std::string.
I don't know what the hold up is on modules. Even a basic "this header uses a C++11 namespace" functionality would fix most of the problems.
But even better would be something like "extern 'Cwrapper'" that would let you expose C++ objects as a C API with a void* object pointer without having to do the pointless wrapper dance every time.
Like make the compiler convert "class Thing { doThing(int a); }" to "typedef void* Thing; Thing_doThing(Thing* this, int a);" automatically so that other languages can call me.
What's the issue with abi stability? GCC broke the Library ABI with C++11 but the old ABI was still available. I thi k it still available in C++14 and 17 mode.
If you switched to the new ABI with c++11 then c++17 as far as I know brings no new breakages.
No, std::string definitely changed between c++11 and c++14. It could be an implementation detail between different libstdc++ versions for the g++ version that supported c++14, but the incompatibility was definitely there.
> No, std::string definitely changed between c++11 and c++14. It could be an implementation detail between different libstdc++ versions for the g++ version that supported c++14, but the incompatibility was definitely there.
that is a problem with your distribution (and the reason why using distro packages sucks for development), not with C++.
The ABI only changes if you change the value of _GLIBCXX_USE_CXX11_ABI with libstdc++
I don’t understand why they don’t do more about ABI. Compared to C# or java it’s such a pain to share code in C++. To me this would open up a lot of opportunities to create more libraries.
ABIis hard because C++ structures are typically more complex than C structures. While the C documentation could simply include the struct (and the ABI would constrain padding, etc), consider a simple std::string, which may (OK, will) have small string optimization: different runtime libraries might make different tradeoffs as to how to do this. You'd have to constrain the entire standard library for your platform which could forestall improvements down the road. The committee already unwittingly made that mistake with std::map and doesn't want to make that mistake again.
C++ also insists on inlining code extensively across the provider/client boundary, so if the offset of any data element changes the binary library is no longer backwards compatible.
This is exacerbated by the lack of usability in compilation toolchains that promotes header-only libraries just for build simplicity and for which the data structures often won't be compatible across .so boundaries.
Not dissing your comment, just pointing out that the committee is not just aware of the problem but slowly doing what it can to address them. C++17 and C++20 have a bunch of features to improve both of these factors.
There are also a number of features not really intended for user code designed to help make libraries faster, and more stable. The downside of course is, well, just look at library source. But really the source to the C standard library has similar issues.
I don't like header-only libraries because they slow down compilation, and slowly the trend is starting to abate.
They do. ABI breaking changes are routinely voted down unless they are really worth it (and the only non backward compatible change that was accepted that I'm aware is the std string changes in C++11).
As somebody who kicks C++'s tires once in a while and is always put off by the incredible complexity of the language, and the complexity and friction and fragility of its workflow, I was sad not to see any explicit mention of pairing an ABI break with removal of the bad parts. (Yeah, I know, that might mean source-level backwards incompatibility...) Makes me wonder if it's ever even discussed at a high level.
Maybe things are better now but in the past almost every new library I added to my projects required a lot of fiddling with headers , link options and other stuff. With C# this is so easy in comparison.
Re (5), I rather feel that the quality of the documentation and communication around modern C++ puts everything else to shame. If you knew java or python in 2009, how are you supposed to quickly catch up with 2019? (Rust also seems to have piled on a lot of additions since 1.0, but I don't use it so I shouldn't speak.) There are books and blogs for everything popular, but it's really a little shocking to me that no other big language has anything that can compare to cppreference.com.
This doesn't change what people other than me might be thinking, of course.
I guess it's a question of habit. I'm learning C++ during my free time since a few months. I find cppreference.com really difficult to parse. I understand way more of the documentation now than when I started because of the bit of experience I gained, but it is written in a way that makes everything sounds really over-complicated. I remember when I googled for something and arrived at the page about "Value category": https://en.cppreference.com/w/cpp/language/value_category. If you're not already well familiar with the language, this page is really hard to understand IMHO.
I found it way easier to deal with Go, C#, and Ruby documentation for example.
I don't recommend cppreference for novices. It is by no means an easy read. It is a reference, first of all. It enumerates all possible minutiae exhaustively. Unless you are already familiar, to a degree, with most of the topics, reading it is going to be frustrating. The second obstacle is the terminology. C++ defines a lot of terms that are not in common use in other languages. It takes time to be acclimated.
The reason that it is popular in the C++ community is that it cuts down legalese a lot when compared to the language standard.
Try books first. Meyers and Josuttis write excellent books. The template guide by Vandevoorde and Josuttis is my gateway drug to becoming a language lawyer. It is still hard, but at least manageable.
> The following expressions are lvalue expressions: [...] a string literal, such as "Hello, world!"
That's pretty interesting. I'm not even sure how to test the effect of
"Hello, world!" = <something>
I imagine the reason this is allowed has something to do with how strings decay into pointers.
> I found it way easier to deal with ... Ruby documentation for example.
Maybe in documentation detailing the methods available, but as far as I know, there's no official spec for the syntax nor the details of the semantics of the language.
(1) It's actually rather sensible, because C++ is famous for its backwards compatibility. So you can use C++14 or C++17 features in some new code while leaving the rest as-is - and gradually refactoring some of it with new features.
(2) This used to be the case with C++03 (or C++98), and it changed...
(3) I partially disagree and partially claim you're begging the question. You don't have poll results to suggest what people think of C++14. But - C++14 had a lot less changes compared to C++11 or C++17; this was natural considering how the cycle of debate of potential features progressed.
The thing is, the fact that C++14 wasn't that revolutionary in terms of features also means it is easier to adopt. If you want more novelty - try C++17; or - try a C++14-dependent library like Eric Niebler's ranges-v3.
(4) C++17 allows for writing less complex code in many cases. Specifically, you can significantly reduce the need for template metaprogramming - a particularly hairy part of the language - by using more constexpr functions and if-constexpr in regular functions. And there are other examples. Like Bjarne says: The C++ committee endeavors to "make simple things simple (to write)". So it's a trade-off.
(5) Some people switch languages because of workplace decisions or personal preferences, that's true; but people also adopt C++ because of what becomes possible, or easily-expressible, with newer versions of the language. For example, using the ranges library and with C++14 or later, functional programming with C++ has become quite reasonable, borderline pleasant even.
---
Bottom line: In my opinion, at this point C++11 has more inertia than momentum. And that's ok. People will progress over time.
Also, this is an "easy" breakage, in that it was glaringly obvious. That is, you didn't encounter a strange compilation bug and had to investigate why it occurred, or got different run-time behavior of the same code.
In C++ there are almost never any overhauls - it's almost always new functionality that can replace older functionality. That's what compile-time evaluation is to template metaprogramming.
> There's no widespread perception that C++14 or later bring compelling language improvements.
I would argue the opposite: C++11 is a fairly different paradigm to C++03, and upgrading an existing code base is a sizable project.
But once you get hooked into the concepts it introduces (auto, lambdas, templates, constexpr), each subsequent revision of the language standard introduces some improvement to these concepts that deals with unpleasant edge cases the earlier revision had, so each of them promises to make your existing use cases simpler, at a moderate rewrite expense.
C++11 vs C++14/C++17/C++20 is not python2 vs python3. Because of the emphasis on backwards compatibility, you can often just recompile your C++11 project with the new version of the standard and everything should just work. Backwards compatability also means that the newer versions are only as "complex" as the features you choose to use.
The tricky part of this is that if your project is a library, you're not just opting into having to update your own toolchain, but also that of all your users. This means that a lot of library developers will err on the side of not updating their minimum version, which in turn means application developers don't have as much pressure to update either. This isn't specific to C++ by any means, but I think it does pop up a bit more in this space due to the fact that a lot of developers prefer to just use the default toolchain of their system, so forcing downstream users to update their toolchain could be viewed as a bit more of a maintenance burden.
> (3) There's no widespread perception that C++14 or later bring compelling language improvements.
Constexpr if (introduced in C++17). If you're building a library that uses templated types (and therefore static dispatch instead of virtual functions and dynamic dispatch), then using constexpr if dramatically simplifies the implementation of your code.
I don't think GP was arguing that there weren't any compelling language improvements, just that there isn't a "widespread perception" of those improvements.
Do you think that will change with introduction of modules? Would that be a feature compeling enough to make people move to newer versions of the standard?
C++14’s compelling improvement is initialization in capture lists, so you don’t have to copy into them. I haven’t seen anything compelling beyond that.
Right now my biggest things I like from C++20 are:
- Concepts (Now I can use static polymorphism without all those SFINAE template hacks)
- Designated Initializers (Finally!!! Using POD structs became way more convenient now)
- Coroutines (Would be pretty nice for writing games / interactive applications)
The things I don't have any interest in (but don't care if it's in or not)
- Ranges (Too much complexity for something you could do with plain old for/if loops...)
The things I'm worried about:
- Modules (Theoretically really good, but in practice the whole thing is starting to become a mess, especially when it's interacting with the preprocessor and external libraries, and when trying to preserve backward compatibility.)
I have hope for modules because they can improve build times. Not exactly sure they would improve build time but if it's true, it would be a massive improvement.
Meanwhile, I am deeply concerned about modules, as it seems like the kind of feature that is going to massively harm build times by making it much more difficult to do truly independent distributed builds (which right now is pretty easy with C/C++ due to its separate compilation and extremely explicit inter-file dependencies).
I do not understand this comment, as I have done separate compilation every day with C++ for over two two decades: the model inherently only allows this, as every translation unit absolutely must be compiled separately and then linked... you don't even have a choice in the matter. I have also used both off-the-shelf and custom distributed build systems (most recently, I have deployed one which does insanely-parallel builds of a C++2a codebase using AWS Lambda), which pair extremely well with the explicit file-oriented dependencies that can be reported by each translation unit and used to determine which parts of the build are now stale given changes to the filesystem. I have never seen languages with module systems support even basic separate compilation, and it terrifies me; meanwhile, the flags I have seen being added to clang for attempting to preserve separate compilation behavior with the modules spec doesn't leave me feeling good about the endeavor. It could be my fears are unjustified, but it is extremely strange and somewhat annoying that the advocates for it don't ever seem to appreciate the state of the art for the status quo.
> I do not understand this comment, as I have done separate compilation every day with C++ for over two two decades: the model inherently only allows this, as every translation unit absolutely must be compiled separately and then linked... you don't even have a choice in the matter.
Except if your headers include macros or templates, which must be evaluated every time. This is not separate compilation. What C/C++ developers have been doing for 20 years is jumping through hoops to incrementally reuse previously compiled results because C/C++ violate basic modularity principles [1]. They call it "separate compilation", but it's a pale shadow of true separate compilation.
Proper modules would enforce abstraction boundaries so true separate compilation becomes possible. I can't speak to the specific C++ proposal here, but enabling separate compilation is exactly what modules are for.
Normally use of templates do break separate compilation but it can be recovered, and it is routinely done on large codebases, via explicit template instantiation. The ergonomics are not great of course and hopefully modules will improve things.
With C++, you have a lot of limitations on cross-machine, cross-library compilation. The library is not defined just by the platform, but by the compilation directives and macros.
Since headers can redefine macros, that also means that even including headers in a different order can change the meaning.
Older languages like C++ with headers are simply source files with a different extension, so your individual source file compilation is actually loading in and parsing cross-sectional subsets of your full project. Modern languages typically define the interface inline with the code, and export that interface to other modules which would consume it. The reason that you don't have intra-module distributed build is because the intra-module code dependencies are implicit - there is no developer-defined partial cross section of the code, all of the code needs to be pulled in and semantically evaluated as a unit.
You could hypothetically still do distributed optimization of the code after it is semantically evaluated, but TBH there is a lot of time wasted in evaluating headers for each file. Modern compiled languages usually have compilation time as a restriction on the implementation and design, and elimination of the need to repeatedly parse and semantically evaluate files is part of their design.
> The reason that you don't have intra-module distributed build is because the intra-module code dependencies are implicit - there is no developer-defined partial cross section of the code, all of the code needs to be pulled in and semantically evaluated as a unit.
And this seriously somehow doesn't seem like a problem to you? My massively distributed (to AWS Lambda) C++ build compiles at the speed of the slowest single translation unit. You seem to be optimizing for serial compile performance, but if you truly want speed you have to go parallel. These explicitly-managed "cross sectional sunsets" are what makes this so easy to do with C++.
There are definite tradeoffs here, but one thing to consider is that dev tooling generally can't benefit from that kind of massively distributed parallelism. If you make a change to a widely-included header file, code intelligence tools suddenly have to do a huge amount of work to reevaluate their internal model of all affected translation units. This can really result in an unpleasant and laggy editing experience, even on medium-sized projects. That's the kind of thing that modules can help with; I certainly wished fervently that they were already a thing when I used to work on C++ static analysis tools years ago.
You can still do distributed builds with modules. You can't have cyclic dependencies anymore though. Your code has to be structured into logical modules that are meant to be built in parallel. It's not going to be free. You could have replicated modules with the old preprocessor style, but this way it is formalized and enforced by the compiler.
Some claim this, but I haven't seen it play out this way yet in what I have seen for modules; it could be that people are hyper-optimizing for serial performance when the future (and present!) is parallel? If so, :(.
This article is possibly out of date--or maybe was wrong to begin with--but it shows how the modules concept from earlier this year simply broke parallel build efforts by enforcing a ton of serial dependencies.
I have also seen this. My guess is that it highlights more that there are way too many dependencies in the libraries being used and that those dependencies aren't organized well.
Not particularly. However I do develop on OpenWrt where size is important. On some C++ projects, when I convert the for loops to range based ones using clang-tidy's modernize-loop-convert, I usually get a reduction in compiled size (compiling with -Os of course).
Designated Initializers done awkwardly a.k.a. "the C++ way"
// Point2D point2d {.y = 2, .x = 1}; // (1) error
Oh my. The C++ committee keeps their flight altitude rivaling moles and worms. This is another brittle feature, just like the initializer lists that have to mirror the declaration order. In what world is this a better option than allowing any order? Can't compiler implementors be bothered or what is the rationale?? C does this better.
I think you are confused about the intention. If you want order independent initialization you could just as well use the old initializer list. The idea here is that you DON'T want to accidentally initialize x with y.
I don't think I am confused. The point is that, logically, either you do not tag something with names, and then, naturally, the order decides. Or, you tag something with names, and then, given you named things, there ought to be no order.
Not so with the C++ initializers. An initializer list in a constructor should mirror the declaration order, or you won't go warning free. Every time you'd touch the internals of your class, you'd need to touch every constructor that uses an initializer list.
Same with the struct initializers now. You are having this:
struct A { int a, b, c};
you need to do this:
A a = { .a = 1, .c = 3};
but you can't do this:
A a = { .c = 3, .a = 1};
because... well, because C++. There is no reason to disallow the second form except for .. what exactly? What is the reasoning of the C++ committee to not be able to mirror what one can do in C (where, aside of the missing typedef or "struct" in front of A, both forms are valid)?
Just as you're saying, the C++ committee seems to be confused about order-dependent and named initializations, and for some love-of-bean-counting insist that named initialization needs to respect order _as well_.
There is of course a reason which is well documented in the papers. Constructors, and initialization expressions can have side effects in C++. An invariant of the language is that member constructors are always invoked in declaration order, so if you were to swap the initializers around, the compiler would still have to initialize them in the original order, which would be surprising.
Which does not means that it couldn't be done, in fact constructor initializer lists have the same issue and you are allowed to list them in any order but this is considered a language mistake and most compilers warn about out of order initializer. This mirror the arbitrary evaluation order of function parameters which is also considered a mistake.
In the end there was no consensus to allowing an arbitrary order, but in the future that can be relaxed without breaking any code if a consensus is reached, while the reverse could not be done.
As usual it is always compromises.
Anyway, at last for me, initializers are more about writing more explicit code and be more robust in the face of changes.
> There is of course a reason which is well documented in the papers. Constructors, and initialization expressions can have side effects in C++. An invariant of the language is that member constructors are always invoked in declaration order, so if you were to swap the initializers around, the compiler would still have to initialize them in the original order, which would be surprising.
The following:
Point2D pt = {.y = 5, .x = 6};
should not have meant that 'y' is initialized before 'x'. It's only a lexical convenience that exists before parsing. When the code is parsed into an AST, the actual AST would reflect the following code:
Point2D pt = {.x = 6, .y = 5};
There is absolutely no reason to enforce declaration order.
I tend to agree with ephaeton that forcing the order is a little heavy-handed. But thanks for the insight about initialization order.
For what it's worth, I find strictness pedantry like this fairly useless in production. For example, a JSON file has an order to the elements of an associative array, but Javascript doesn't. So most of the time, I treat associative arrays as having no order, and use another array to map indices to keys or whatever.
Contrast that with PHP, which does maintain key index in the order it's added. There have been countless times where it turned out that I needed that order, and it saved me the work of declaring the extra array. Even more importantly, I've never been hit by that surprise (of not having order) in PHP, whereas I have in pretty much every other language.
So maybe PHP is less pure, but truthfully, pureness is the single biggest source of friction in my daily life. Basically the entirety of my day now goes to setting up environments from minimalist environments, working around subtle language errata, or translating what I want to do in my head to the domain-specific language decisions that are so widespread in languages like Ruby.
I prefer the lazy route now in most things. Just give me everything, with the least-friction needed to do what's in my head, and let me prune it/optimize it when I'm done. So in this case, I'd prefer C++ to have a strict ordering of elements, but let me initialize them in any order, so that I can think in abstractions rather than implementation details. Which is why PHP is roughly 100 times more productive for me than C++, even though I grew up with C++ and know it like the back of my hand.
> built-in library support for logging, http, zip, gzip, json, yaml, template rendering, RFC3339 datetime reading/writing
That's completely out of scope for a programming language pretending to be taken seriously as a general purpose one (including e.g. systems programming).
Also, your list looks like a pretty arbitrary choice of protocols and formats, why don't we also add support for SSH, SMPT, IMAP, PNG?. In the end, everyone would want their niche protocol/format to be included in the standard.
> the dream: compliant compilers must be able to compile down to static binaries, cross compiling built-in.
Cross-compiling to what architectures specifically? The most used ones are proprietary and I think that disqualifies them from being included in a serious international standard, since you'd need to read the spec and implement it in order to be compliant (I'm not versed in ISO inclusion requirements, so this may be already happening, but it would still be wrong. (grepping "x86" in the offical C standard returns no results though.)).
> built-in library support for logging, http, zip, gzip, json, yaml, template rendering, RFC3339 datetime reading/writing
There is a Boost library for each of those things.
> An opinionated, modern packaging and dependency story (like go modules, Rust crates)
> the dream: compliant compilers must be able to compile down to static binaries, cross compiling built-in.
You can use Conan or vcpkg. LLVM basically solves the cross-compiling issue since you can take the IR to any platform that has a LLVM target.
Neither of these are feasible to include in the International Standard because C++ runs on more than amd64 and would make a lot of obscure platforms no longer compliant with the standard. Rust crates are nice, but people building medical devices with C++ shouldn't need to worry about supporting an npm-like monstrosity in their builds.
> Nothing forces you to depend on any packages you don’t want to.
Nothing stops you, either, which is a problem for everyone in the future who uses your code. The first reaction to any problem in JavaScript/webdev is to google for a library to import to solve the problem. This is acceptable behavior because the existence of a web browser implies powerful enough resources to accommodate bloat. But this mentality isn't acceptable on other systems and has even infected some basic functionality:
Random number generation has trade-offs for speed, security, and thread safety.
Most other langues with a "simple" built-in are unfit in some of these aspects, so you may end up with a footgun and still have to find a replacement.
In Rust you can choose which algorithms are used and how. And because it's a regular package, it can be improved without breaking the language.
Note that number of dependencies doesn't mean anything. Some crates are split into tiny deps for each feature, so that you can reduce binary size and compile time if you disable optional features.
As does Java and .NET. Without any dependencies and guaranteed to be portable.
> Note that number of dependencies doesn't mean anything. Some crates are split into tiny deps for each feature, so that you can reduce binary size and compile time if you disable optional features.
In theory I agree. It just seems like one of the persistent unsolved problems in software is dependency hell (spawning the whole containerization movement), so I'd like to avoid it if I could.
C++ has many audiences with disparate needs; ranging from firmware, to scientific computing, to application development.
Most of the industries using the language optimize their code for a target platform. And general non-optimized portable binaries are simply not cost effective, so standardizing installation has not been simple.
If you do care about portability, you functionally need to.
- ensure you're not linking to anything but LIBC and the VDSO
- compile with no assembly language
- disable most optimizations, and select a sufficiently generic hardware target. For the most part, they succeed in features.
- directly compile all 3rd party source in your binary.
Then, your binary should last a long time.
That all said, I believe your needs are in the minority in the C++ community, and other languages likely better suit your needs.
i never understood the need for package managers. just check your dependencies in and build with the rest of your source code. this doesn't introduce extra moving parts into the system (why should i need an internet connection to build my stuff?), easy to patch and debug dependencies, gives you full control and doesn't restrict your toolchain (e.g. conan doesn't as of now support vs2019+llvm).
what i _would_ like to see though is a standardized build system for c++ projects, although that will likely never happen.
On thing I appreciate is mdspan -- multidimensional projections onto a regular linear vector. I've always had to spin these myself which means they don't fully participate as ordinary containers (who's going to go tho all that work for their own application code).
I'm hoping I can map them into GPU space -- looks like the standard has adequate control for this.
It's often overkill for what I do, especially as it can't use the same compiler options as my tree normally does (they have to bend over backwards for back compatibility; I don't). I do use it some places. I certainly don't envy the hard work of library developers!
When I simply need a 2D or 3D resizable array it's often easier, as I mentioned, to simply spin one up. Having that support "native" will be great.
I've been anxiously awaiting modules and concepts. Both are incredibly important to reduce developer burden and improve the development experience.
Modules: While it is important to be able to fine-tune linking and compilation in some settings, in most, and especially for beginners, this should be handled by the compiler. Especially when compared to other modern languages, C++ is much more complex to understand the toolchain and what's going on under the covers with the dichotomy of compilation and linking. Header files were a hack that have been around for too long, and there needs to be less separation between the interface and actual binary code when compiling. This is a headache both for novices and the experienced.
Concepts: The missing link for templates. Besides removing some of the roundabout-ism of SFINAE by providing compile-time verifiable interfaces, I think the biggest benefit of concepts will be the error messages. Right now, the state of errors in heavily templated code is abysmal, and this translates to a lot of wasted time for developers. Concepts should allow the errors to indicate exactly what's wrong.
I can't wait to be able to use these in public code. Some compilers support concepts with a flag already (and GCC 10 has it without the special flag), though none support modules yet...
Could someone describes the keyword "unlikely" and "likely" in a bit more details? It seem to be a very niche thing to add to the language. Is it expected that those hints will have an impact important enough regarding what the optimizer can do?
likely and unlikely exist in different forms as compiler extensions for a while. There is code where they indeed help compilers to produce slightly better code for the hot path. Good use typically involves lots of measurement as developers are often wrong about the impact and about what the real flow is.
What kind of things can the compiler improve? For instance it can arrange the code in a way that the instructions of the hot path are directly behind each other, whereas the unlikely case jumps further away. Or it can arrange code a bit to help the branch predictor.
gcc and clang (at least, I don't know about msvc) have had these for a while. Basically, the instructions for whichever branch is declared most likely to be taken will be placed immediately after the comparison instruction(s), which is generally more cache-friendly.
It seems conceivable that it might also affect the compiler's inlining choices in each branch (i.e. don't bother to inline as aggressively in the less likely branch, to reduce code size), though I don't know for sure.
A common way to extend a language is to standardize what people are doing in practice. It seems niche, but you have to remember what C++'s niche is. Linux kernel etc use hints like this extensively
Say you are writing a high speed trading program. At some point you get to the "if(wouldMakeMoneyOnTrade())...". However for every time this passes and you trade there are 1000 where it fails and you don't. However time is critical because your competitors are using similar algorithms and so you need to beat them - thus you put in a likely and ensure that the CPU jumps to the trade code the fastest possible. Your competitors that use Java pay a CPU branch prediction penalty here because the hotspot optimizer has noticed that the if almost always fails and optimized for the false case. Thus you can make money by beating your competitor to the trade. (Note, this doesn't mean you can't use Java for your code, but if you do you need to manage that risk somehow)
Many HFT firms use C# or Java. Quite a few matching engines are written in those languages as well.
The trick is to remember that garbage collection is not some sort of werewolf that wakes up and decides to mess with your stuff whenever it feels like it; it (can) happen only at certain, documented points. So you have to write your code in such a way that it does not do memory allocations in the critical path (which you would not be doing in C++ anyway, for a similar reason; malloc/free is expensive).
Generally if you're doing HFT your critical path is not going to being doing much anyway. A few reads, comparisons, maybe some trivial arithmetic and that should be it.
For what is worth, at least in London, all the HFT firms I have worked with or interviewed for use C++. As I'm a C++ programmer there is bias of course, so my sample is probably not representative. Still I suspect that those that use anything other than C++ are a small minority.
Without actually looking at the docs, I would guess these map directly to cpu intrisics that can be used to guide the cpu branch prediction algorithms.
At some point pentium4 had static branch prediction hints, but they are obsolete and no longer relevant.
Today __buitin_expect are mostly used to mark hot/cold branches. The compiler will normally optimize hot code for speed and cold code for size also will attempt to put cold code in separate cachelines and even pages. And other similar code layout optimizations.
Been away from C++ a long time. If picking it up again, is it better to just start with C++17 or 20 or stick to C++11 which seems to be the defacto production version?
Depends on your needs. If you don't have a lot of or any legacy code yes, go straight to c++17 (or even 20 though you won't be able to use many of the features right out of a the gate, or without third-party libraries). If you can do this you'll be glad you did.
I was able to start a project in c++17 in 2016, though at the time I had to use boost::variant with the Xcode compiler and boost::filesystem for all three compilers (only a limited use so I could even have skipped it). Also for some complex third party libraries I had to make a little trampoline file that compiled in C++14, or with special flags, and then call those entry points.
Since I was starting from a blank slate I also compiled everything with pretty much maximal warnings an -Werror which really improved the code a lot, something you can never get away with with legacy code (not to insult working legacy codebases!)
C++11 was the major version bump. It changed how you're supposed to write C++. C++14 & 17 are just minor fixes & improvements. So there's really no reason to not start with at least C++17.
I haven’t done C++ a while but I really like where this is going. It’s a complex language for sure but I always liked about it that whatever needed to be done the language would not get in the way. There is something to be said for working in the language your OS and a lot of other libraries are written with. You know that you have high probability of achieving what you need to do. It may need a lot of work though.
There's still the need for logging and assertion functions, where you want to test the debugging level or some other value before you evaluate the arguments to the function. Right now, you need to use a macro, but there's a proposal:
James Dennett and Geoff Romer. P0927R2: Towards A (Lazy) Forwarding Mechanism for C++. ISO/IEC C++ Standards Committee Paper. 2018. url: https://wg21.link/p0927r2.
Is there a good "tutorial for modern c++" resource out there? The last c++ I used heavily was c03 and a sprinkle of c11. I find reading modern c++ to be somewhere between frustrating and an exercise in hieroglyphics thanks to how much it's changed. Something along the lines of "basics of why to use the different std pointers/views instead of raw pointers", lambdas, all the new const-like bits, and whatever else have become "core" features.
Think of `strcmp` which returns an int. The int can be <0, ==0, or >0. That same `strcmp` can then be used to implement all other standard comparison functions. `operator <=>` is the generalized version of that.
An important part there is: Most users will never call <=> themselves. However somebody implementing a type can implement a single function and the compiler will translate usage of <, <=, ==, !=< >=, > to the single function. Reduces boiler plate in the implementation. (Think about it - most implementations of > or < will often use the same logic already, spelling this out is "annoying"; mind that for performance reasons it can still be worthwhile to implement a == explicitly as equal comparison is often faster than ordering, but that's something the type designer can chose)
> Think about it - most implementations of > or < will often use the same logic already, spelling this out is "annoying"; mind that for performance reasons it can still be worthwhile to implement a == explicitly as equal comparison is often faster than ordering, but that's something the type designer can chose
Yup. My C++ code would often include a `int T.compare(const T& a)` member function. Then any comparison operator would be custom written too -- but only to delegate to the custom compare and check its result.
This one feature alone will eliminate several lines of boilerplate.
On case is string comparison. When implementing == I can shortcut if length doesn't matter the string can't be equal. When implementing <=> I however have to iterate in any case. (While there the question then is if the shortcut is really efficient, as the iteration might already stop on first character ...)
Along with automatic synthesis of all the operators, c++ 20 also clarified rules about overload-matching and automatic construction of objects for purposes of comparison, so you don’t have to write the friend methods to for example compare int to your type if you have trivial int-taking constructor for your type. Also if your == operator or != is more efficient than a full lexigrapgical comparison the compiler will now do the right thing
Some parts of C++20 are really good, like most of what is stated in the article.
But C++ would not be C++ if it had no shenanigans coming. Unless they have changed things within some months the new calendar/datetime library is hilarious.
It has features like being able to write a date like: 1/2/2000y. ooh fancy operatoe overloading. Which one is month? The first one ofcourse as it they didn’t standardize on any of the existing ISO standards, or allow you to choose, but selected ”Customary US way” as the Only True C++ Way(tm).
Also do not forget the abuse of operator overloading like in the good 90’s when misusing it was the recommended thing.
As far as I can tell from cppreference (I literally found out about the datetime extension to chrono right now), your example won't come mobile because it is ambiguous.
Why 'basic_fixed_string'? Is it really so basic that it has to be mentioned in the name? Is it just in case there might be a 'fixed_string' that isn't quite as basic, in the future?
what would you do instead ? surely you would not have std::string be a template directly and type std::string<char> / std::string<wchar_t> instead of std::string / std::wstring every time you want a string ?
cppreference doesn't have it yet, but as far as I can tell from P0259R0, basic_fixed_string is a template, both parameterized on the char type and the size N, and I can't see how it could be otherwise.
And no, if it weren't a template, it would not be possible to make it a template in the future as it would be a breaking change.
Not seeing a reason in this response for basic_${foo}, implying the existence of a ${not_so_basic}_string as was the original question. Sure typedef a template instantiation to get around language syntax awkwardness, that's fine. Why basic_string? why basic_fixed_string? Or is this just a curveball and it would have been better as
typedef _string<char> string etc
Or maybe not, I don't know what they had in mind, which is why I'm asking...
It does with newer compilers. Was some bugs with gcc and clang earlier.
Try:
#include <cstdio>
template <class T = char> class string
{
public:
string(const T *const c) : c_ptr(c){}
const T *const c_ptr;
};
int main()
{
string s = "qwe";
printf("%s", s.c_ptr);
return 0;
}
C++ should adopt Rust-like approach on deprecating things faster and a tools like `cargo fix`/`cargo clippy --autofix` to automatically fix/suggest more modern alternative to the deprecated language constructions.
What are people using C++ for in 2019? Last time I used it was for a Qt desktop application of all things. I would imagine its main usage is in high performance or hardware constrained environments these days, but I'm curious what people are doing with it.
I do a lot of node.js programming, which uses C++ to write extensions, so here's hoping I rememeber it if/when I need to.
Why I'm using it to write a Qt desktop program of course.
Actually a bit frustrating, since Qt is very opinionated in a way that hasn't aged well with newer C++ standards, but it is probably one of the best cross platform UI libraries.
I've really enjoyed programming in c++17 for the last three years (I had the luxury of starting from an empty buffer) and find the language pretty expressive. If you are able to use it as a "new" language, ignoring its C roots and features that exist pretty much just for back compatibility, it's really quite expressive, powerful, and orthogonal.
I'm actually pretty glad the c++ committee has been willing to acknowledge and deprecate mistakes (e.g. auto_ptr), to squeeze out special cases (e.g. comma in subscripting) and to attempt to maintain generality. Conservatism on things like the graphics standard is reducing the chance of the auto_ptr or std::map mistakes.