Table of Contents for Programming Languages: a survey
C
Because it is so well-known and well-liked, C gets its own chapter.
See chapter on chapter on C.
C++
"In C++’s case, the Big Idea was high level object orientation without loss of performance compared to C." [1] (my note: and backwards compatibility with C; and close-to-the-metal low-level operation)
" C++ is a general-purpose programming language with a bias towards systems programming that
is a better C
supports data abstraction
supports object-oriented programming
supports generic programming " -- http://www.stroustrup.com/C++11FAQ.html#aims" ...C++’s fundamental strengths:
- A direct map to hardware (initially from C)
- Zero-overhead abstraction (initially from Simula) " -- Thoughts about C++17 by Bjarne Stroustrup
Attributes:
- Imperative
- Compiled
- Low-level
- OOP
Pros:
- Backwards-compatible with C.
- "You only pay for what you use"; that is, although the language does provide optional abstractions which require more time or memory overhead than C, these are always optional.
- "support for object-oriented programming, generic programming, traditional procedural programming, and multi-paradigm programing combining elements of those." -- A rationale for semantically enhanced library languages
- "Classes plus templates plus overloading is the basis of expressiveness and performance." -- A rationale for semantically enhanced library languages
Cons:
- Big language
- Many of the most obvious ways to do things are antipatterns. Must learn best practices to avoid writing buggy code. Cite e.g. http://bartoszmilewski.com/2013/09/19/edward-chands/ , https://news.ycombinator.com/item?id=6418165
- Build time with many header files
- "How did we get here? Well, C++ and its stupid O(n^2) compilation complexity. As an application grows, the number of header files grows because, as any sane and far-thinking programmer would do, we split the complexity up over multiple header files, factor it into modules, and try to create encapsulation with getter/setters. However, to actually have the C++ compiler do inlining at compile time (LTO be damned), we have to put the definitions of inline functions into header files, which greatly increases their size and processing time. Moreover, because the C++ compiler needs to see full class definitions to, e.g., know the size of an object and its inheritance relationships, we have to put the main meat of every class definition into a header file! Don't even get me started on templates. Oh, and at the end of the day, the linker has to clean up the whole mess, discarding the vast majority of the compiler's output due to so many duplicated functions. And this blowup can be huge. A debug build of V8, which is just a small subsystem of Chrome, will generate about 1.4GB of .o files which link to a 75MB .so file and 1.2MB startup executable--that's a 18x blowup." [2]
Good for:
- low-level code (for systems programming or for performance) when more abstraction is desired than C can provide
- simulations; simulations are generally CPU-bound programs that take a long time to run and hence demand efficiency. The OOP paradigm is generally a good fit; types of domain objects are usually represented by object-oriented classes.
Best practices and style guides:
References:
Retrospectives:
- http://www.stroustrup.com/hopl2.pdf A History of C++: 1979−1991]
- The Design and Evolution of C++
- "..In D&E §9.2.2, I outlined fundamental design criteria for C++: What would be a better language than C++ for the things C++ is meant for? Consider the first-order decisions:
- Use of static type checking and Simula-like classes.
- Clean separation between language and environment.
- C source compatibility (“as close as possible”).
- C link and layout compatibility (“genuine local variables”).
- No reliance on garbage collection.
- " -- [research.att.com/~bs/hopl-almost-final.pdf]
- Evolving a language in and for the real world: C++ 1991-2006
- "Were I to design a new language for the kind of work done in C++ today, I would again follow the Simula model of type checking and inheritance, not the Smalltalk or Lisp models."
- "a couple of language-technical criteria:
- Provide as good support for user-defined types as for built-in types
- Leave no room for a lower-level language below C++ (except assembler)"
- "A good tool would have Simula’s support for program organization – that is, classes, some form of class hierarchies, some form of support for concurrency, and compile-time checking of a type system based on classes."
- "A simple linkage convention is essential for combining units written in languages such as C, Algol68, Fortran, BCPL, assembler, etc., into a single program and thus not to suffer the handicap of inherent limitations in a single language"
- "A good tool should allow highly portable implementations.... a tool must have multiple sources of implementations (no monopoly would be sufficiently responsive to users of “unusual” machines and to poor graduate students), that there should be no complicated run-time support system to port, and that there should be only very limited integration between the tool and its host operating system"
- "Language-technical rules:
- No implicit violations of the static type system.
- Provide as good support for user-defined types as for built-in types.
- Locality is good.
- Avoid order dependencies.
- If in doubt, pick the variant of a feature that is easiest to teach.
- Syntax matters (often in perverse ways).
- Preprocessor usage should be eliminated."
- "Low-level programming support rules:
- Use traditional (dumb) linkers.
- No gratuitous incompatibilities with C.
- Leave no room for a lower-level language below C++ (except assembler).
- What you don’t use, you don’t pay for (zero-overhead rule).
- If in doubt, provide means for manual control."
- "There is a direct mapping of C++ language constructs to hardware"
- "The standard library is specified and implemented in C++"
- "C has those two properties ((direct mapping to hardware, and stdlib implemented in the language)), but not the abstraction mechanisms needed to define nontrivial new types"
- "To be concrete, I consider the STL a model example: had Alex Stepanov proposed just a few containers or just a few algorithms, that proposal would have appeared to be simpler, but it would have had far less positive effect than the STL. In fact, the STL was transformative. I hope that concepts and a standard library based on concepts will be another model example, rather than an example of how "design by committee" and risk aversion can derail a good idea." -- Thoughts about C++17 by Bjarne Stroustrup
- http://www.cplusplus.com/info/history/
- "Most criticisms of C++ fall into two categories, the legacy of language constructs that descended from C, and its static (compile-time) type checking system, which purists view as being less than object-oriented. Stroustrup deals with both. First, he could have built a better language instead of a better C. He could have assigned less importance to compatibility with C. "Within C++, there is a much smaller and cleaner language struggling to get out," which he says "would ... have been an unimportant cult language." Second, he is committed to the concept of static (as opposed to dynamic) type checking as being inherently safer and essential to retain the efficiency of C. Without that guarantee, programmers used to C's efficiency will not switch to a new language no matter what promise it holds." -- http://www.stroustrup.com/dne.html
- https://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote
- https://mariusbancila.ro/blog/2022/01/01/the-evolution-of-functions-in-modern-cpp/
People:
Tours and tutorials:
Reference/details:
Books:
Opinions:
"With C++ I at least have the escape hatch of going back to C. I started using some of the C++ 11, 14, 17 template and compile time programming stuff: constexpr, etc. Some of it is OK. There are lots of holes. When there’s a hole, I go back to a C macro! That has worked well enough many times. So ironically C++ has incredible complexity, but you can always avoid it by programming in C :) On the other hand, the template stuff can be useful. I kinda cringe when I realize someone’s first experience with systems programming could be in Rust. But then again I also cringe at how long it took to be productive in C and C++ too (more than a decade). We’re not there yet :) " -- [10]
- "This is my main issue with C++. For a while my job was to get game engine codebases running, integrate tools and move on. So I saw a lot of big C++ codebases. Nearly every one had the same bad behaviors. Tons of globals. Configuring build options from code. Header mazes that made it clear people didn't actually know what code their classes needed. I then worked for awhile developing a fairly fresh C++ code base. The programers I worked with were very willing to write maintainable code and follow a standard and it was still really damn hard to keep things like header hygene. When I go back to the language I can't believe how much time I spend dealing with minor issues that stem from the bad habits it builds. For years I would refuse to say any language was good or bad. Always I insisted you use the right tool for the job. And there are some features of C++ that when you need them you have to use that language or maybe C in its place. But the shortcomings are unrelated to language's issues which largely seem to come from a focus on backward comparability. And so even used in its right application it seems incredibly flawed. And I pretty much believe it's a bad language now. Disclaimer: I learned to program with C++, I understand its power and for years I loved the language. I also understand there are situations where despite its shortcomings it is the right choice. " [11]
- https://danluu.com/boring-languages/ contains a list of important software written in C++
- "Generally speaking, what killed C++/C for me is the absolute insanity that comes with it's build systems/third party dependency management. I feel like the notoriety that C++ gets for it's absurd complexity comes not from variadic template metaprogramming/rvalue references/unique_ptr or whatever, but the absolutely humongous effort needed to understand automake/CMake/autotools just to build other people's code (god forbid on a different platform than the original author)." [12]
- "Many years ago, when C++ was more or less where Rust will be in a few years, you’d hear things like “oh yeah, we’re a C++ shop, but we don’t use multiple inheritance/exceptions/whatever”. Because of the sheer complexity of the language, very few people really knew, like, all of it, and most of them were either the ones writing compilers, or language evangelists who didn’t have to live with boring code for more than a consulting contract’s length. This led to the development of all sorts of “local dialects” that made integrating third-party code a nightmare. This is, I think, one of the reasons why Boost was so successful. For a long time, there were a lot of things in Boost that were much better implemented elsewhere. But getting these other implementations to work together – or, actually, just getting them to work with your compiler and compiler flags – was really unpleasant." -- [13]
on C++ interop:
- "Name decoration isn't the hard part; it's types that are hard. Remember that a simple string is a std::basic_string<char, std::char_traits<char>, std::allocator<char>>; passing a string from one place to another requires either sharing the latter two, or having a way to convey pointers to what the latter two do. And that's not even considering things like, oh, throwing an exception whose class uses multiple inheritance and virtual methods. Take a look at the Itanium C++ ABI, the de facto standard on GNU/Linux etc. for a sense of the scale of the problem: https://mentorembedded.github.io/cxx-abi/abi.html And that doesn't even standardize the layout of the actual types in std::* themselves, just the typesystem. That just gives you enough information to correctly access someone else's implementation of std::string; you still need to have that precise implementation around at compile time. There is, however, a proposal to standardize the layout of std: https://isocpp.org/files/papers/n4028.pdf To be fair, you have the exact same problem when trying to call into e.g. Rust from e.g. C. Rust's typesystem is complex and its libstd is involved and not stabilized, so the easiest way is, again, to route through C types instead of trying to access Rust's String type in C. " -- [14]
Comparisons:
Examples of good code:
Best practices:
Misc:
- The name makes it regrettably hard to search for on the web using some search engines.
- https://cppinsights.io/ "C++ Insights is a clang-based tool which does a source to source transformation. Its goal is to make things visible which normally, and intentionally, happen behind the scenes. It's about the magic the compiler does for us to make things work. Or looking through the classes of a compiler."
Types:
C++ Features
References
References are kind of like pointers, but with a few differences. Some of these are:
- they are implicitly dereferenced; if "x" is a reference to "y", then writing "x" implicitly refers to "y", you don't have to write "*x"
- they are bound to their target at initialization and cannot be rebound
For example, say you want to write a '++' operator for a type that doesn't have one. You have to use references for two reasons:
- ++ mutates its argument, so you can't use pass-by-value. And you want the client to write "i++", not "&i++". So it should be a reference, not a pointer. [17]
- the way C++ polymorphism works, you can't say "here is an ++ method to use on pointers of this type"; if you say "objectType *operator++(objectType *i);" that is interpreted as a redefinition of "++" for all pointers, not just for pointers of to int. (todo: that may be wrong, i'm not sure i understood this correctly) [18]
Links:
C++ Features: Concepts
discussion (with comparison to Rust trait syntax and semantics):
logicchains 1 day ago
>That said, I've mostly reached the conclusion that much of this is unavoidable. Systems languages need to have lots of detail you just don't need in higher level languages like Haskell or Python, and trait impls on arbitrary types after the fact is very powerful and not something I would want to give up.
Have you checked out C++20 concepts? It supports aliases and doesn't require explicit trait instantiations, making it possible to right such generic code with much less boilerplate.
reply
loeg 1 day ago
My experience in C++ prior to 20 is that it is a lot more verbose/boilerplatey than Rust. I'd love to see that get better, but I think C++ is starting from significantly behind.
reply
jcelerier 1 day ago
C++17:
template<typename T, std::enable_if_t<std::is_floating_point_v<T>, int>* = nullptr>
void func(T fp) { ... }C++20:
void func(std::floating_point auto fp) { ... }reply
Frizi 1 day ago
There is an equivalent syntax in Rust to both of those examples, and in both cases I find it less verbose. The template variant is roughly equivalent to:
fn func<T: FloatingPoint>(fp: T) { ... }And the "auto" variant is similar to impl argument in Rust:
fn func(fp: impl FloatingPoint) { ... }reply
jcelerier 1 day ago
I really don't see in which way the second case is less verbose especially if you add a non-void return type, e.g. i32. The first case would also be doable like this, which is pretty munch the exact same than your first example with the added "template" keyword
template<std::floating_point T>
void func(T t) { ... }(also, it's not really equivalent - if I'm not mistaken with traits you can only use what the trait declares ; in C++ you can for instance do something like
template<typename T>
concept CanBlah = requires (T t) {
t.blah();
};and still be able to do
void func(CanBlah auto t) {
log << "func:" << t;
t.blah();
}instead of polluting the prototype with all possible side concerns)
reply
tialaramex 1 day ago
> instead of polluting the prototype with all possible side concerns)
C++ Concepts are duck typed, and Rust's Traits are not, so in Rust you are expressing meaning here, and in C++ only making some claims about syntax which perhaps hint at meaning.
WG21 seems to dearly wish this wasn't so, offering Concepts which pretend to semantics they don't have, such as std::totally_ordered and I'm glad to see your "CanBlah?" concept doesn't do this, to be sure all things which match this requirement can, indeed blah() although we've no idea what that can or should do.
Once you've accepted that you only have duck typing anyway, you're probably going to have to explain in your documentation the actual requirements for this parameter t, as the prototype merely says it CanBlah? and that's not actually what we care about.
In contrast the Rust function we looked at actually does tell us what is required here, something which "implements FloatingPoint?", and that implementation (plus the data structure itself) is all that's being exposed.
reply
C++ features: coroutines
C++ Opinions
Favorite parts:
- Deterministic destruction, Manual memory management / no GC, Templates, C interoperability, operator overloading -- [19] [20]
Critical:
- http://www.fefe.de/c++/c%2b%2b-talk.pdf
- http://yosefk.com/c++fqa/
- "ambiguities in the grammar (declaration/definition, type name/object name...), duplications of functionality in the different features (pointers/references, constructors/aggregate initialization, macros/constants/templates, files/namespaces...)."
- "The single biggest problem with C++ classes is that private members are written in header files, so changing them requires recompiling the code using them - for important practical purposes, this makes private members a part of the interface.
- http://yosefk.com/c++fqa/defective.html
- "Talking with other C++11/C++14 users here, the general feeling is that, yes, C++ is a good language because of the performance you can get out of it and some of the nice features that have been added, but the language carries around a lot of cruft that is unavoidable due to its age and maturity. C++ has always been an expert's language. These days it's the expert's expert language++. There are no shortages of landmines and gotchas in the C++ landscape." -- https://news.ycombinator.com/item?id=9274328
- What's Wrong with C++ Templates? compares C++ templates to Java interfaces, ML modules, and compares template metaprogramming to Lisp macros
- "It’s interesting to read that in the context of C++20. Concepts address a lot of the type checking problem: if you use concepts, then you don’t have the deferred type checking problem, your code is guaranteed to work with any template instantiation that matches the concept definition. A lot of the comparisons to Lisp macros are now addressed via constexpr and constinit functions: they look exactly like normal functions but are evaluated at compile time." -- David Chisnall
- https://news.ycombinator.com/item?id=24855401
- "As a non-C++ coder there are two things that "scare" me about C++ (ok, maybe not scare, but are definitely big turn offs): - lack of a proper, cross platform, package manager...- template meta-programming plus the C++ legacy features which are probably lurking in many third-party libraries ... There's also the compilation speed, but as far as I know there are workarounds for that." [21]
- "The real issue that should scare you is something called "undefined behavior". It's very easy to do subtle things that cause you programs to break in weird and hard to debug ways...There are tricks and tools to avoid undefined behavior, but I find that most C++ engineering orgs don't prioritize the tooling (linters, UB sanitizers, etc.) needed to let engineers do this productively. Consequently, a lot of writing good C++ involves detailed coding standards and/or very detail-oriented gurus poring over code reviews. " [22]
- "And array over-runs are just the beginning! (friends don't let friends use idices over iterators) Had a fun bug at work once where we somehow accidentally built against two subtly incompatible versions of the same library. This actually mostly worked until some random virtual function call sometimes called the wrong function silently..." "Yes, C++ without a monorepo is very difficult." [23] and [24]
- "Yeah, undefined behaviour was my thought too. There's a few warts with templates etc. but undefined behaviour is the granddaddy of all skeletons on the linguistic closet. Realistically, C++ isn't actually a well-defined language unless you specify platform and language. You don't program in C++. You program in a C++-derived language jointly defined by the compiler and the hardware." [25]
- "The things that concerned me about C++ were the pitfalls that could happen due to ignorance or omission vs. errors introduced by commission. For example forgetting to implement a copy constructor for a class that allocates member data. (That results in two objects that hold pointers to the allocated data following a copy operation and if one is deleted, the other points to now deleted data.)...My other concern is how big the language has grown." [26] (see also followup comments on that thread for how using shared_ptr or unique_ptr may or may not help with this particular issue)
- "There are many dark corners, historic baggage, and complex interplay of language features in C++. This complexity breeds bad codebases plagued by subtle bugs created by misunderstanding of finer points of the language. If these things scare you, run away in the other direction as fast as possible." [27]
- "The thing I love about using C++ is that you get direct access to all these rock-solid libraries in C that everything is built on -- zlib, libcurl, etc. So instead of dealing with a tower of wrappers in a high-level language, each of which has quirks and bugs, you get that core lib, where you can safely assume 99% of the time any problem is your own fault. Even when a C++ wrapper exists, I use the C API just to reduce deps and avoid the kind of problems you're talking about, which I think boil down to templates most of the time. I don't use templates unless there is no other way...I heavily use the STL and greatly prefer it to C idioms. I also like namespaces. Templates can be very hairy but void pointers are not great either. In short, I simply think C++ can be used responsibly if you limit yourself to the subset of features that you (or your team) can understand....the thing I miss most from Python in C++ is yield and generators, which hopefully coroutines will eventually help with. Defining your own iterators is very annoying. " [28] and [29]
- detailed examples of how C++ is a big language: https://news.ycombinator.com/item?id=24839732
- "My main issue with template, aside from the slow compilation time, is that they are most of the time very difficult to read." [30]
- "Simple templates are not that hard to read. They can be tricky to write if you have things like templates using templates and specialization. But once you get the compiler to understand what you want, reading the code is usually not a problem. Hell breaks loose when you start doing "metaprogramming". I did a bit of it and it felt like I was exploited the compiler in clever ways not intended by the original authors, SFINAE checks in particular. And to be honest, I think it is the case, or some people should have their head checked. From the look of it, some modern C++ features attempt to fix that mess, notably concepts in C++20." -- [31]
- "It seems that templates are becoming more of the core of C++ abstractions as the language picks up functional idioms and people are moving away from overusing/abusing object oriented designs. Concepts are making templates much accessible and easier to work with. Link time optimization helps reduce some of the template bloat and virtual call overhead. As much as people seem to be ragging on C++ lately, it keeps getting better and better." [32]
- vs Rust: https://news.ycombinator.com/item?id=31435739
Other:
- "I love seeing the difference between Python and C/C++ coder cultures: To a Python'er, bonus points are given for performance and friendly coding is nearly assumed. To a C++'er, bonus points are awarded for friendly coding, and performance is nearly assumed. (Even more, I love how we're seeing some convergence here: no-compromise improvements to weak spots on each "side" of the programming language spectrum.)" [33]
- vs Python: lambda expressions (from [34]: " Python
def adder(amount):
return lambda x: x + amountC++
auto adder(int amount) {
return [=](int x){ return x + amount; };
}" about the [=]: "turns out there are various forms of Lambda (of course there wouldn't just be one in C++), for ex: [a, &b], [this], [=], [&], []
Depending on how the variables are captured. " "The stuff in square brackets is the capture list. Unlike python, which implicitly captures the full outer scope by reference, C++ allows/requires you to specify which outer variables you want to reference in the lambda, and whether they are captured by value ("=") or reference ("&"). This allows C++ to implement the lambda with a simple static function in most cases, where python needs a bunch of tracking data to preserve the outer stack from collection." 00 [35]
- "I personally think C++ still has a role in the world, as it clearly has lots of adoption. But if you want non-Ugly then those various use-cases have to be broken up into single-purpose (or niche) languages such as Rust for systems programming and Python for 'scripting' and Lua for glue/config code. All of those languages listed have made hard tradeoffs to be good at certain things. C++ and Java did not - largely because they are "yes" languages, as Steve Yegge calls it (the language designers said yes to everyone early on)." [36]
- C++ "was my #2 choice for writing a middleware for Elasticsearch, however I ultimately chose Golang because I didn't want to deal with CMake." [37]
- "...fibonacci numbers at compile-time. You can do those things with regular functions now, and just marking it as constexpr or constinit." [38]
- "The problem with templates is that it's a new language on top of an existing language. Why not use the same language for types as you already have for values?" [39]
- "Concepts are a recent addition, but not the only one that has improved compile-time or static programming in C++. The `constexpr` specifier, for instance, was added in C++11 and with every release since then it's been able to replace more and more complicated template metaprograms with just regular C++ code. Other things like generic lambdas and return type deduction also serve to make template-heavy code simpler to both write and read." [40]
- " I did not care much for C++ templates, until I moved to dynamic languages and understood the power. In dynamic languages I hated the inability to know if I was passing in an incompatible type. Templates are a perfect combination. The main problem with templates are the compiler error messages." [41]
- " C already has a replacement, and that replacement is, manifestly, C++. Essentially all of the most demanding, highest paid system development is done in C++. The number of C++ programmers, judging from attendance at rapidly proliferating C++ conferences and seminars, including even ISO 14882 SC22/WG21 C++ Language, is growing faster than ever. Attendance at each WG21 meeting in the last several years has exceeded that of all previous meetings." [42]
Why it compiles slow:
- "Aliasing issues, C++ heavy reliance on virtual methods, too many levels of indirection in the STL, overuse of signed integers due to "int" being easier to type interfering with loop analysis, const being useless for optimization, slow parsing of header files... " [43]
C++ advanced practices
C++ Gotchas
- "{mutex_guard(some_mutex); foo();}" "locks some_mutex, then immediately unlocks it, then calls foo()", as opposed to "{mutex_guard guard(some_mutex); foo();}", which does what you want (unlock the mutex AFTER foo) -- [44]
- "overly agressive implicit conversions (incl. the array-to-pointer conversion)" "unchecked unions" -- [45]
"the major insecurities in C++ code:
- Buffer overruns - i.e., reading or writing outsider the range of an array
- Dereferencing an uninitialized pointer, a zero-valued pointer, or a pointer to a deleted object
- Misuse of a union - i.e. write a union variable as one type and read it as another
- Misuse of a cast - e.g. cast an int to a pointer type where no object of that type exist where the new pointer points
- Misuse of void* - e.g. assign an int* to a void* and cast that void* to a double*
- Deleting an object twice, not deleting an object after use, or using a pointer after deletion. " -- modified from [46]