10 ms·
Ranges, Code Quality, and the future of C++
- dilippkumar 8y agoWhy is C++ so complicated? My initial hypothesis was that the language was evolving to simplify how a someone can stitch together multiple libraries to create an application. If the complexity can be reduced (by say, adding garbage collection), then the quality of programmers a software company would need to hire can drop from the Donald Knuths of the world to someone who is more likely to submit their resume at an average software firm. However, at this point, it is safe to claim that the complexity of keeping up with C++ far exceeds the complexity of the original problems it was trying to solve. Does anyone have a good argument for making C++ so convoluted?
- pjmlp 8y agoBackwards compatibility with C89 copy-paste semantics, which is only simple for those that never read ISO C documents, and the goal to design a powerfull expressive language, that allows any developer to create libraries, which in other programming languages require compiler and runtime support instead. Plus even other languages that appear simple, are only so for those that don't really know the complete language standard. Python is my favourite example, as it is demeed as simple language for begginers, yet the language + library reference PDFs are also a couple of thousand pages long, and there are so many PEPs that hardly anyone can pinpoint which is the minimum Python version to execute a random piece of sample code.
- gumby 8y agoC++ is a large toolbox. You don't have to use all of it, and probably shouldn't use all of it in one program. There are other approaches: e.g. js + npm, which can get you super rapidly to something functioning. It has different risk points than C++, and different benefits. If it works for your use case you'd likely be a fool to use C++, especially since its "standard" library is so much larger. But likewise there are complicated problems for which you'd be wrong to use node. Transportation runs from bicycles to mack trucks to tanks to spacecraft, each with different complexity and performance issues. What it means is that you can quickly write a powerful program or a team can build a large, maintainable system. Some people on that team might use some of the more abstruse tools to make stuff (specific macros (aka templates), other complex objects) that create common resources used across the team. You can choose a simpler language but then for some cases every user needs to repeat certain boilerplate and/or risks forgetting some important corner cases. You can let everyone free to use any arbitrary tool in the toolbox but then you equally end up with an unmaintainable mess. This really isn't any different from building, say, a power plant. Some people have gone to a lot of work to develop the steam schedules (regulatory code, such as schedule 60 piping) and you can buy them and have people who know what they are doing weld them up as you need them, but you don't have to design the safety code, simply be sure you're within its constraints.
- ncmncm 8y agoIt was Jonathan Shopiro (author or iostreams) who interrupted a comparison equating Language X with a seaplane, interjecting that by such a standard, C++ would be a boat anchor with feathers. It remains the funniest thing I have ever heard said about C++. It completely derailed what had been becoming a tedious conversation. I don't recall what Language X was.
- Koshkin 8y agoI have to disagree. From where I stand, complexity of C++ merely reflects that of practice of programming in general (modulo the language's historical origin and evolution). C++ remains a pragmatic language that helps solving practical problems. Memory management is one aspect, and the RAII paradigm and smart pointers are an excellent way to go about it; garbage collection libraries are also available.
- munchbunny 8y agoI don't think C++ is actually that complicated. However, I do think C++ tries to use way too many different permutations of the ()[]{}<> characters. Once you try to stuff too many of them into the same piece of code, you end up having to read code twice, once to figure out what the braces are for, again to figure out what the code is doing. As an example, the < and > characters may indicate comparison (< and > in boolean expressions), a template declaration (< and > with a comma separated list following a function/class name declaration), a template specialization, an operator overload, an object access via pointer (->), a bit shift, (<< or >>), and possibly writing to a stream (<<). A less egregious example, [] might be used to indicate indexing into an array, indexing into an object via overloaded [], declaring an array type, or declaring the captures in a lambda. While you can figure it out contextually, it's often irritating to have to figure out context first and then interpret the code.
- gumby 8y agoThey consciously try not to add many keywords or reserved characters. That has the downside but so does the alternative. I do agree that the stream operators << and >> was a bad experiment in retrospect.
- jokoon 8y agoBackward compatibility is a hard problem. Look at rust, or python, those are great languages, but obviously you need a new compiler and you need to write new code. With C++, you can just add new code to an existing code base, and share that code across projects. What is nice is that the language is evolving, but previous code still works. So the problem with C++, is adding new cool features, on an existing language, while not breaking existing code bases. That's how you improve a language, by keeping its users while adding new better ways of doing things. I honestly don't think rust or go will really survive over the decades. C++ already has momentum, and honestly, its close-to-C syntax is still a good thing because it's intuitive. How down-to-earth simple do you think go or rust are for students? How do you wrap your head around having to explain students the purpose of a garbage collector, not to mention how it will behave and how predictable it will be?
- KayEss 8y ago> If the complexity can be reduced (by say, adding garbage collection) Adding a garbage collector would make code more complex, not simpler, as it would break RAII. I spend a lot more time dealing with memory and resource allocation issues in GC languages than I do in C++. In languages like Python we have to use `with`, and this is harder to use in the client code, and the class is much harder to write, than anything in C++ where we just add a destructor to clean up. std::vector<char> data(10 << 20); std::ifstream("/dev/urandom", std::ios::binary).read(data.data(), data.size()); The above code can never leak (either memory or file handles) under any circumstances and the implementations of the types involved push zero overhead/boilerplate on me as a user of them. I think if you want to reduce complexity of C the place that everybody else is looking is at how to allow opting in and out of legacy support.
- longcommonname 8y agoAs someone who is on a personal journey to transition from c with classes thinking to modern c++ I can see why there's so much hate from non-cpp developers about how these features look ugly. But, as a developer, who has dealt with an absurd amount of bugs that can be prevented from these modern c++ features I really like this.
- sifoobar 8y agoI went the other way, started writing C++ in 95 and digging into C properly around 2005. Just make sure that the cure isn't worse than the disease. It's all to easy to slip into identifying with C++ because of the substantial effort it takes to master. Should you, like me and many others; eventually find yourself spending more time and effort on taming C++, don't hesitate to let go. It's a tool.
- pjmlp 8y agoThe solution is easy, managed languages for 90% of the code and C++ for the remaining no-excuses full throtle execution speed, while offering reasonable safety for arrays, strings, out parameters, enumerations, lifetimes. Not everyone is running Solaris on an ADI enabled SPARC.
- Koshkin 8y agoI have a somewhat different perspective to offer: C or C++ for components (effectively 90% of all code), a managed or scripted code (10%) for the glue. The reason is that the modern C++ is perfectly suitable for high-level programming -- on par with Java, C# or Visual Basic. No "taming" is needed.
- pjmlp 8y agoTaming is necessary, because regardless of what you and me think about modern C++, there is a large crowd that still writes C with C++ compiler with total disregard for any kind of security validation tools. So the only way to prevent such security exploits is by preventing copy-paste compatibility with C, which C++ sadly cannot do without breaking backwards compatibility. You can see this happening across all desktop and mobile oriented OSes, where both C and C++ have been reduced to high performace low level OS layers, with userspace migrating to something else. Microsoft is the only vendor left on OSes for the consumer space that still cares enough to support C++ on their UI toolkits.
- Entalpi 8y agoI feel like a development such as the C++ standard needs to deprecate things a lot faster than they currently do. Imagine in 15 years time when C++ compilers are so complicated that they become self-aware and decide to get rid of us programmers.
- arithma 8y agoThis does not go unnoticed, but with so many interested people who are actually contributing, it is hard to avoid the clutter. Bjarne Stroustrup (C++ creator) on this: http://www.stroustrup.com/P0977-remember-the-vasa.pdf http://www.stroustrup.com/P0977-remember-the-vasa.pdf
- notacoward 8y ago> the C++ standard needs to deprecate things a lot faster than the currently do. This, a thousand times. It's all very well for gumby to say that C++ is a big toolbox, but a good toolbox doesn't have all the mismatched tools from ten previous toolboxes all thrown together. Longcommonname can say that using modern C++ features help prevent errors, but that statement becomes a bit problematic when the most modern C++ style becomes deprecated C++ every few years. As the OP aptly illustrates, there are just too many ways to do even fairly simple things. All were idiomatic "best practices" for their time. It's not a problem if there are many ways to do things if they're all comprehensible to someone who knows the universal core of the language (as is often the case e.g. in C or Python). It's a problem when understanding each of them requires a whole separate excursion through some separate specialized and time-bound part of the standard, yet they all exist together in any large codebase that has had to build on those shifting sands. That's not good for maintainability. Future programmers will misunderstand the intent of code using each transient style, and either break it when they change it in place or miss some important detail when they try to replace it. Different versions of C++ should remain separate to a much larger degree than is currently the case. Trying to combine several distinct language-as-written into a single super language-as-compiled (a mistake also made by Scala) never seems to work out very well.
- MauranKilom 8y agoDo you interact with any significant C++ code base? Because I'm having a hard time seeing what your practical recommendations would be past the idealism. Yes, you can have major breaking changes every few years. But do you really think every larger C++ code base being stuck with the C++ version it was founded on is the way to go? Should they all be rewritten for each version?` Don't get me wrong, I'd love if we could deprecate more old stuff. The problem is that it doesn't just disappear from existing code (of which there is a _lot_) by marking it as such. If there are straightforward replacements (e.g. auto_ptr -> unique_ptr) that's one thing, but unless you want each new C++ standard to be unusable with existing code, you can't just do that.
- muterad_murilax 8y agoIs modern C++ the new Ada?
- Koshkin 8y agoI think the biggest difference between Ada and C++ is that much of the functionality in C++ is implemented in the form of libraries vs. something that is built into the language itself. Character strings is the most obvious example, but there are many others (such as concurrency primitives). Personally, I am not sure which way is better: on the one hand, a good language should, indeed, give the programmer powerful means of abstraction; on the other hand, it might be beneficial if the language grammar could somehow directly absorb, in time, things of unquestionable value - so that the construct's syntax could be simpler or more natural, or, perhaps, so that the compiler could employ better optimization techniques.
- pjmlp 8y agoAlmost, copy-paste compatibility with C89 hinders its safety. However it is better than keeping with plain old C.
- cbsmith 8y agoNah. Ada is the new Ada. C++ is a different beast altogether.
- topspin 8y ago> Is modern C++ the new Ada? I don't know much about Ada except what experience I've had with PL/SQL, which is supposedly related to it. I found myself counting the number of characters I'd have to change to get that first C++ snippet to evaluate as ES6.
- notacoward 8y agoNo. Ada might be too complicated, but it's far more coherent. C++ is like a novel with each sentence written by a different author. Even worse, each author keeps trying to pull the plot and the characters in a very different direction. Any C++ codebase that spans more than one version of the C++ standard ends up combining the two into its own special dialect, defeating the whole purpose of having standards in the first place.
- CorvusCrypto 8y agoIn both the original example and this updated version, the nested loops (or foreach calls) create generators on each iteration. However, the first example is creating the generator chain more implicitly than the newer example. Since the original example also uses a coalescing expression (i.e. I mean that he is returning a dynamically-created generator chain as a variable) it means the use of "return" keywords at every subcall to foreach which can confuse programmers that aren't as seasoned with generators or functional generator patterns. It's actually, I would argue, not "bad code" but rather a different approach that procedural programmers probably aren't used to. The benefit of this style of doing things is that it comes with the benefits that usually come with creating lambda expressions. There are better examples of creating on-the-fly generators, and surely thats what the original author wanted to showcase, but to programmers not well-accustomed to common range-based generator patterns, the Pythagorean triple example for this looks and feels a mess when it's clear you don't need to generate the generator dynamically. This updated example I think is more generally useful for showcasing the range iterators to programmers. Also this is just my opinion. Of course it could be that the original example is hated by all and a total POS, but to me it seemed quite clear in a functional context what it was getting at and what it meant for use of dynamically-created generators in higher order functions.
- Koshkin 8y agoAgree. The "bad code" example looks beautiful to me, albeit unusual for someone who is accustomed mostly to the imperative style; I have no doubt that it would only take a small amount of (self-)training to absorb such style to the extent that it becomes easy to read.
- iseeyoubydesign 8y agoyou could have put "beautiful" in quotes as well. These type of discussions are too subjective to have meaning to engineers. Every project has its guidelines and standards. This is the same problem with python, the language is trying to dictate too much. If someone makes a bunch of oil paint pigments they are not supposed to dictate how the artist uses them. Let the engineers and individual projects figure out how best to use the tools of the language. Any attempt to create coding standards across the board is an attempt to control others creations. If its really good style you would not use the word "beautiful" you would use the word correct , no quotes. Engineers dont deal in vague ideas.
- Asooka 8y agoAs a full-time C++ developer, my only gripe with features like these is that they require nontrivial compiler optimizations in order to produce the equivalent "old-style" high-performance code. That leads to two issues for me: 1. Longer compilation times 2. Your program may be too slow in debug mode for you to meaningfully debug it We're kind of at a crossing point where I would love it if I could have a compiler that "de-sugars" these expressions into optimized C code, i.e. removes the lambdas, adds local variables, etc. and then compiles with no or minimal optimizations for debug mode. I really love gcc's -Og mode, which should do something very close to that, but I still have to read the assembly code to get a full picture of what's happening. I feel like we could do a little bit better. Edit: And if you're wondering why I care that much about performance, it's because there is really no reason to use C++ today other than to write high-performance software. There are other languages that can get bare-metal performance and I don't want to insult anyone using Fortran, Ada, etc. plus I'm keeping a close eye on Rust, but the combined tooling that exists for C++ still makes it a better, more productive alternative as far as I can judge. The other reason to use C++ would be to interface with a C++ system, in which case it might be easier to just write everything in C++ and skip the translation layer pain, but you might not care that much about performance in that case.
- jlebar 8y agoAs a C++ compiler developer I don't think these features are what's causing long compilation times. Most of them don't even require any changes to llvm. AFAIK ranges doesn't, and the changes necessary for coroutines are simpler than you may think. Next time you have a program that takes a while to compile, try profiling clang with perf! It's super interesting.
- cbsmith 8y agoHonestly, I don't understand how the coroutine version is easier to read, or what was confusing about the original example. Either you grok yield or you don't.
- MauranKilom 8y agoI find it's a lot more boilerplate, more visual overhead in finding the important bits in the sea of various brackets... But maybe I'm just blinded by imperative upbringings.
- shaklee3 8y agoGreat example. The first one was very hard to understand, and this one made it a lot more clear.
- saagarjha 8y agoThat’s an interesting perspective; I found both to be similarly complex. Both boiled down to “lazy generators”, albeit with different syntax.
- nuclx 8y agoLooked at the code and my current endeavors to learn Rust are reinforced. I'd prefer to never touch that abomination of a language that is C++ ever again.
- ausjke 8y agomy biggest problem with modern c++ is indeed readability. c++11 remains to be the most popular language in algorithm competitions(e.g. usaco), 51% usage there, along with 33% java and 12% python, which surprised me
- dmix 8y agoOff topic: the code examples don’t scroll on mobile
- notatcomputer68 8y agoWhere are the local variables stored for a coroutine? I do like new advanced features but we must not let them make us lose sight of the metal.
- Quekid5 8y agoReally weird to read this and not a single mention (in the article or ITT) of the most critical concept here: Composability. That is what ranges can do for your code -- make it composable. The imperative-style examples don't compose in any meaningful sense. The only thing you can really do with the imperative style programs is to sequence them, i.e. run one after the other. With ranges you can apply various types of transformations, combine multiple ranges, it lets the consumer(!) decide how much work should be done, etc. etc. The gives incredible leverage for code reuse.
- notacoward 8y ago> The only thing you can really do with the imperative style programs is to sequence them What's wrong with that? Isn't that the most readily accessible (and therefore maintainable) kind of composition? I realize that some people might prefer other forms of transformation or combination, but can you give an example of where it provides a tangible (not just aesthetic) benefit?
- Quekid5 8y agoI answered that in the following paragraphs: It gives you a huge amount of potential for reuse -- essentially for free. (Alright, this is C++ so there's a little bit of cost due to a tiny amount of extra 'syntax' where C++ imperative syntax is "free", but that's trivial for any but the most trivial of examples.)
- notacoward 8y ago"Following paragraphs" is an ambitious term for barely one line of text. You have claimed there is some unspecified promised land of reuse through unspecified transformations and combinations, but you really haven't shown anything that's not equally achievable with "mere" sequential application. It still looks like a matter of aesthetics - that you prefer a more syntax-laden functional style, but not one that's objectively better.
- 8y ago