I know I'm not going to make a lot of friends with this opinion, but this is a perfect example of everything that's wrong with modern C++.
Seriously who, except for a small circle of academics, actually believes that this is good, readable, concise, maintainable code? Who asked for this?
When I read
inline 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;
};
I can't help but being reminded of Java [1][2].
Meanwhile it's 2019 and C++ coders are still
- Waiting for Cross-Platform standardized SIMD vector datatypes
- Using nonstandard extensions, libraries or home-baked solutions to run computations in parallel on many cores or on different processors than the CPU
- Debugging cross-platform code using couts, cerrs and printfs
- Forced to use boost for even quite elementary operations on std::strings.
Yes, some of these things are hard to fix and require collaboration among real people and real companies. And yes, it's a lot easier to bury your head in the soft academic sand and come up with some new interesting toy feature. It's like the committee has given up.
This is library code which most users of the range library will not themselves need to write. Using ranges absolutely leads to shorter and more elegant code. The implementation of the library is complicated. Much of what’s likely unfamiliar with this example is the use of C++ “concepts” (a technical term), and is not actually necessary, but is placed there to actually give better error messages to users of the library (without them, template code is basically duck-typed, and can give terrible error messages). I’d argue that the core of that code: the transform followed by the join is actually quite readable.
I also wouldn’t at all say C++ is overly academic. It’s an extremely pragmatic community. Yes, the standardization process is slow, but I don’t view, as a C++ user, the issues you mention as serious. To me, there’s nothing wrong with using high quality third party libraries like boost (and Eric’s range v3 library). My biggest frustrations with C++ are the build and packaging stories.
Boost is not high quality. It massively balloons the compile time, and there's random incompatibilities between minor versions. It's also a hunt to figure out which header I need to include for certain libraries. There's no consistent feeling to the library. My biggest gripe is that it makes vims completion stupid slow by bringing in a large number of headers.
You're talking about compile time and issues with your IDE. It is unfair to call it "Not high quality". We can think about this from an end-user standpoint that it bloats the IDE and impacts usability. Fine. Your IDE/compile times may be different than others.
Boost is extremely high quality in terms of its documentation, algorithms, and readability of the code - which is its core purpose. Please don't conflate your minor development environment gripes with the excellence of Boost. It is really not fair.
I don't think it's an unfair complaint. Developers interface with the language using compilers and IDEs, and until recently (with libclang getting more popular) that interface has been terrible. In a lot of cases it's still terrible. It's a real inconvenience, and there's no benefit coming with the cost.
> Boost is extremely high quality in terms of its documentation, algorithms, and readability of the code - which is its core purpose.
I've used Boost for over a decade now, and IMO "extremely high quality" is a stretch. Some modules are better than others, but there's no getting around the fact that many of them are only necessary because the C++ standard library is so bad.
Not OP but - Boost is a large library that can be tricky to extract only the portions you want from, leading to instances where you compile a lot of unwanted code, leading to ballooning compile times in exchange for only a little bit of extra functionality that you want. In my experience boost typically ends up being all or nothing, and very hard to remove once integrated.
Has been terrible?
IMHO (eclipse and clion user) for C++ big projects the IDE experience is terrible!
Vim isn't better as you need to spend a lot of time configuring it plus ctags and multiple tabs doesn't work well together..
But so is the viewpoint that one of the major problems with C++ culture is the misconception that developer experience and high quality tooling are afterthoughts and orthogonal to "quality". In this viewpoint, readability in a editor or IDE at least as important as readability in a browser or email.
Complaining about an external third party library that is trying to fix the issues for its compile time and how fast it loads IDE is very obtuse and unfair. Boost is a bandaid. Don't complain about the bandaid, complain about what causes the wound. Overall, I agree with you that C++ has poor user-experience - don't blame Boost for it!
To be fair, it is very common to hear "well, why don't you just use Boost?" in response to the criticism of the C++ standard library. It may be an external third party library in name, but in practice it has long since became stdlib++ for idiomatic C++, and a testing ground for new libraries to be eventually adopted into the standard.
But...I will say some good ideas made it into the standard thanks to a first implementation in boost, though what ended up being standardized is typically much cleaner and more orthogonal.
Then again this is how we got the standard containers and so much more standard library goodness -- via Stepanov stepping up and writing the STL.
While I tend to agree with your points overall, I'd like to point out that this part irks me a bit:
> and is not actually necessary, but is placed there to actually give better error messages to users of the library
That's not the first time I see this kind of reasoning; you can find it all over programming. If the alternative is an API that sucks (and yes, template errors in C++ do suck), picking that alternative is not a very viable option.
Another scenario where this often pops up is whenever someone complains about very verbose error handling. If you want to write quality software, you shouldn't just skip that part (in fact I'd say it's the most important part of the code you're writing). In the same vein, there's a talk by Andrei Alexandrescu somewhere (on D), where he points out that probably 0% of the main functions ever written in C or C++ are correct in that they catch everything that can go wrong.
Also, since I find that most code should be written in a library style (i.e reusable, well-encapsulated) except if it's explicitly tied to a specific program (say config or cli argument parsing), I also don't find "only library implementers will have to deal with this" is a convincing argument. OTOH, at least, writing in a library style doesn't necessarily imply overly generic code, so that might not be that much of an issue.
I'm not convinced by the argument that it's fine since most people can just use a library someone else wrote. Requiring complex code to do something simple doesn't bode well if you want to try something complex. And when judging a programming a language it's a bit weird to reason from the perspective of someone not writing the code.
Nothing comes for free though. While we can easily write list comprehensions in Haskell or Python, their implementations are complicated. In the case of Haskell and Python, lists and comprehensions are language features, and for C++ ranges, they’re implemented as a library. Both approaches provide building blocks for users to build software and not have to roll their own implementations.
Haskell list comprehensions are a small layer of syntactic sugar over the monadic implementation, that is implemented on a library (that comes with the compiler, but it's still a library).
It is entirely dependent on lazynes, first class functions and complex compiler optimizations. But there isn't much specific code for comprehensions there.
Most of that code is for optimization or error messages. I think you could strip it down to:
auto for_each = [](R&& r, Fun fun) {
return r | view::transform(fun) | view::join;
};
I also removed inline and constexpr because he didn't need them in his example.
I don't disagree that it's ugly, but it's not as bad as it looks when you go through the standard library. Those are functions that have been written, optimized and generalized to handle all sorts of cases. Your needs are almost certainly more specific, and you will undoubtedly write functions that are much simpler.
That being said, I kind of agree. I would have preferred your list of features to getting Concepts. I find SIMD to be a particular sore spot.
I much prefer having separate operators for namespace/module qualification and member access. These things seem like they have something in common, but in practice, IMO, the languages that separate them are much cleaner.
Whether :: specifically is the best choice of such an operator is another question. But there aren't exactly many options left, and this one is firmly established by now, even outside of C++.
It is as if we don't learn! Same thing in Rust. :: is adds so much visual noise.
Julia is the only modern language with beautiful syntax. I think we should pay more attention to the aesthetics of a language just like we do with spoken languages. Certain languages just sound beautiful - Japanese and French.
C++ is old but Rust could have paid more attention to subjective aspects of a language.
> Certain languages just sound beautiful - Japanese and French
You've managed to pick the two languages whose sound irritates me the most, which I think illustrates the fragility of this kind of thinking perfectly.
This has been the story with C++ forever. I think currently half the standardization and compiler implementation bandwidth is taken up by turning C++ into the slowest virtual machine ever devised (constexpr).
The constexpr story is presumably very similar to what happened with templates. There was a genuine niche use case, and that was perfectly covered in the initial implementation. But the feature just lends itself perfectly to extremely clever, single A4 page example code. No one ever went "ooh" and "aah" at a basic_string::split paper.
This looks like something a library author would write while implementing a range and not something that end users would write while using ranges. Could you point to an implementation in a different language that offers a similar abstraction in a more succinct manner (While offering the same type safety and low runtime overhead).
I feel like what you’re touching at is kind of the core of why C++ can be so difficult to parse through sometimes. It really is trying to make a general purpose abstraction while also having a low overhead. This results in a need for detail and complexity that you can simply not need to worry about in languages like Python or Clojure.
When my university peers and I were learning C++, a lot of us strongly preferred using C99 since it didn’t seem as awful in terms of complexity and verbosity.
I really, really don't want cross-platform SIMD in C++. The reason I write asm is I know how the machine works and exactly what I want the machine to do. It's already hard enough to get what you want from the compiler and trying to make it cross-platform is guaranteed to make it even harder. If you don't believe me try opening an audio device or a serial port from Java, which purports to contain cross-platform libraries for these devices but in fact makes it totally impossible to use them.
As for std::string I never find myself using boost for any reason so I'm curious what the indispensable boost feature is for strings. The only thing I want from std::string is the ability to resize it without initializing allocated space, the ability to construct a string from an existing data pointer, and the ability to release the data pointer from an existing string (basically the union of a string a unique_ptr).
Do you use std::vector when working with SIMD? I find the SIMD alignment requirements make it difficult to integrate intrinsics with the rest of my code. Maybe I'm just missing something, but I'm having a hard time with it.
That's a good point. If you use a custom allocator with your vector then you can be sure of the alignment, although the compiler will pretend to be unaware of it. All that stuff I want for string goes for vector, too.
Not to mention the nightmare that is organizing the compiler on large projects. I've seen multiple large companies employ teams of 10+ engineers just trying to keep the compiler in order. Optimizations that take weeks or months to develop result in 10x improvements while I'm not aware of developers in other languages having this problem.
Seriously who, except for a small circle of academics, actually believes that this is good, readable, concise, maintainable code? Who asked for this?
When I read
I can't help but being reminded of Java [1][2].Meanwhile it's 2019 and C++ coders are still
- Waiting for Cross-Platform standardized SIMD vector datatypes
- Using nonstandard extensions, libraries or home-baked solutions to run computations in parallel on many cores or on different processors than the CPU
- Debugging cross-platform code using couts, cerrs and printfs
- Forced to use boost for even quite elementary operations on std::strings.
Yes, some of these things are hard to fix and require collaboration among real people and real companies. And yes, it's a lot easier to bury your head in the soft academic sand and come up with some new interesting toy feature. It's like the committee has given up.
Started coding C++ when I was 14 -- 20 years ago.
[1] https://docs.spring.io/spring/docs/2.5.x/javadoc-api/org/spr... [2] http://projects.haykranen.nl/java/