17 ms·
“Modern” C++ Lamentations
- Mauricio_ 8y agoThe problem is not modern c++, it's only when c++ tries to be a functional language that it becomes unreadable. As I see it, there are multiple "sub languages" in c++: -Macros: use as little as possible. -Classic C: use only when interacting with C libraries, or in the few cases where it's more convenient (printf over cout). -Classic C++ (classes, etc): use it all the time. -Template meta programming: use only for simple things, eg containers. -Functional C++: try to avoid.
- muterad_murilax 8y agoHow about going back to and forking C++98 (or whatever version preceded the first syntacical horrors such as reinterpret_cast<>) and start over with more C-like additions.
- deleted 8y ago[deleted]
- makapuf 8y agoYeah but what about implementing the real compiler after the spec? While you're at it why not make it a different language?
- stabbles 8y agoIt's amazing how much of a stir the C++20 ranges have caused. Very entertaining to follow.
- gavanwoolery 8y agoThings developed by committees often tend to lack the market-driven features of privately-owned products. On the other hand, privately-owned products are just that, and suffer from the usual consequences of closed systems. Would be nice if there was an intersection of the two, somehow (C# sort of comes to mind, as it was privately developed but has open-source implementations). The one I always think back to is DirectX vs OpenGL. DirectX has had historically better driver support, documentation, and ready-to-compile example code (OpenGL has caught up a lot admittedly over the years, due to greater proliferation of OpenGL-driven devices).
- gbear605 8y agoJava has achieved a lot of this.
- ilovecaching 8y agoIt's really crazy how much C++ has already evolved since the start of my career (with C++98). C++ has been the millennium falcon of languages; creaky and old, not pretty to look at, but still good where it counts. That said, I completely switched over to rust many months ago and rarely look back. Rust is a tribute to C++, an incorporation of lessons learned that sheds all of the baggage C++ will never be rid of. Rust also has less of learning curve than C++ (although both are quite high), simply because C++ has so many odds and ends to keep track of these days. Rust has also made me realize how bad class based OO is as an abstraction. OO glues together operator overloading, methods, inheritance, all into one package. Rust breaks those concepts into component parts that provide much better abstractions with less keywords and jargon to worry about.
- kbwt 8y agoI've personally been watching Rust from the sidelines. I am still waiting for a language to tackle excessive compile and link times.
- monetus 8y agoI've not used it for anything too big, but D compiles incredibly fast.
- kbwt 8y agoRecent article regarding D compile times: https://blog.thecybershadow.net/2018/11/18/d-compilation-is-too-slow-and-i-am-forking-the-compiler/ https://blog.thecybershadow.net/2018/11/18/d-compilation-is-...
- gmueckl 8y agoIt is very fast for certain projects, but link times on some can make up for that. I have a toy program using vibe.d that takes around 1s to compile and 10s to link. I could probably make that faster by switching to gold, but I didn't yet.
- 8y ago
- mschuetz 8y agoI was really looking forward to C++ ranges. However, that blog post by Eric Niebler felt like C++ ranges are trying to get everything right from a cs point of view, but in the process completely ignores usability / legibility, just like the previously introduced list operators that suffer from the requirement to specify start and end iterators. Some of the code samples are an atrocity. That C# example looks so much better to read and easier to understand.
- munk-a 8y agoI put a lot of value into the maintainability of what I'm writing and I think legible syntax is a language quality that's often minimized but provides a lot of value in terms of labour savings over the lifetime of a project. I've been working in PHP a bunch lately and the expressiveness there is amazingly strong, every time I go back to writing C++ it feels much more cargo-culty. (Also, PHP isn't perfect, but it's really good at allowing clear readable code, while also allowing terrible code, also the performance is terrible... Basically, don't waste your breath on why PHP is terrible - it isn't, it's a decent language - you're just using it wrong)
- zackmorris 8y agoI concur. My biggest gripe with C++ today is that it's conflating two concepts: imperative programming (IP) and functional programming (FP) via templates. Imperative programming has no future IMHO. Rust is going to the ends of the earth to make it "safe" but my gut feeling is that it will never be fully statically analyzable. Today I only see needless complexity that distracts from the underlying logic. I lost interest in this style of programming around a decade ago. Functional programming via templates is an admirable endeavor, and I went all the way to the ends of the earth to make something of it. I was left with code that wasn't reusable (wasn't salable) so in the end, just wasted years of my life on needless complexity. And both of the above only make sense from a performance perspective. An argument that becomes weaker with each passing year as processing speed improves. I think the future will be something more like reactive functional programming, written in a high level language like Javascript and only dropping down to bare metal optimizations with something like Rust when necessary. It will work like the UNIX shell (which is the only proven model that's endured). So something like the Actor model where a series of black boxes are wired together with pipes and as much FP as possible, and any IP sections are treated like monads and flagged as having unknown side effects. ClojureScript does something like this, running its FP code to completion and then yielding until more input/output happens on the Javascript side. Another way of thinking about this is a spreadsheet with some imperative code within the cells where needed. PHP is my favorite language currently because it has some notion of constness and fairly decent higher order functions (but who are we kidding, map and reduce give us everything else). PHP is a conceptually simple language that has some longstanding flaws with naming conventions that make it not as easy as it could be. It's largely succeeded in moving away from object-oriented programming, favoring a fairly simple interface implementation instead. I've never run into the implicit context problems that plague languages like Ruby (where convention over derivation is written into its DNA). And PHP is fairly fast, on the order of 200 times slower than C++ but probably within the same order of magnitude as the newer C++ methods mentioned in the article. My gut feeling is that proper string handling, with every contingency accounted for, runs approximately the same speed in any language. I'm not willing to spend time and effort trading safety for performance anymore.
- HippoBaro 8y agoI'm working with C++17 right now and I personally can relate to the author. Reading the discussions around C++20 has been an eye-opening experience for me. C++ was already a difficult language but has become a behemoth of complexity. But it has to be said that once you know "Modern" C++ it's a very productive language. Somehow, it feels good to use it and after a while you find yourself reveling in complexity. I'm almost ashamed to say really.
- lallysingh 8y agoThe complexity in the language is gameified a bit. It's a terrible sport where everyone suffers.
- munchbunny 8y agoMy personal favorite metaphor for the complexity of C++ (and other languages, like Scala) is a hamster wheel. You enjoy writing it, but from the outside it feels a bit ridiculous to see the amount of energy dedicated to problems of your own making. R-value references, for example. This is entirely a problem that C++ has created for itself with its copy semantics. From inside the C++ ecosystem, the language feature makes sense, but from outside the C++ ecosystem, the idea that there are now two types of references and two types of copy semantics triggered based on the implicit interaction of reference types, and you sometimes need to do idiomatic things like declaring undefined private copy constructors to lock down unintended implicit behaviors relating to copy semantics for R-value references... yup, hamster wheel. C++ isn't the only language where I get this feeling. I think Scala is pretty guilty of this too. It's not that I can't grok it if I wanted to (survived my share of proof based, pure theory math classes back in college), it's that I think it's entirely too much energy to be spending on mundane code.
- jstimpfle 8y ago> two types of references include normal pointers and it's three.
- ryanianian 8y ago
- petermcneeley 8y agoprintf code should not appear in timed scope.
- tom_ 8y agoWhy not? It clearly doesn't add anything meaningful to the run time, and helps avoid the question of whether the loop got optimised out.
- petermcneeley 8y agoI timed his code on Windows and it certainly adds to the runtime. To prevent the loop from being optimized out I created a pre-sized vector to index the ith ouput and print it outside the timed scope. I have worked in the game industry and have many bad experiences with printf/logging consuming so much performance it makes games unplayable.
- e12e 8y agoFor a simple example, surely the easiest/safest way to make sure the loop remains a loop, is to look at the assembler output?
- cjhanks 8y ago`printf` implements a barrier which cannot be crossed by an optimizer.
- twtw 8y agoMeh. Aras is comparing apples to apples, so I don't see how it matters.
- petermcneeley 8y agoI agree its a general nitpick but it actual furthers Aras point if he removed the printf and the difference in performance (c-style vs ranges) was to increase.
- tpush 8y agoThe statement that I found most powerful: > Compile time of this really simple example takes 2.85 seconds longer than the “simple C++” version. > Lest you think that “under 3 seconds” is a short time – it’s absolutely not. In 3 seconds, a modern CPU can do a gajillion operations. For example, the time it takes for clang to compile a full actual database engine (SQLite) in Debug build, with all 220 thousand lines of code, is 0.9 seconds on my machine. In which world is it okay to compile a trivial 5-line example three times slower than a full database engine?!
- jackewiehose 8y agoAnd that is a really old problem. I remember trying boost::phoenix about 10-15 years ago but it was completely unusable because even the hello-world-example took like 10 seconds. Thats incompatible to my C++-workflow. Today I'm trying to port some big old Embarcadero (Borland) C++ projects to their new clang-compiler but the compile times are so terrible that I'm not sure this will be usable at all (and these projects are mostly C++98).
- badsectoracula 8y agoWell, it doesn't help that Borland focused on compiler performance (i have Borland C++ 5.0 and i use it for a lot of my C coding since i write in C89+a few extensions that are supported anyway, because it takes around a second for a full rebuild for my code and is practically instant for a partial build - and that is for a singlethreaded compiler, for comparison the same code takes 4 seconds on a parallel build in gcc with make -j8). I don't know of any other (non-toy) compiler that focused on that. 15-16 years ago i used the free BC5.5 command line tools exclusively because on the crappy computer i had at the time (Pentium MMX at 200MHz with 32MB of RAM) it was the only that was fast enough to work with. I've also played a lot with Borland C++ Builder and the compile times for C++ (pre-C++98) are comparable (that is, practically instant).
- deleted 8y ago[deleted]
- 8y ago
- gpderetta 8y agoI think that people have been taking Eric example too seriously. I can't speak for him, but I doubt that Eric was suggesting that such code would be routinely written with the range library, he was just showcasing as much as possible of the range library in as short an example as possible. In a way it was designed to maximize complexity. These excesses have in fact a positive side: the extent that people will go to get a specific functionality (in this case lazy ranges) as a library is a signal that the feature should be part of the language. This happened with lambdas, boost.lambda, boost.phoenix and to a lesser extent boost.bind showed that the functionality could be implemented as a library, people stated playing with it and demanded the functionality to be baked into the language. A similar thing is happening now, there is a push to extend the proposed await syntax to become a more general (and more efficient) continuation syntax that, among other things should lead to built-in support for lazy ranges.
- alacombe 8y agoI can say that I have been guilty of writing such code, and people I worked with have too. When you are given a tool, you want to use it, especially if it gives you a lot of bell and whistle.
- rmcclellan 8y agoI work using C++17 for high performance applications, and I can relate to a lot of these gripes. I think it's a fair point that C++ is unreasonably complex as a language, and it's been a serious problem in the community for a long time. One part that really struck me as odd is the focus on non-optimized performance. To me, this is an important consideration, but not nearly as important as optimized performance. Using techniques like ranges can definitely slow down debug performance, but much of the time it _dramatically increases_ optimized performance vs. naive techniques. How do ranges speed up optimized builds? One of the best techniques for very high performance code is separation of specifying the algorithm and scheduling the computation. What I mean by this is techniques like [eigen](http://eigen.tuxfamily.org/index.php?title=Main_Page http://eigen.tuxfamily.org/index.php?title=Main_Page) and [halide](http://halide-lang.org http://halide-lang.org) where you can control _what_ gets done and _how_ it gets done separately. Being able to modify execution orders like this is critical for ensuring that you're using your single-core parallelism and cache space in an efficient way. This sort of control is exactly what you get out of range view transformers.
- kbwt 8y ago> Using techniques like ranges [...] _dramatically increases_ optimized performance vs. naive techniques. This claim will require some evidence. In my experience, it's extremely common for novice engineers to trade orders of magnitude in build time overhead chasing negligible runtime performance improvements.
- modeless 8y ago> I work using C++17 for high performance applications > One part that really struck me as odd is the focus on non-optimized performance I'm guessing your high performance applications aren't interactive? When your application has to respond to user input in real time, a binary that is 100x slower than real time is completely useless. You can't play a game at 0.3 frames per second. I would be interested in seeing an example of how Halide-like techniques can be used with C++ ranges. I am skeptical that you could get the kind of performance improvements that Halide can achieve. And of course you won't get the GPU or DSP support that is really useful for that kind of computation.
- ptero 8y ago> Cognitive load is important This, to me, is a very powerful statement that points to the major change between old C++ and the current model (which is IMO is a monster). It may have gained a lot in the process, but at a huge cost to conciseness and clarify of written code. And this may be fatal for a general purpose language. Maybe as other languages with focus on cleanliness and clarity (Pythons, Kotlins and Haskells of this world) become fast and powerful enough for large projects the use case for simple and clear C++ code decreases. Then C++ can still provide benefit for, say, intermediate representations. But this complexity increase may be one of the last nails into its coffin as a general purpose language. My 2c.
- jstimpfle 8y ago> clarity [...] Haskell no. Just no. Haskell is a lot like C++.
- zozbot123 8y agoHaskell 98 is a hell of a lot cleaner than C++98!
- SomeHacker44 8y agoFor the case in point, Haskell is much clearer. You can do Pythagorean Triples in one line. Google it if you don't believe me. However, in general, (in my opinion of course) the cognitive load for Haskell is low as a novice, but gets higher and higher as you go down the rabbit hole toward learning, understanding and applying all of its power - which is constantly evolving not unlike C++.
- jandrese 8y agoJust because you can do it in one line doesn't mean that it has low cognitive load. In fact it probably increases the load since you have to think about all of the abstractions that are built into the language instead of reading them off of the screen. The three for loops version lays everything out right in front of your eyes.
- jlebar 8y agoI want to say one word to you. Just one word. [...] Are you listening? Modules.
- whatifitoldyou 8y agoA pretty good summary of the state of C++. I actually enjoy the language and most of the new features. The 'modern' part is not at fault here. This has always been a problem. The cult of generic metaprogramming was always a pain to deal with. In part, the new language features can help reduce over reliance on templates. As a community we must push for features that can get is out of this situation, like a sane modules system. I think Bjarne Stroustrup is partly to blame here. He has succeeded in creating a versatile and popular language but has given little priority to problems like build time, error messages and debug performance. These sound like petty, practical problems that the tooling guys should eventually figure out. Except they aren't. They are hugely dependent on the language design. Another thing we are missing is a high profile figure that will show the way on correct patterns and that will literally publicly shame the authors of too "meta" template heavy libraries. You are not smart if you are writing these monstrosities. It should not feel good. Making it simple requires much more intelligence.
- ubadair 8y ago> The cult of generic metaprogramming was always a pain to deal with. In part, the new language features can help reduce over reliance on templates. As a community we must push for features that can get is out of this situation, like a sane modules system. The "cult of generic metaprogramming" is the very community pushing for those features. > literally publicly shame the authors of too "meta" template heavy libraries. This is pretty harsh. I agree that metaprogramming abuse in application code is a problem, and it's not something that should be advocated. However, there is a big difference between promoting C++ literacy and saying "everyone should commit metaprogramming code at work". The blame for metaprogramming abuse lies _squarely_ on engineering teams with poor quality control, not on library developers who like to push the language to its limits in their free time. Everyone knows that C++ is a language of footguns. Metaprogramming is one of them; Boost is a mixed bag. Every successful C++ engineering team enforces coding standards to address this.
- erik_seaberg 8y ago> publicly shame the authors of too "meta" template heavy libraries Wouldn't they say we should instead shame the people who can't read them? What makes you right and them wrong?
- makecheck 8y agoC++ compilation feels like what you would experience if you could only understand a sentence by looking up each of its words, and each word was defined in a separate book, and each book was in its own shelf in an enormous room full of shelves, and one of the shelves was in a library on the other side of town, and one was in Alexandria.
- userbinator 8y agoThe desire to overcomplicate and overabstract things does seem to have taken hold in the C++ community within the past few years. It's not anything new, however; just that for C++ to get the "Modern" treatment, is relatively recent. (Look at the Java and C# communities for example. What they don't have in terms of complexity in syntax, they make up for with an abundance of classes.)
- guillaumec 8y agoGood article. Compilation time is indeed an important issue, and the main reason why I use C instead of C++ as much as I can.
- buserror 8y agoExcellent article! I stopped using C++ completely after completing the meme that is posted near the end, I wrote gazillions of lines of C++ over 20 years, used all the gizmos, and then, gradually started to cut down on them, eventually settling on stuff like --no-rtti and --no-exceptions. It's mostly working on the linux kernel and qemu that showed me I could write extremely nicely structured code, in plain C. Very easy to debug, very readable, very quick to compile and run. And then one day I looked at one of my C++ module and told myself I actually didn't /need/ all that jazz of classes, namespaces, constructors etc etc. It could be done as a small .h/.c couple with 2 functions... And that day I started to convert many many lines of C++ to plain C. Because it's /indestructible/, it'll continue compiling and working on /anything/ forever, without having to figure out why the new C++ compiler is throwing you a 10 lines error message just because that codebase is 10yo and isn't up to scratch with the 'new' way of doing things. I DO miss bits of C++, I miss some of the encapsulation it provides, I miss stack-based objects, and funnily enough, I miss exceptions. But I haven't looked back. The other points he makes about 'junior hires' is also very valid, with C++ you could /easily/ have a new guy write completely bonkers code, add subtle bugs that takes days to find, much much later. I had my own horror stories about that, and that's another factor that pushed me over the edge to drop the language.
- thestoicattack 8y ago> the new C++ compiler is throwing you a 10 lines error message just because that codebase is 10yo and isn't up to scratch with the 'new' way of doing things Does this happen? I thought the committee was so into backwards-compatibility that C++ will also work on everything forever.
- 0xcafecafe 8y agoI doubt it and would love if the OP gave an example. One of the reasons that C++ suffers from bloat is they always strive to keep backward compatibility.
- buserror 8y agoSure. I remember one. For about a million years, you could declare a naked virtual method with int blah() = NULL; -- perhaps it wasn't /suposed/ to be used like that according to the standard... but it worked, and was used a lot, as it made perfect sense. Then one day, you recompile it and no, it /needs/ to be ZERO and not NULL sorry. But it's just one simple to fix example, in plenty of cases, especially as templates (and especially template instantiations) evolved, the whole thing would come crashing on you. For a while trying to compile templated code on MS, CodeWarrior and GCC was pretty much impossible without deploying ruses that made C preprocessor macros tame in comparison.
- utopcell 8y agoI echo the sentiment of the author. Also, It seems to me that in all three articles, what comes through is not that ranges will be cool or useful, but that coroutines are awesome. With this, I couldn't agree more. I sometimes wonder if we didn't have discussions about or these tertiary[1] TSes, we'd have coroutines in C++17 already. [1] subjective opinion
- 4bpp 8y agoI think that at this point, C++ has basically to be treated the same as the English language. Sure, you could write sentences that span three pages, employ Shakespearean vocabulary or words that hardly anyone has known since the abolition of Classics from university admissions tests, or encode another layer of meaning in the bit pattern of when you do and when you don't use Oxford commas. What makes something good English communication is not whether you used those features correctly, employed the most in vogue ones or found the "most elegant abstraction" ("there's this one word attested in a handful of 14th century sources that captures exactly what I want to say, so I'll use it without explanation"), but whether you have struck the right tradeoff of minimising the combined effort put in by you and your audience and conveying your message faithfully. Of course, for English, this is exactly the sort of problem that is addressed by style guides (and teachers/peers reacting to you, and natural selection). I think what we need is more and better opinionated C++ style guides that don't just talk about what line to place your { on, but have the confidence to say things on the level of "std::move may add a marginal amount of efficiency, but greatly detracts from the legibility of code. Avoid using it altogether."
- Aardappel 8y agoThere should probably be two C++ styleguides: one for library authors and one for library users. For the former, use of std::move can be absolutely essential to abstractions that are both efficient and correct. For the latter, it probably means you're overthinking things.
- 4bpp 8y agoThis is a fair point, but I still feel like there should be some regulations on how it is used in libraries (lest we wind up in a "this chunk of code is going into a .so, so everything goes!" situation), since library code still has to be read and maintained. Compare inline assembly: it can be absolutely essential to talk to hardware or use specialised processor features (like if you want to use fancy SIMD instructions in the inner loop of a linear algebra library), but everyone would probably agree that a library which keeps the actual assembler code contained inside one or two specific functions is somehow preferable to one where each of the 40 different ways of calling it happily start out with an __asm block that packs their respective arguments into the relevant special-purpose registers and only then call the common "do the actual processing" function.
- modeless 8y agoI haven't seen anyone mention https://zapcc.com https://zapcc.com yet. I've been testing it and it works as advertised, cutting compile times by a factor of 5 or more in real world projects with zero code changes. It's both easier to use and faster than C++ modules could ever be, even in theory. It could go a long way to addressing one of Aras's complaints. In order to improve C++ compilers, we are going to need to throw away some old assumptions about how they should work. Zapcc challenges the notion that the compiler should exit after compiling each file and start over with the next one. Perhaps some of the other issues Aras points out could be addressed by rethinking some assumptions. For example, tighter integration between the compiler and debugger could potentially make it feasible to debug optimized builds.
- jcelerier 8y ago> Zapcc challenges the notion that the compiler should exit after compiling each file and start over with the next one. this idea has been floating for a long time : ftp://gcc.gnu.org/pub/gcc/summit/2003/GCC%20Compile%20Server.pdf and https://gcc.gnu.org/wiki/IncrementalCompiler https://gcc.gnu.org/wiki/IncrementalCompiler sadly, it seems that modules has put these approaches on hold, even though I am pretty sure that modules won't solve the problem.
- oh_teh_meows 8y agoI wonder if the state that C++ finds itself now is a result of sub-optimal compromises between different interests in the standards committee. Year long arguments of what ought to be included or how they ought to be done may cause folks to lose sight of what's important, you see. I still like C++ though. At least a subset of it anyway.
- amluto 8y agoI have a large C++ code base (mostly C++11), and I agree with this article. My code base is hilariously slow to compile, and the biggest single culprit is the variant header. I sometimes wonder whether I should just remove all the users to save time. To add insult to injury, std::variant is a rather bad approximation of a sum type. variant<int,int> is only partially functional, and you can’t disambiguate the two ints by name the way you could in, say, Rust. I think that a lot of these problems stem from two systematic problems 1. The desire to avoid modifying the language. A good modern language should have sum types. Heck, the ranges paper explains how ranges fundamentally have the wrong semantics wrt const and that this can’t be fixed without changing the language. So fix the language, please! 2. The fact that even new libraries are specified to be header files. Surely there could be a variant module even if modules aren’t really for full deployment.
- modeless 8y agoUnfortunately modules don't solve the compile time problem, as demonstrated in the article, which matches my own experience. I was excited about modules until I tried them out and realized they don't help much in practice. I hate to sound like a broken record but I've been telling everyone I know to try https://zapcc.com https://zapcc.com. If you are on Linux then it is a real solution to long compile times, especially for incremental builds.
- jcelerier 8y ago> My code base is hilariously slow to compile, and the biggest single culprit is the variant header. yeah, <variant> is so slow that I wrote my own (domain-specific but could be repurposable) variant-like code generator. It keeps the same API but makes everything sooooooooooooo much faster to compile - especially the visitations with multiple arguments. (ugly fugly, full of dirty hacks, but it only had to run once or twice : https://github.com/OSSIA/libossia/blob/master/tools/gen_variant/gen_variant.cpp https://github.com/OSSIA/libossia/blob/master/tools/gen_vari...)
- SomeHacker44 8y agoAs a long time lover of functional programming, and have not used C++ professionally since the 90s, this whole article feels sort of damning about the future of the language from a functional perspective. The calculation of Pythagorean Triples is literally one line in Haskell (a language I have used for almost three decades), as a simple list comprehension. It also can then be consumed or used in any fashion, and is calculated lazily. While I am not particularly adept at other "functional" languages, I would imagine similar brevity in F#, OCaml and possibly even Python (which I believe has some sort of list comprehension syntax).
- jimmychangas 8y ago> the time it takes for clang to compile a full actual database engine (SQLite) in Debug build, with all 220 thousand lines of code, is 0.9 seconds I immediately thought about some Java (Gradle) projects at work that take 10 minutes to build, and other transpiled javascript projects that take an hour or so. What am I doing with my life?
- hendzen 8y agoAn hour? For JavaScript? Wtf?
- jimmychangas 8y agoBabel + a million lines of Javascript, and a few other build steps. Durying development, only the required modules are built, so it takes only a few minutes to get up and running, but a full build takes ~1 hour and produces ~100mb in bundles.
- Kenji 8y agoC++20 is a clusterfuck to put it mildly. And that comes from someone who loves C++11/14 to bits.