18 ms·
“Modern” C++ Lamentations (2018)
- deleted 5y ago[deleted]
- einpoklum 5y agoI also have to agree that ranges functionality in C++20 is somewhat warty. I wonder, though, how much time the Pythagorian triplets example takes to compile with C++20. The ranges library simulated functionality which would later go into the language, so the comparison is not entirely fair.
- vinkelhake 5y agoThis is 95% criticism about C++20 ranges (specifically the ranges v3 implementation), but spun as being about some more general trend. I think some of the criticism of ranges is fair - compile times do matter and non-optimized performance can also matter. Stuff like `iota` being obscure is less convincing. It wasn't invented here and it is anyway something you learn once and then it's part of your vocabulary.
- dundarious 5y agoThey mention the joke of adding boost being a fireable offense. Boost is a massive and very common dependency in less opinionated C++ codebases (i.e., a lot of corporate C++ codebases). There are plenty of other massive "all header, all template, all the time" C++ codebases. I think the trend for C++ libraries to be like this and to turn into major compilation time problems is already well established. If there has been any reversal of that trend, it's because of the observations and work of people like OP. As a side note, a major folk selling point for modules was that it might help with compile times, and a ton of work has gone into it in order to try to deliver. I haven't checked in on it in about a year, but my understanding is that it has largely failed on this front. Saying that there is a general trend/problem in C++ projects with very long compile times for even simple examples is, I think, an extremely well-grounded assertion (although ranges-v3 seems to be the new champion there).
- vinkelhake 5y agoYeah, I agree that there are libs that have this problem, but it's not a new thing. Boost is a huge collection of libraries, with different properties and tradeoffs. It has been the case for two decades that a lot of the Boost libs are on the heavier side with regards to compile times. That's partly because Boost is an incubation chamber that pushes the limits. Perhaps an even bigger reason is that Boost libraries mostly err on the side of being generic, allowing users to wrap the lib around their data rather than the other way around. But really, there's nothing new here. When a developer brings in a library to their project there are always a bunch of dimensions to evaluate. Compile time is simply one that C++ developers have to evaluate, just like developers who rely on NPM have to ask themselves: what happens if $author goes nuts? FWIW, I think it's too early to declare failure on modules. The standard library hasn't been "modularized" yet, but the three major compilers all offer ways to import, rather than #include the standard lib. I just did an experiment the other day and switched my current hobby project to use import for my standard library dependencies and it nearly cut the build time in half. Very promising.
- dundarious 5y agoI don't understand how it's not a general trend but it's also not a new thing. Saying it's not a general trend is the particular point I disagreed with. It's a general problem in the C++ world, especially with the "modern" stuff, and it's either as bad or worse than before. Some people say boost isn't "modern", because it has accumulated a lot of historical cruft to support buggy compilers (old versions and varied vendors). I don't think that's the most accurate interpretation. I think "Modern C++" became such a big thing after C++11 because the effort required to make and use boost style libraries dropped significantly -- no longer do you need your project to either depend on boost, or implement its own version of some NONCOPYABLE macro to disable copy/assignment(/move) operators, or what have you. I think saying "modern" means removing the hassle along the way to implementing boost, is to miss that the point is to be able implement things like boost -- implementing/using things like boost is what's "modern". The fact that an absolute ton of the new standard library features have an origin story in boost only further confirms this. One thing I forgot to mention when I distracted myself with modules, is that I totally agree with the author that the problem is also about debug build runtime performance, not just compile time. Interesting to hear about modules. I'm out of the C++ game but I'd be happy for it to be working well.
- tuetuopay 5y agoWow, and colleagues dared complain that Rust has a terse, hard to read, syntax...
- betterunix2 5y agoThere are examples of far worse languages: https://en.wikipedia.org/wiki/APL_(programming_language)#Examples https://en.wikipedia.org/wiki/APL_(programming_language)#Exa... At least the Hello World example is readable...
- idealmedtech 5y agoI always love watching that incredible old APL demo [1], though. Seems incredibly futuristic for the time! edit: this is actually a different demo, but there's a similarly impressive one focused on showing APL, from the 70s I think? Will update if I find it! [1] - https://youtu.be/yJDv-zdhzMY https://youtu.be/yJDv-zdhzMY
- microtherion 5y agoIncidentally, that obscure "iota" name that OP was complaining about came about by way of APL (which of course used an actual Greek letter to write it).
- scotty79 5y agoActually I'd prefer greek letter. At first I read iota as itoa which is also a thing.
- mshockwave 5y agoIIRC, APL even has its own keyboard (to type the code)
- a_t48 5y agoI feel this. On the subject of Boost Geometry I cut multiple minutes out of our build times by removing all instances of "#include <boost/geometry/geometry.hpp>". I hate that the example code seems to encourage this - https://www.boost.org/doc/libs/1_64_0/libs/geometry/doc/html/geometry/reference/algorithms/transform/transform_3_with_strategy.html https://www.boost.org/doc/libs/1_64_0/libs/geometry/doc/html... as it adds a few seconds to the compilation time for each TU that does this (which can be most of them if you have it in another header).
- hawk_ 5y ago> other viable systems programming languages simply did not exist (now you at least have Rust as a possible contender). (I have no horse in this race) But if a big part of their complaint is compile times, Rust may not be the best example of a contender.
- spoiler 5y agoRust incremental (and debug) compilation times aren't that bad, though. It almost feels like a mindlessly perpetuated rumor at this point. Sure, it's kinda slow when compared to something like Go, but the amount of extra work rustc does compared to pretty much every other compiler is very big. Taking this into account, I can forgive it. But even with all that said, Rust has a compiler performance team that is constantly trying to improve things and keep track of regressions. I dunno if C++ implementations (eh gcc) have something similar? But yeah, at the end of the day, I think compilation times are important, but not as important as some other aspect of the language... Like, in C++ you can't even catch some errors until you start compiling and it fails in template expansion, while I usually don't have to compile Rust until I'm checking business logic. This allows me to focus on the problem at hand, not the language.
- xedrac 5y agoAs someone who loves Rust and uses it daily, compile times are abysmal, even compared to C++. It's the sole reason I bought a 32-core workstation. But you're right, when iterating, incremental compilation times are reasonable enough. Compile time is a very small price to pay for all the developer time that is saved over the life of the project.
- djdjfhsje33edh 5y agoHave you tried the mold [1] linker? It's a drop-in replacement with a single cargo config change and gave me a massive speed-up (> 10x) for incremental debug builds of a large project with around 450 dependencies. [1] https://github.com/rui314/mold https://github.com/rui314/mold
- tacostakohashi 5y agoI concur. All programming languages have their intrinsic features, but they also have a culture that surrounds them. C++ has a rather odd culture... it's very a very fundamentalist/extremist one, all providing the optimal way to do things with no / little extra runtime overhead, but with no regard at all to compile times, readability, etc. It's not necessarily a bad thing, and it has its uses for games, ultra low latency software, all that stuff. I am proficient in C++, I've worked in it for a bunch of years and can get stuff done in it and solve problems and ship software. C++ job interviews tend not to pay much regard to that, and focus on the arcane finer points of move semantics, rvalue references, ranges, concepts, all this new stuff... which has its place, but there is a lot of that to learn, and in many cases it just doesn't matter. After all, the language existed without it for a bunch of years. Compare this with Java, or python or whatever, where the culture and focus is very much about "can you solve a problem / implement a solution using this language <or not>?" rather than "of the dozens of different ways to do this... what are the differences between each one". For me, I really don't mind using and programming in C++, and I would probably even choose it for many / most projects, but I can do without the C++ people and culture.
- NL807 5y ago> C++ has a rather odd culture... it's very a very fundamentalist/extremist one, all providing the optimal way to do things with no / little extra runtime overhead, but with no regard at all to compile times, readability, etc. It think this is attributed to fundamental rule of not breaking legacy language features. That is the number one rule (much like how Linus is iron fisted about not breaking user space in the Linux environment). However, there is a lot of wiggle room in the way binaries are generated, so compiler authors can get really creative with their optimisation efforts.
- inetknght 5y ago> It think this is attributed to fundamental rule of not breaking legacy language features. That is the number one rule No. The number one rule in C++ is "don't pay for what you don't use".
- munk-a 5y agoI think people vastly undervalue static linking when it comes to compile times. If every file in a project changes every minute then yea - you're hooped... But usually certain groups will work in certain areas with those changes being synced only so often. If you examine the dependency tree and manage to split up the codebase into modules that you compile into .a files - then have a final step where you whip together any overly broad files along with just extracting the .o files from the archives[1] you'll live a happy life. This can terribly fail if you have header files depended on by everything that constantly change - but honestly... that's a pretty bad code smell so either make an archive that's a dependency of all those others (and occasionally feel real pain when the header file actually gets updated) or else refactor things for sanity's sake. A year and a half into my first job I was tasked with revising the build system and there are some really effective things you can do there without very much training. You want to lean on `make` a lot - no seriously, a lot - but if you do you can make some amazing things happen. You've also got a lot of cross platform tooling if you need to compile on different architectures - though scripting how those interact does get progressively more difficult in those cases. 1. Just in case you're unfamiliar - linux static libraries are essentially just an archive of a bunch of object files... so you can freely use ar to manipulate them in all sorts of ways https://tldp.org/HOWTO/Program-Library-HOWTO/static-libraries.html https://tldp.org/HOWTO/Program-Library-HOWTO/static-librarie...
- rileymat2 5y agoI don't completely disagree, but templates become a big trap with respect to header files and trying to get static linked files precompiled.
- inetknght 5y agoIf you know your types then you can define the templates' bodies into a static library and keep the definitions there.
- boardwaalk 5y agoThe problems being: * External libraries don't do this. See: All those "header-only" libraries you see, starting back with boost * It's a total PITA once the types get complicated Source: Have worked for 10 years on a large codebase with "template auto instantiation" off.
- betterunix2 5y agoEh, of all the things to criticize C++ over, compile times is somewhere near the bottom of the list. Compile times matter in extreme cases, but 3 seconds is not really something I would worry about. I have seen Lisp compilers take longer than that just to start the REPL. The fact that we are still dealing with weird problems with pointers, the fact that C++ has lambdas without garbage collection (explanation of the problem is too long for this comment), and the incoherent type system are much bigger issues than weird syntax or long compile times. The C++ feature set is a bunch of semi-compatible, sometimes outright incompatible (ahem destructors vs. exceptions and coroutines), ideas that keep getting extended further from compatibility by the standards committee.
- melony 5y agoWhat's this about lambdas and GC? Are they reference counted?
- jcelerier 5y agoNot at all. But if you are saving lambdas for callbacks or other async things you'll generally want to add your own layer of reference counting anyways, unless your software architecture is very clear from the beginning.
- tialaramex 5y agoLambda expressions are a place where it's particularly likely that your mental model of what's going on isn't correct, or fails to account for something important in some edge case and so you lose track of who "owns" objects and thus is responsible for cleaning them up and when they need to do so. With GC, this might cause a small unexpected leak of some kind. But in a language like C++ it can be Undefined Behaviour and all bets are off.
- brandmeyer 5y agoThat's part of why the capture list is explicit in C++. Automatic by-reference captures should only be used for lambdas whose scope is strictly lexical (ie, passed down the call stack, aka "downward funargs"). Otherwise stick with by-value captures.
- adamrezich 5y ago> So this lazy evaluation LINQ style [in the C# example] creates additional 0.03 seconds work for the compiler to do. In comparison, the C++ case was creating an additional 3 seconds of work, or 100x more! This is what you get when “features” are part of the language, as opposed to “it comes as hundred thousand lines of code for the compiler to plow through”. imo this is the big takeaway here
- uwagar 5y agoif i could have function overloading, operator overloading (particularly (), ++, --, *) and i could auto-convert my existing codebase to C, i'd move right now to C. i stay with C++03 and thats enough.
- google234123 5y agoNo unique ptr, auto, or for each loop? No thanks
- uwagar 5y agoi write/maintain solo this software for 15 years and just dont want to start doing autos in for loops. then i have to go and change all the loops otherwise it would bother me. i dont want to go back and start messing with code that is pretty solid all these years.
- ncmncm 5y agoThe answer is simple: just don't. Write new code the new way, and leave old code that works alone. There will never be a case where you wonder why old code looks like old code. Continuing to write new C++03 is just masochism. You might find reasons to switch some old stuff to use move semantics, eliminating reference counts, which is a small job. Leaving alone stuff that doesn't need to change needs only discipline, not time. Discipline builds with use. I recommend it.
- uwagar 5y ago> Continuing to write new C++03 is just masochism disagree. this keeps things simple, readable and maintainable. beyond STL i havent found need for any new data structures or algorithms. i roll my own for domain specific (audio/graphics). > Discipline builds with use. I recommend it sure daddy. perhaps if the c++ committee had discipline they wouldnt have added these crazy things to the language too and made it a mess that it is today. however we masochists are pleased with the concession offered by stroustrup's one-liner: 'if u dont use it, u dont pay for it'
- jepler 5y agoIt's interesting to note that the algorithm can be rewritten with 2 nested loops instead of 3; for each z and x, check that there exists an integer y. At 10,000 triples found, this is the difference between (on my system) 700ms and 42000ms, a factor of 60.
- mzimbres 5y agoSenders and Receivers is the next big thing Eric is delivering to C++, stay tuned.
- avrionov 5y agoI have 2 personal projects, one in C++ about 90k line. The other one in JavaScript (ReactJS). The full rebuild of the JS projects takes longer than the C++ project.
- iainmerrick 5y agoAre you using webpack, and if so is it possible to switch to esbuild?
- avrionov 5y agoYes, I'm using webpack. Moving to esbuild will be non-trivial amount of work.
- erwincoumans 5y agoIn my opinion, simpler is often better than complex. C++ gives control over memory layout and memory (de) allocations. Aside from complexity of newer C++ template/stl features, it becomes harder to stay in control of memory (and performance). Also, code becomes harder to read. Hence in games (and often in embedded) the use of C-style C++ is popular.
- dang 5y agoDiscussed at the time: “Modern” C++ Lamentations - https://news.ycombinator.com/item?id=18777735 https://news.ycombinator.com/item?id=18777735 - Dec 2018 (249 comments)
- ncmncm 5y agoThe key is "(2018)". Things are better now. Use ccache, ninja, the mold linker. Lean into asysnc. Split off libraries. A little attention, once, saves time all day every day.
- zvrba 5y agoYes, MSVC STL implementation is dog-slow in Debug. I solved the problem by creating a new build configuration called "RelNoOpt" that builds with "Release" runtime-libraries and STL, but turns off all optimizations. I get debugging experience of "Debug" build with none of its performance penalties. (Though the extra checks -- esp iterator invalidation -- in Debug STL have saved me tons of debugging time a couple of times on another project.)
- randomNumber7 5y agoI played around with ranges in dlang. The idea of C++ ranges kind of originates from there. In D it's very easy to use and the standard library lets you write beautiful code with it. C++ made it so ugly. Especially if you wanna implement a custom range. I can recommend this article by Andrei Alexandrescu, if you are interested in the idea of ranges. https://www.informit.com/articles/printerfriendly/1407357 https://www.informit.com/articles/printerfriendly/1407357
- gpderetta 5y agoI think that the original boost.range (form 2003) predates ranges in D. There is a continuous evolution from the original boost range, boost range v2 and std range.
- torginus 5y agoI genuinely think that the 'zero-cost abstraction' feature of C++ is ultimately a poisoned apple that has caused far-reaching damage to the entire programming experience. The problem, is that zero-cost in C++ means that - if all the cards line up - feature X will not cause performance overhead in a Release build. In C, I think it's reasonable to assume that there is a linear relationship between the number of expressions in ones code, and the amount of assembly instructions it generates. No such relationship exists in C++ code. The problem is that due to this zero-cost mentality, brain-dead simple libraries, such as iterators, often have dozens of layers of abstractions, which all show up in a Debug build. This makes Debug builds harder to well, debug, as you have to understand all these abstractions, (and the reason why the designers thought they were a good idea, but that falls under the umbrella of psychiatry), as well as making the build unusably slow, of†en forcing C++ devs to debug Release builds, which means that they are staring at 3 lines of x86 assembly, with hundreds of lines of 'helpful' compiler generated source code around it.
- tomsmeding 5y ago> of†en How did that dagger end up there?
- torginus 5y agoI have genuinely no idea. I'm typing this on macOS in Chrome, with an English keyboard
- siltpotato 5y ago
- blinkingled 5y agoinline constexpr auto for_each = []<Range R, Iterator I = iterator_t<R>, IndirectUnaryInvocable<I> Fun>(R&& r, Fun fun) requires Range<indirect_result_t<Fun, I>> { return std::forward<R>(r) | view::transform(std::move(fun)) | view::join; }; Unless it solves world hunger or COVID permanently I am not typing this abomination, much less try to understand it. Very seriously whatever "team" is working on C++{20,21,..} should be disbanded and sent to someplace where they don't have access to computers. Nothing modern including Kubernetes ecosystem induces this much rage in me.
- lionkor 5y agoTo be fair, you could write this in a more comprehensible way. I wouldn't say "Rust sucks" based on reading Err(Err::Error(Error::new("c1", ErrorKind::Digit)))); either, since I just assume its not the language's fault.
- blinkingled 5y agoI am a bit more willing to put up with Rust syntax monstrosities as at least the compiler tries to help me not shoot myself in the foot and when I try the error messages seem to be more helpful than C++ compliers'. Also I don't have as much of an issue with sane subset of C++ as a language - it's the modern crap they keep adding pompously without regard to complexity and usability that irks me.
- eska 5y agoThat's not idiomatic code.
- gpderetta 5y agoThat's the pedantically, fully reusable, correct version. Most of the time I would type something like this: template<Range R, class Fun> auto for_each(R&& range, Fun fun) { return std::forward<R>(r) | view::transform(std::move(fun)) | view::join; } I.e. I wouldn't bother to type check Fun and I would use a function instead of a function object. Both features are important for library code though. Forward and move are still pointlessly noisy, I wish there was a keyword for those (especially forward). And I wish we had proper UFC instead of abusing pipes.