C++ is really hard. I have learned enough about it to understand that there's a lot of idiomatic ways of achieving things that I do not understand, especially with the latest standards.
When someone claims to know C++, especially in a recruitment context, I tend to challenge that, since it's really hard to actually believe these days. Some people claim to know C++, but basically just write everything as if it was C.
I have been programming in C++ for almost 17 years now and I would say I know maybe 20-25% at most.
The reality of C++ is you know what is needed for the problem you need to solve. As people mostly work in the same (broad) areas for their day to day work it is unlikely you need to know outside of that scope regularly. At least not in my personal experience.
Also I strongly believe nobody truly understands C++ streams :)
> functional programming (which is not that complicated at all in C++)
> I think most C++ programmers have a reasonable command of all of these
These gave me a bit of a chuckle. It's true only if you've never done proper functional programming using a genuinely functional language.
It's true that you can now pass lambdas around as first-class values easily, store and manipulate them as you might in a functional language, and that's a fantastic convenience.
But that's not what functional programming is. None of the idioms you'd use in Clojure or Haskell are wise to use (yet) in C++ (although Niebler's Range library will make a big step in the right direction... in C++20, where it's currently slated for specification).
If you try to do real functional programming in C++, you'll end up with a very inefficient program that pales to writing an algorithm with a more procedural or OO idiom.
And, in my experience interacting with lots of C++ developers, most do not know what actual functional programming is all about.
Sorry, but this is just simply not true. If you've never done functional programming before, then I could see why you think doing it in C++ is not complicated. Here is an example of just how large the can of worms gets when attempting to bring functional programming to C++:
And, I disagree that you can change the definition of a particular programming paradigm when discussing a particular language. Functional programming means something that is not really possible in C++. For starters, the fundamental underlying data structures for functional programming do not exist in C++ and there is no third-party implementation of them worth mentioning; the above experiment is admitted by the author to fall flat when doing anything idiomatic.
As I mentioned before, C++20 might turn the tide on this front a little bit, thanks to the Range library.
Fine, what shall we call this programming style in C++ (lambdas, immutability, function composition, etc) so that everyone is happy then?
Functional style C++?
I haven't felt the need to go deeper than that, it's very possible that I've missed many interesting things, but it's not clear that I can use those things and see tangible benefits in my projects today.
C++ OOP is an endless tire fire. I now have 7 years experience writing C++ "OOP" on the job and I still run into surprises when I try to do things that are easy in other languages but unnecessarily hard in C++. Plenty of examples in the C++ FQA[1].
I'm not an expert at this, but it seems to me that it would be a good idea to take the time to learn it properly in one go instead of independently discovering all the mistakes that can be made in 7 long years and then being upset about it.
> On the contrary, I think 80%+ of the language is accessible to every programmer.
80% of the language looks accessible to every programmer. Until you hit some corner of C++ behaviour and you realize your mental model of what C++ is doing is woefully incomplete. C++ is stuffed full of syntax, and obscure interactions between that syntax will continually trip you up. Each of the things in your list is somewhat contained on its own but interacts in surprising ways with every other thing on that list.
For example[1], want to create a template friend function for a template class? Prepare for function definitions like:
Foo<T> f<>();
Edit: that's not to mention various C++ idioms like CRTP [2] which don't fit neatly with the stuff you listed but are non-obvious and occur in a lot of complex C++ code.
This really starts to hurt when you go around shopping for C++ libraries. You can totally mandate a particular C++ style in an organization and achieve good results but good luck trying to use a library written in another style. Which is one of the reasons why C++ library ecosystem is relatively scarce.
It is one language. Many of those subsets are not mutually exclusive.
Regardless of what your preference is, if you claim to know C++ you need to at least know some of its basic features, such as references, streams, templates, STL, etc. (features that have been around for a long time).
And if you claim expertise, to at least be familiar with basic features from the new standards.
Sure, with an execution model that is so complex that it takes scientific experimentation to determine the /best/ (in terms of execution speed) to write code to manipulate a simple memory mapped device. (This, from the lecture).
That is so complex, it is the only language that I need the language at hand to read someone else's code. Not a library reference, a /language/ reference.
Indeed, I am maintaing one C++ application. This is a wrapper for CEF3 (https://cefbuilds.com/). This is the Chromium browser as a library. I have to support the library and wrapper on two platforms: Linux 64 bit and Windows 64 bit. Note that the usual Windows build uses the Microsoft C++ compiler. But, I am uses mingw. This requires source code edits to compile! I blame the complexity of the language.
Indeed, C++ is so difficult that the shop I work for (which is, by the nature of our product, very conservative) has gone to Java instead.
We did the same in 2006, and are mainly a Java/.NET consulting shop nowadays, but I still use C++ on side projects.
Given language difficulty, while I hope never to maintain C++ code that makes use of SFINAE, Java's simplicity is misleading.
The language might appear simple, but mastering the whole Java eco-system (JSE, JEE, Spring, embedded, Android, features per JDK version), performance monitoring tools, commercial JDKs and IDEs, isn't that easier either.
There's everything simple about Java. The semantics of Java are actually pretty simple, at least compared to C++. What's complicated are the monstrosities people build with Java, but those aren't inherent in the language.
Saying that Java is complicated because EE exists is like saying that C is complicated because Linux exists: Complexity can be built atop simplicity. And for some reason, Java is a complexity magnet.
I would say that the complexity of a language and the complexity of programs within that language are inversely correlated. Java is a very simple language, but as a result, the programs written in it must be more complicated to make up for the shortcomings of the language.
As an example, Java's lifetime rules are much simpler than those of C++. At some point after all references are gone, the object will be garbage collected. Simple. But as a result of this simplicity, you don't know when the finalizer will be called. You can't use it, for example, to flush and close a file, because you don't know when file will be closed. Instead, you need to use try/except/finally, and so you can't add this to an existing class, because it would require code modification from all the users of the class.
Up until Java 8 and lambda functions, functions could not be passed as arguments to other functions. Therefore, the developer needed to make an entirely new classes derived from "Callable". Complexity that came from the simplicity of the language.
C++ has a lot of complexity in the language itself. I won't deny that. What I will argue is that the complexity of the language enables simplicity in programs.
Edit: Lambda statements came in Java 8, not Java 7.
No, not really. Complexity is not about the language: It's about how you use it. It's not necessarily Java's featureset that makes it so complex, it's its idioms.
C++'s lifetime rules, on most variables, are this: It's allocated until you say it isn't. Some of the vars might be refcounted or GCed, but with GC you've still got the same problem on your hands as finalizers, and refcounting can't handle cycles. Manual memory management is dramatically more error-prone than finalizers ever were (unless you're using RAII, but that's not entirely idiomatic, and cannot be).
As for functions not being able to be passed as args, that's not simplicity, that's a straight up limitation. What makes a language simple is uniformity, simplicity, and consistency. Java has a good deal more of all of these than C++
Complexity in the language doesn't enable simplicity in programs, though, because now your program has to deal with 6000 different special cases.
In any case, Lisp and Scheme are possibly the simplest languages, and are celebrated for their power, clarity, and the simplicity with which they can express programs.
I'd be interested to hear why you say that RAII cannot be idiomatic. With `std::unique_ptr` and `std::shared_ptr` for handling pointers, `std::vector` for handling data arrays, `std::string` for handling char arrays, I would argue that modern C++ is very heavily RAII. At this point, if I have a class that cannot be declared on the stack, with cleanup handled by RAII, I consider it to be a broken class.
Good point on limitations vs simplicity. I think that there are different forms of complexity. Some complexity is necessary, due to the nature of the underlying problem. Other complexity is incidental, being due to poor implementations. As an example of essential complexity, C++ has different declarations for stack-based variables and heap-based variables, while Java does not. This is because Java does not allow classes to be declared on the heap, and so it does not need an extra form of declaration. On the other hand, C++ definitely has lots of incidental complexity as well, mostly due to its long history. Having four different constructs for loops is incidental complexity.
I agree that the idioms are what make Java programs be complicated, rather than the language itself. What I'm not sure on is how much those idioms are needed to overcome limitations in the language, and how much they are unnecessary parts of the culture.
Oh, absolutely on Lisp. Lisp/Scheme are amazingly simple, and amazingly powerful. The one downside that I've found is that it doesn't correspond to the hardware as much. Much of C++'s complexity stems from trying to provide as many features as possible while still compiling down to reasonably fast code.
The main issue with RAII is that you can only do properly if as you say, the classes were designed to be stack allocated.
Also that no one just placed such class in the the heap, and it was missed, because no one is actually doing code reviews or making use of static analysis.
Another issue with best practices and ownership are binary libraries. There isn't any sort of control one can have over them, so they are the place where RAII and ownership just goes out of the window.
In any case this is much worse in C than C++, because at least C++ does offer some language tools to deal with it, however there is no rescue from developers using C with a C++ compiler.
> Oh, absolutely on Lisp. Lisp/Scheme are amazingly simple, and amazingly powerful. The one downside that I've found is that it doesn't correspond to the hardware as much.
Actually it can also be an upside, as you see with the adoption of FP patterns in mainstream languages, including C++.
One of the themes at CppCon was exactly hardware heterogeneity and possible C++ abstractions to take advantage of it, while keeping the algorithms high level.
I feel that the solution to both classes designed for heap allocation and binary libraries is the same: making a decent wrapper around them that obeys RAII. In many cases, this is just requires using `unique_ptr` with a custom deleter.
(Note that the example there is only for use within a single function. If the safe_wrapper is to be returned from a function, then the copy/move operators should be defined as needed.)
Certainly, when passing ownership back to the binary library, you are relying on it to correctly handle ownership. But so long as the ownership is in one's own code, you can easily add the type safety.
Regarding a stack-based class being accidentally placed on the heap, you can't prevent all errors, you can only make it less likely. I'd argue that it is easier to accidentally forget to call a cleanup function than it is to accidentally place something on the heap. With C++11 and up, any calls to "new" should be regarded with deep suspicion.
True on the FP patterns moving in, and I love them. `std::function` is a joy to work with, especially as compared to either C-style function pointers or inheritance from a "callable" type.
> The one downside that I've found is that it doesn't correspond to the hardware as much.
The hardware keeps changing. What Lisp provides is a way to access low-level details when necessary, but with a default mode of operation that is managed (developer-friendly): GC, safety checks, arbitrary precision integers, etc. Like for all languages, a compiler can be told to try more optimizations if you want, and if you are ready to spend a little more work on it. In some implementations, you can extend the primitives known the compiler to emit better assembly code (e.g. https://www.pvk.ca/Blog/2014/08/16/how-to-define-new-intrins...).
Well then, that's your problem: C++ is trying to be both low-level and high level at the same time. This isn't a good idea, IMHO.
What I mean is that while some amount of RAII is a common idiom in C++, It's rare for a program to use RAII fully, as many C++ idioms conflict with it. Mind, I'm not an expert, so I might be wrong, but that was my understanding. AFAICT, if RAII was used all the time, idomatic C++ would look a lot more like Rust.
> Well then, that's your problem: C++ is trying to be both low-level and high level at the same time. This isn't a good idea, IMHO.
It is a very good idea and C++ isn't alone there.
Professional Basic dialects, Turbo Pascal, Delphi, Modula-2, Modula-2+, Modula-3, Ada, D, Rust, Mesa/Cedar all share this idea that you can program at both levels, depending on the needs of the use case.
C++ has less distinction though, and it's so huge that even Bjarne can't keep the whole language in his head. The net result is that many people use the low-level parts of the language for high-level work and vice-versa.
The same applies to the languages I listed, given the amount of years they have.
C++ main issue has always been C's compatibility and the C subculture.
The copy-paste compatibility was necessary to bring C developers over the fence to C++, with minimal changes to their tooling.
So C++ inherited all the flaws and UB from C.
Then many of those kept using C++ as plain C with Classes, which influences many of the design decisions regarding language semantics, specially given backwards compatibility as no one wants to repeat Python's error.
Having said this, although I enjoy playing with template metaprogramming, I hope never have to deal with SFINAE, declpspec and function return arrow syntax in production.
...But that's not often how those languages are used.
And most of those "flaws" aren't flaws in C: They were deliberate design decisons. Arguably bad ones, yes, but they were made a reason. By C++, most of those decisions didn't have any reasons, or were actively against C++'s goals, if not C.
I know you hate C, but blaming C for C++'s problems is like blaming dogs for the monstrous dog demon that someone made with gene editing. C compatability was a bad idea, but you can't blame C for that. Also, it's overly baroque.
But yeah, I think we can all agree that complex TMP is kinda sucky.
It's not Lisp, but OTOH, C++ doesn't need puzzles: Template Metaprogramming, as well as many other parts of the language, are already one. And god help you if multiple programmers on a project use different subsets of the language...
Your list of common features is a bit too broad. For instance a common sentiment among game programmers is "STL is shit, you better don't touch it at all".
What's the reasoning? I haven't regularly used c++ for ~8 years, but I know the basics. I thought the downside to STL was increased executable size? If there are no generic constraints I can imagine issues popping up; effectively using generics in .NET often means using a non-generic abstract base class, building the generic class on top of that, and then constraining the generic type to the abstract class. A pain compared to Java, where you have runtime type erasure, but worth it IMO for the better reflection. I figure there's some similar quirk/gotcha in c++?
I guess the main gripe is performance. Using STL containers involves lots of implicit heap allocations and copying things around (this last thing should have improved after the introduction of move semantics, does anyone have any numbers on that?). Most of the times performance is perfectly acceptable but a few cases when it is not + inertia can give rise to this anti-STL sentiment.
But in terms of knowing the latest features, it might be hard to keep up with the latest developments in C++, especially since even if a standard is released, it takes some time for compilers to implement them.
The same is true of any language with similar history.
Do you believe anyone knows Perl, Python, Ruby, Java, C#, Ada, Haskell, OCaml, VB.NET, F#, ... across all language versions, the whole standard library, and most used third party libraries?
The obvious comparison is C — where it is common to know 95-100% of the language. (The 5% I'd pick is general disagreement about expectations of UB and "weird" pointers, like pointers to array types.)
People that don't write portable C code think that they know 95-100% of the language.
I don't miss the days in the late 90's, early 2000, writing portable C code across multiple compilers from each OS vendor, across all major UNIX flavours and Windows.
Many that think to master C, actually master C in compiler X targeting OS Y.
However I prefer that pain, to the lack of strong typing that C++ offers over C.
But better would be to use something else instead of C and C++, more type safe, without UB and without the wide range of implementation differences and compiler extensions.
What sort of thing do you want to read?
I have tagged posts on my blog: http://daurnimator.com/tagged/lua but that's probably mostly library release annoucements.
When someone claims to know C++, especially in a recruitment context, I tend to challenge that, since it's really hard to actually believe these days. Some people claim to know C++, but basically just write everything as if it was C.