Settings

Theme

You can't make C++ not ugly, but you can't not try (2010)

apenwarr.ca

142 points by soundsop 6 years ago · 186 comments

Reader

gumby 6 years ago

This decade old article describes an ancient and obsolete language (C++03 probably though some of the text suggests it might be C++98). It's not worth reading in 2020.

Modern C++ is a very different language though it can still almost completely interoperate with quite old code. C++11 and C++14 already addressed most of the things brought up, and contemporary C++ (obviously most code is not in it yet) even supports generic template functions with a straightforward syntax (i.e. use auto instead of <..>).

  • Rusky 6 years ago

    To the contrary, modern C++ has solved very few, if any, of the problems described in the article.

    Being generous:

    * There's now `std::string_view` to address some of the problems with `std::string`, but the rest are still there. There are some attempts to specify the encoding now, at least.

    * Lambdas and `std::function` pretty much solve the function pointer complaints, with some added complexity.

    * Containers still do silly things when you use `c[..]` syntax with no element there. (Both when trying to insert and when trying to retrieve!)

    * The general level of language size and complexity, especially around templates, has only gotten worse. Concepts will finally help in some ways here.

    • rumanator 6 years ago

      > To the contrary, modern C++ has solved very few, if any, of the problems described in the article.

      The author, after a long and dubious appeal to authority, claims that:

      * C++ exceptions are outright evil and should be avoided

      * C++ string class "is so utterly godawfully stupid" because it has no garbage collection or refcounting, and in short doesn't precisely match Python's implementation. He also felt personally insulted by the fact std::string is a template class.

      * He feels C function pointers are fine but believes function pointers in C++ weren't extended to support pointers to member functions, which he then backtracks and says they actually exist but they are "so horrendously ill-conceived that absolutely nobody uses it for anything", thus showing his ignorance and lack of experience.

      * For some reason he criticised std::map for the way it's std::map::operator[] returns a reference to the mapped value, eventhough that's what it is supposed to do by design and by the principle of least surprise.

      * The author made other claims, but the text grows so much in the style of "foaming from the mouth" that it's just better to stop reading after a point.

      In short, this article is just a rant where someone vents that a programming language other than Python is not Python at the eyes of the author. It's a pretty unconvincing rant and full of logical and rational holes, but a rant nonetheless. So the author loves Python and his personal take on C++ does not match his view of Python. That's fine. It's just weird how this sort of text floats up in a technical forum after over a decade after it has been published, as if it's expected to add or say anything of value.

      • klingonopera 6 years ago

        > "Using the "+" operator with two string constants gives a weird compiler error about adding pointers?"

        He'd have that in C too, and that's the expected behaviour. What he would want is:

          #define STRING1 "hello"
          #define STRING2 "world"
          const char* gString = STRING1 " " STRING2 "!\n";
          #include <stdio.h>
          int main() {printf(gString); return 0x0;}
        • AstralStorm 6 years ago

          If you make them string constants rather than character arrays with the new literal ""s added in C++14, problem is gone. Author is using outdated C++.

          This is all due to C compatibility.

          • klingonopera 6 years ago

            Ah, yeah... no clue about C++'s strings, I just read his question and that sounded familiar to the problem, whose solution I posted above.

            There's some overlap between C and C++, but maybe this wasn't such a case? Sorry, if it was uncalled for.

      • Rusky 6 years ago

        You're not wrong, I wrote off the insubstantial stuff as rhetorical and started my comment with "being generous." :P

        But on the other hand, even some of these less-helpful points have an element of truth:

        * A lot of people/domains/codebases do wind up avoiding exceptions, for good reason. There is even an active proposal to provide an alternative kind of exceptions to solve their problems!

        * Member function pointers are quite a mess, introducing a lot of complexity by not being compatible with regular function pointers. It wasn't until long after this article that those incompatibilities were papered over with the standard library.

        * std::map::operator[]'s behavior arguably does not follow the principle of least surprise- it's a giant footgun.

        The author goes out of his way to point out that the changes he wants don't require GC or an interpreter, despite all the comparisons to Python. It's not an insubstantial comparison in that light.

      • ahaferburg 6 years ago

        Your version:

        > The author [...] claims that C++ exceptions are outright evil and should be avoided

        What the article actually says:

        > This includes the RTTI and exceptions stuff; C++'s versions of those were enough to convince a whole generation of programmers that introspection and exceptions were outright evil and should be avoided

        Your reading comprehension is lacking. You didn't understand what he is talking about. Why is Google not using exceptions?

    • gpderetta 6 years ago

      > Containers still do silly things when you use `c[..]` syntax with no element there. (Both when trying to insert and when trying to retrieve!)

      FWIW, I find std::map operator[] creating an object extremely convenient and it is very annoying having to use defaultdict to get the same behavior [edit: in python]. So ugliness really depends on what you are used to.

      > The general level of language size and complexity, especially around templates, has only gotten worse. Concepts will finally help in some ways here.

      variadic templates and type deduction greatly help. A lot of complex template metaprogramming is suddenly not needed anymore (I've used boost::mpl a lot in the past, but not once in the last 10 years). constexpr functions also helped get rid a lot more use cases. Finally if constexpr made a lot of sfinae hacks obsolete.

      • eqvinox 6 years ago

        > FWIW, I find std::map operator[] creating an object extremely convenient and it is very annoying having to use defaultdict to get the same behavior [edit: in python]. So ugliness really depends on what you are used to.

        Adding behavior to replace a NULL/None return with a default object is pretty easy.

        Removing the default object that you've been inflicted upon... how would you go about that?

    • cmroanirgo 6 years ago

      If everyone can forgive my ignorance... I used to call everything in the std:: namespace as just that, the standard template library (STL) (whatever it's iteration). So is this C++03 /C++11 stuff just updates to this library, or is std::string recognised at the compiler level? (Genuinely confused).

      (In old man voice) We ended up rolling out own stuff and marking std:: verboten. Why? Stl was too slow, too verbose, and too hard to grok the stack when debugging. We ended up with less memory fragmentation, less dangling ptrs, etc etc. In the rare case there was something in STL that was actually cool (or faster, which was very rare), we'd gut it and reef out the part that was cool to use in our implementation.

      I presume these comments aren't popular (sorry about that, but this is during 90s and early 00s when dev cycles were clearly different). Eg. We had string classes in all different flavours, some would interop, some wouldn't. Eg. We had tree and hash classes that, while templateable, had a few core implementations that made compilation fast. We had various ptr management systems (ref counted, stack based, etc).

      We made STL between verboten in all APIs because we been burnt so many times using (/trying to use) other ppls APIs that exposed STL in its library. (We were exclusively a windows shop, if that helps understand my confusion... PS. I'm retired these days and have been out of the c++ game >10yrs)

      • kllrnohj 6 years ago

        > So is this C++03 /C++11 stuff just updates to this library, or is std::string recognised at the compiler level?

        std::string is in the STL. So is std::string_view. There are language changes as well that enable some of the new STL additions but some are just STL updates and you could "backport" them so to speak. Or do the same thing but yourself.

        Making std:: verboten these days would likely be a mistake, though. There's so many things that are just not controversial and not worth re-implementing. Like std::unique_ptr. Or std::array.

        Most of the containers are still better off ignored, though, many of them unfixable. The least worst of them is std::vector and it's still _ok_ but even there things like absl::InlinedVector are worth a strong consideration instead ( https://github.com/abseil/abseil-cpp/blob/master/absl/contai... ). Or boost's small_vector ( https://www.boost.org/doc/libs/1_60_0/doc/html/boost/contain... )

        • vlovich123 6 years ago

          I still think the STL containers are the sane default to reach for & switch to more obscure ones when the problem domain and performance requirements say to use a different one.

          • kllrnohj 6 years ago

            You have that backwards. The STL containers are for when you have a hyper-specific niche use case. They are otherwise terrible defaults and everyone should use boost or abseil by default otherwise.

            std::map, for example, is only appropriate if you need a red-black tree specifically. Which almost nobody does. std::unordered_map is less awful, but abseil has a literal straight upgrade. With the same API. So... why would you pick the slower thing when you're using C++? std::vector is only really appropriate if you know you never have small vectors, which is again a more obscure situation.

            • rumanator 6 years ago

              > You have that backwards. The STL containers are for when you have a hyper-specific niche use case.

              That assertion makes no sense at all. The stl contrainers work very well as basic generic containers that can safely be used for pretty much any conceivable use where performance isn't super critical. I'm talking about cases like, say, you need to map keys to values but you don't really care about performance or which specific data structure you're using. That's stl's domain: robust, bullet-proof implementations of basic data structures that are good enough for most (or practically all) cases with the exception of a few very niche applications.

              If you happen to be one of the rare cases where you feel you need to know if a container is built around a red-black tree or any other fancy arcane data structure, and if this so critical to you that you feel the need to benchmark the performance to assess whether you either need to use non-defaults or completely replace parts or the whole container with a third-party alternative... Then and only then the stl is not for you.

              • kllrnohj 6 years ago

                This makes no sense. The STL is the specialized containers with obscure performance characteristics & behaviors. Boost & abseil provide the generic, reasonable default ones.

                You're arguing it's better to use something that's across the board worse for nearly every user, and by a lot, just because... why? It's slightly more convenient?

                • rumanator 6 years ago

                  > This makes no sense. The STL is the specialized containers

                  It really isn't. The whole STL is, by design, a template library packed with generic data structures that are designed to have very robust defaults and still be customizable and extensible.

                  When the defaults are good enough, which is the case in general, the STL will do. If you have a niche requirement (say, games) or feel adventurous, you adopt custom and/or specialized solutions.

                  This has been the case since the STL's inception. They are the standard, default library. I can't understand how someone is able to miss this fact.

                • pjmlp 6 years ago

                  Because STL is part of the compiler, guaranteed to work on every platform supported by the compiler, and does not require lots of paperwork for adoption at many shops.

                  • kllrnohj 6 years ago

                    > Because STL is part of the compiler, guaranteed to work on every platform supported by the compiler

                    No it isn't and no it's not. There are even platforms where an STL isn't even provided out of the box, you have to pick one. And there's quite a few at that - libc++, libstdc++, stlport, etc...

                    But clang, g++, etc... they don't care. To them it's just another library you're linking against, no different from any other dependency. They don't provide it, they don't care. It can even be quite a pain in the ass to use the "native" STL of a given compiler, like trying to use libc++ with Clang on most Linux distros.

                    • pjmlp 6 years ago

                      Only ISO C++ compliant platforms matter.

                      As having multiple implementations to choose from, that is the beauty of language standards.

                      Abseil and boost do not fall under that umbrella, and I belong to the C++ subculture that never ever touched them, or plans to.

                      • cmroanirgo 6 years ago

                        STL by it's very name is a library, and because it ships with a compiler, doesn't mean it's part of that compiler. It's just a library, like Boost. It might have some ISO standards behind it: great! But it's still a library and not intrinsic to the language itself (see my orig question).

                        eg. to use an 'int' you just declare one and use it. To use strings, you need to include <string.h>.

                        • gpderetta 6 years ago

                          STL (if we use this name as a arguably incorrect alias for the c++ standard library) is intrinsically linked with the language. A lot of its implementation requires primitives that are not part of the language (although often exposed as intrinsics as an extension). An implementation can and often does treat the names under namespace std specially and assumes invariants and behavior.

                        • pjmlp 6 years ago

                          The S in STL stands for Standard, something that neither boost nor Abseil are, or will ever be.

                          • kllrnohj 6 years ago

                            Being in the standard doesn't mean they are good or should be used.

                            java.util.Vector is standard as well but you'd be laughed out of a code review if you tried to use it anywhere. Similarly std::auto_ptr is in the standard, and is universally agreed to be trash to avoid like the plague. So much so it was (eventually) removed. The container section of the library has just sadly not had the same deprecation & removal of bad code put into it.

                            The claim was "STL containers are the sane default" not that "STL containers are more available". I stand by that claim being wrong. You have so far not actually attempted to counter it on any technical grounds, just bad appeal to authority fallacies.

                            • pjmlp 6 years ago

                              Except that java.util.Vector is for backwards compatibility and everyone should use java.util.ArrayList instead.

                              Likewise std::auto_ptr got replaced by std::unique_ptr.

                              As you see the standard library keeps the knowledgable developers covered, no need to look elsewhere.

                              You are indeed wrong.

                    • de_watcher 6 years ago

                      You're a troll from 2003. I remember huge forum threads about STL.

                      Now it's the Standard Library like in any other language.

            • mehrdadn 6 years ago

              Small vectors break iterator guarantees, for one thing. They also really only make sense for tiny objects (ints, etc.) given you don't want a pickup truck's worth of data on your stack. They're most definitely not general-purpose.

              There are lots of subtleties STL containers have to worry about in designing containers, regarding everything from iterator & pointer invalidation to allocation and allocator propagation. All this is because they're designed to be general-purpose and support most conceivable use cases. Their replacements have to trade off requirements in order to get better performance or otherwise improve on some axes.

              • kllrnohj 6 years ago

                > Small vectors break iterator guarantees, for one thing.

                It only breaks swap of the container itself during iteration. Which is a super niche condition.

                And that swap also invalidates some of std::vector's iterators as well - specifically the end() iterator.

                > They also really only make sense for tiny objects (ints, etc.) given you don't want a pickup truck's worth of data on your stack. They're most definitely not general-purpose.

                Of course they are still general-purpose. They can (and do) specialize on the size of the object being contained. The only reason std::vector doesn't also have SSO is because it's an ABI break. Not because it's better in some way or less fragile. Legacy is the only reason.

                • mehrdadn 6 years ago

                  > And that swap also invalidates some of std::vector's iterators as well - specifically the end() iterator.

                  And they don't invalidate the iterators that point to actual elements, which was kind of the entire point I was making. Don't let that stop you from trying to make it look like I'm just blurting out nonsense, though.

                  • kllrnohj 6 years ago

                    You made a broad, vague claim that iterator guarantees were broken. You misrepresented it as being a much larger issue that it actually is. Nearly all iterator guarantees are not broken. One very specific guarantee in one very specific case is, that's it. And it's a rare, not general, case at that, making the trade-off necessary to achieve it a bad default.

                    • gpderetta 6 years ago

                      returning, moving, swapping a small vector would break any pointer to an element. That's a big deal. Interior pointers are used all the time (that's, more than performance, the primary reason that reserve exist).

              • leetcrew 6 years ago

                how do small vectors break iterator guarantees?

            • Espressosaurus 6 years ago

              Because that way you don't need to haul in dependencies unless you have a real reason.

              std::function is fine for prototyping, but its size hit is extreme, so in embedded code we use other implementations. But where size and speed doesn't matter? Why bother?

              • kllrnohj 6 years ago

                > Because that way you don't need to haul in dependencies unless you have a real reason.

                These are all largely header libraries. You're already hauling in a dependency, and in every c++ file that uses it at that.

                > std::function is fine for prototyping

                std::function isn't part of the containers library of the STL (containers being all the stuff here: https://en.cppreference.com/w/cpp/container ). I agree std::function is fine, it even has a pretty reasonable small-size optimization.

                • mbeex 6 years ago

                  > These are all largely header libraries.

                  That's not even true for boost, no matter if they always advertise that. The lib is also notorious for bad decomposability (using only a subset without installing the whole monster). Not to speak about idiosyncratic naming and build system, making it sometimes hard to include it in meta builds of other libraries and frameworks.

                  In sum: Anyone sensible, regarding different kinds of footprint and dependencies will think twice, before pulling in these kind of libraries.

                  • gpderetta 6 years ago

                    boost does not advertise being an header only library. Some boost libraries are header only, some offer hybrid modes, some require a library to be linked in. Each case is documented individually.

                    Boost is better thought as a loose collection of libraries that try to follow some common design principles instead of a single monolithic library.

                    I can't defend the build system though.

              • fctorial 6 years ago

                I'll use java where size and speed doesn't matter.

                • pjmlp 6 years ago

                  I rather have my phone be fast, without the apps taking ages to download.

                  • fctorial 6 years ago

                    Java apps are actually pretty small in size. It's the assets that make take up the most space, and that is independent of language.

                    App installation is a one time cost anyways. Did you mean s/download/startup/?

                    • pjmlp 6 years ago

                      I was giving a counter argument in a very not intelligent way.

                      Being a polyglot developer, with Java, .NET and C++ as my favourite stacks, means I don't suffer from Java hate from C++ point of view, rather enjoy how one can combine their strengths to achieve a good outcome.

            • steerablesafe 6 years ago

              abseil's might have a similar API but it's most definitely not the same (function signatures looking the same doesn't make it the same API). Some of the standard containers can't be fast because too strict/specific standard requirements, not because they don't try hard enough.

              Having said that I think using abseil's containers is reasonable, even as a default, if you can afford the dependency.

              > std::unordered_map

              AFAIK unordered_map is the most awful of all standard containers.

              • leetcrew 6 years ago

                what's wrong with unordered_map? it's at least more useful than map.

                • aw1621107 6 years ago

                  From what I understand its API is overspecified to the point that it basically forces a "traditional" array-of-linked-buckets implementation, which can be horribly slow on modern processors due to the need to chase pointers. This means a lot of the potential performance improvements of allowing map elements to be unordered are lost.

            • pjmlp 6 years ago

              Because C++ is a nice, mostly safe, general purpose language, being spoiled by the 1% "performance above anything else crowd".

              I want my OWL, VCL back, not an hash table able to do lookups in micro-seconds

              • TeMPOraL 6 years ago

                I know you're exaggerating here a bit, but come on, a hash table not able to do lookups in micro-seconds is just garbage. I expect better of any language, not just C++.

                • pjmlp 6 years ago

                  Indeed I was.

                  The point I was trying to make, was that from productivity point of view, there are more relevant stuff to fix in C++ than algorithm complexity of STL implementations.

                  Like catching up with what Java 5 standard library offers for networking and parallel/concurrent programming.

            • gpderetta 6 years ago

              on the contrary, std::map is a good default container with predictable performance. If you need fast O(1) look up std::unordered_map is really not fit for purpose and requires you to come up with an hash function.

              • kouteiheika 6 years ago

                > on the contrary, std::map is a good default container

                If you care about performance then it's not. (And if you don't care about performance then why are you using C++?) The standard requires that `insert` will not invalidate iterators, which basically forces everyone to implement `std::map` as a red-black tree, and those are pretty bad performance-wise on modern hardware mostly due to cache misses.

                > If you need fast O(1) look up std::unordered_map is really not fit for purpose and requires you to come up with an hash function.

                Modern hash table implementations (along with a modern hash function) are exactly what you should use if you need fast average-case O(1) lookup, so I'm confused why would you say that it's not fit for purpose? Unless you specifically meant only `std::unordered_map` which, yes, is pretty atrocious performance-wise (again, due to the iterator invalidation requirements).

                • gpderetta 6 years ago

                  I meant specifically std::unordered_map, because how it is specified in the standard it is very hard to implement it efficiently. If you need performance, yes, use a good hash table implementation. But even in C++ you do not need to be shaving cycles everywhere and there std::map is better than std::unordered_map.

      • vlovich123 6 years ago

        Every new standard incorporates language & library changes. A perfect example of this is r-value references. That was a new language feature in C++11 & was adopted by all the standard library containers & algorithms.

        Not sure which part of std::string you're referring to but the compiler generally doesn't contain any knowledge of the library itself. It does goes the other way though where the standard library has to know how to implement certain functionality on a given compiler (some type traits functionality IIRC isn't possible to implement without compiler builtins that expose the AST to you). I think Rust has taken a more sustainable approach with their macro system which can modify the AST instead of relying on builtins but even in Rust I suspect they use builtins in certain places of their standard library.

        Today's STL implementations are going to be better performing and more robust than anything you'd write yourself so generally a good idea to stick to it as a rule of thumb for the majority of code.

        • atq2119 6 years ago

          > even in Rust I suspect they use builtins in certain places of their standard library.

          There are places in the rust standard library which just omit the implementation because it's magically filled in by the compiler based on the type and function names. Which, for really low-level and fundamental functionality, seems fair game to me.

        • gpderetta 6 years ago

          As non exhaustive list, compilers have have intimate knowledge of:

          * various operator new * all the type traits * a lot of the names inherited from the C library

          compilers could do much more, but in general they prefer to implement generic optimizations instead of targeting a specific library name (for example removing allocation for stack allocated std::strings was not done until the generic removal of alloc/free was implemented).

      • kccqzy 6 years ago

        That is indeed a very different time. I believe in the 90s the STL was considered too radical in its use of templates to be practical. People's perception has come a long way. Heck, instead of sane std::list<T> people used to do intrusive linked lists by having T inherit from a linked list base class. What a terrible idea.

        • AstralStorm 6 years ago

          Intrusive lists are so good they are likely to land one of these days in standard. Except via templates and traits not silly unnecessary inheritance. (Boost-like.)

        • taejo 6 years ago

          Do people use std::list? I can't think of a time when I've needed a non-intrusive linked list (either vector is better or I need an intrusive list).

      • pansa2 6 years ago

        > We ended up rolling out own stuff and marking std:: verboten.

        I think that’s still pretty common, especially in game development and/or when compiling with exceptions disabled. AFAIK std::vector is fine, and probably std::string, but for things like std::[unordered_]map, Abseil and Folly have equivalents that are slightly non-standard and much faster.

    • Tallasatree 6 years ago

      2 people equally confident in their own, completely opposing opinions. Every comment enabled website in a nutshell.

      • ajna91 6 years ago

        The real problem is everyone else who pattern-matches confidence with competence and upvotes.

      • saagarjha 6 years ago

        I think this transcends comment enabled websites.

      • rumanator 6 years ago

        > 2 people equally confident in their own, completely opposing opinions.

        Also known as a debate.

      • Ragnarork 6 years ago

        And as long as both are honest and well argued, that's incredibly rich.

    • UncleMeat 6 years ago

      I think most people agree that operator[] is a big footgun for containers, but rigorous use of const helps at least prevent surprises.

      • gumby 6 years ago

        And operator new is hardly used any more in modern C++ code.

    • _pmf_ 6 years ago

      > Containers still do silly things when you use `c[..]` syntax with no element there. (Both when trying to insert and when trying to retrieve!)

      This is surprising for Java devs, but given that value semantics must be supported, there's no way to avoid it.

    • dheera 6 years ago

      Yep. There isn't even a split/join method in std::string ... you need to use boost for that (wtf)

      • BooneJS 6 years ago

        Or Abseil, which had a lot of handy string manipulation functions including Append/FormatAppend.

  • JonathonW 6 years ago

    The author also seems to really, really want C++ to be Python, despite that they're completely different languages for completely different purposes that make completely different sets of design tradeoffs.

    In that respect, the author probably wouldn't be happy with modern C++, either. For example, C++ is never going to be a garbage-collected language (by default, at least). Modern C++ gives you better tools to deal with that, but the core design concerns that make C++ the way it is haven't gone away.

    • pjmlp 6 years ago

      Actually C++11 introduced a GC API.

      • gpderetta 6 years ago

        It seems to me that just a couple of do nothing placeholders were added to please Hans Boehm and get him to work on the c++ memory model :).

        • pjmlp 6 years ago

          Even so, there are the .NET, Unreal and COM/UWP programming models as well, which while not taking advantage of C++11 GC, do bring a GC into C++ world. :)

    • Someone 6 years ago

      I’m not aware any exist, but implementations of C++11 can have garbage collection by default. https://isocpp.org/wiki/faq/cpp11-library#gc-abi:

      ”Garbage collection (automatic recycling of unreferenced regions of memory) is optional in C++; that is, a garbage collector is not a compulsory part of an implementation. However, C++11 provides a definition of what a GC can do if one is used and an ABI (Application Binary Interface) to help control its actions.”

      • deaddodo 6 years ago

        That's not "by default". The compiler doesn't provide it, the language provides for you as the developer using the language to implement your own (or use a third-party) GC and utilize it. D and Rust both offer the same amenities and are not "garbage collected" languages.

        • kllrnohj 6 years ago

          > D and Rust both offer the same amenities and are not "garbage collected" languages.

          Rust isn't but D is absolutely a garbage collected language. The GC is provided by default and expected to exist. https://dlang.org/overview.html#resource & https://dlang.org/spec/garbage.html

          There's a non-GC'd subset of D, though. That would be the BetterC subset https://dlang.org/spec/betterc.html

          • bachmeier 6 years ago

            I think it's more accurate to say D's GC is expected to exist if you want to use the full language. That's sensible, because the option to use GC makes some things practical that otherwise wouldn't be. You can disable or simply avoid the GC, and you can add @nogc attributes to your code if you want to be certain there won't be any GC allocations. BetterC certainly does guarantee there's no GC, but that's a limited (for now at least) subset of the full language.

        • dfox 6 years ago

          The quote seems to be about the compiler/environment being permitted to offer GC, ie. permits you to write C++ implementation on top of some GC'd runtime. It does not say anything about you as a language user having enough introspection capabilities to write an GC. As a side note it seems that one can (ab)use smart pointers enough to build essentially working tracing GC on top of that, I've seen that done and well, the performance is horrible, obviously.

  • downerending 6 years ago

    You wish. Unless I'm terribly misinformed, "modern C++" contains all of the features of the prior versions, which means that in the real world you have to learn and deal with all of it.

    • jfkebwjsbx 6 years ago

      That is the point of backwards compatibility. It is a feature, not a bug.

      Whether that feature is a good idea or not and to what degree, is the question.

      • downerending 6 years ago

        I'm not saying it's a bug. I'm saying that backwards compatibility means that you still have to deal with all of the historic flaws of the language today.

        The article is indeed still relevant.

        • UncleMeat 6 years ago

          But that’s true for literally everything. If you have a cpp code base and are building new stuff in rust to interop then you still have legacy cpp code even though rust has fewer problems.

          • KptMarchewa 6 years ago

            Not really. Legacy stuff can be contained. For example, you could specify language version level in file and it would disable removed/deprecated stuff.

      • Quekid5 6 years ago

        ... but it's worth considering that this is a 'question' from 2010. Just saying...

    • gumby 6 years ago

      I don’t really understand your point. Just because all the implementations support the `asm` keyword doesn’t mean you need to know the assembly language of every CPU that has a c++ compiler.

      You can write your code in modern c++ and ignore prehistoric carbuncles (some of which have been deprecated and eliminated FWIW). And you can call external code written in older dialects without having to look into its source code.

      • downerending 6 years ago

        That's great for a greenfield project where you're the only developer. If it's multiple people, you have to deal with whatever subset the powers-that-be on your team wish to use. If it's not greenfield, you have to deal with the subset exists in the codebase.

        And if you're a Dirty Harry type like me, debugging issues with dozens of disparate codebases written in various crap subsets, you pretty much have to know the whole cursed thing.

    • saagarjha 6 years ago

      Not everything; see for example std::auto_ptr.

    • Ragnarork 6 years ago

      > which means that in the real world you have to learn and deal with all of it.

      This is not what happens in the real world though.

      While I agree that you have to deal with whatever is in your codebase at a given point, it also doesn't imply that you have to use everything and that everything can be useful for your project.

      As standards keep coming and features get added, it's still increasingly prevalent to see guidelines and awareness around the topic of choosing your own subset of C++ to work with.

      The main drawback is mostly that it incurs a cost in terms of brain power to discipline yourself to keep your work within a restricted set of language and standard library features.

      It's extremely rare (and even debatable) whether a single person masters all the aspects and features of C++ (and if there's one, it's probably Alexandrescu).

      • downerending 6 years ago

        In my world (see other reply), I get to deal with whatever is in dozens of codebases written in many times and places. The superset of everything they're using is pretty much everything.

        Beyond that, even the "good" modern subset of C++ is crazy complex. I've sat in a room with some of the better C++ programmers in the world while they study and try to comprehend the new features like rabbis interpreting the Talmud.

        If there's one concise, fundamental rule of software engineering, it's this: Complexity kills.

    • rumanator 6 years ago

      > which means that in the real world you have to learn and deal with all of it.

      That makes as much sence as claiming that to develop software in, say, Python on the real world you have to learn and deal with all of its standard library.

      That is not the case. That has never been the case ever for any programming language, be it large or small. Specially in C++, where since it's inception the standard approach is to, at best, adopt a subset of all features and stick with it.

      • tonyedgecombe 6 years ago

        Making a language so big and complex that you can't expect to understand it all isn't a good idea. You might be able to get away with using 50% of the language but if some project you depend on uses a different 50% then you have a problem.

        • rumanator 6 years ago

          IMHO C++ is unfairly singled out with regards to it's extension. That criticism rings true if applied to pretty much every single programming language ever devised, including "small" languages such as C.

          • downerending 6 years ago

            C, even today, fits entirely in my head. C++ does not.

            I suspect this is not an uncommon situation among programmers.

        • 2zcon 6 years ago

          OK but if you're a competent, adult software engineer it should be fairly easy to overcome that problem.

          • tonyedgecombe 6 years ago

            Then why is it so common for people to try and select a subset of the language. Arguing about what people should do is pointless, you have to look at what they do do.

            • 2zcon 6 years ago

              If there are multiple ways of achieving the same result (e.g. assignment) and you want to be consistent, you have to choose a subset. If there are features that time has shown to be less-than-ideal (e.g. malloc/free) you choose a subset while the language avoids removing breaking backwards-compatibility.

              So when you go to another project that made different decisions, what you should do is understand the decisions. It's quite easy. And that's why it's what people do do, and it's why people are capable of contributing to projects other than their first.

              If you program in C++, it's the same skill you used to learn one or more of {CMake, Makefiles, scons, bash, python} except it's easier because you already understand the programming language's model.

          • rumanator 6 years ago

            > OK but if you're a competent, adult software engineer

            "No true Scotsman" and all, please refrain from posting messages that add nothing of value besides petty insults thrown in broad brush strokes.

            • 2zcon 6 years ago

              Much as I didn't mean to insult anyone with my post, I'm sure you meant to add value by calling it petty and fallacious.

              Picking up features in a language you already use is easier than learning the language to begin with which itself is easier than learning to program at all. If you were able to read the documentation to get to 50%, you have all the skills needed to pick up the rest and get to the difficult part of picking up a new project: understanding the problem space. If you're capable of learning the meaning of an API you've never seen before - and you will, if you're not writing your last project verbatim - then you're capable of learning what a lambda means. This is not me saying that all programmers should be able to do this, it's me saying that doing this is a predicate of engineering software.

              • rumanator 6 years ago

                > Much as I didn't mean to insult anyone

                You accused anyone who did not agreed with you of being incompetent and immature. In the very least own your own claims.

                • dang 6 years ago

                  Would you please stop posting in the flamewar style to HN? You've been doing it a lot lately, and it's really not what this site is for. What we want here is curious conversation, which generally requires stepping away from the keyboard when feeling provoked, even if other people are doing or saying bad things.

                  https://news.ycombinator.com/newsguidelines.html

      • downerending 6 years ago

        If your job involves maintenance or support of multiple Python codebases, you do have to learn it all. And at least before Python 3, this wasn't that hard. (Python 3, unfortunately, has made the language a lot more complex.)

  • nmeofthestate 6 years ago

    True - some of the specific problems have been handled, but C++ is still kind of a disaster. The language has got even more complex in many ways. Some coders like that and get really into understanding the byzantine complexities, so the codebase inevitably gets dragged into unmaintainable, hard-to-debug cleverness. From my experience, C++ development is getting better, largely because tools are getting better at hand-holding and pointing out all the foot-shooting mistakes you can and do easily make on pretty much every line.

    Maybe C++30 will have fixed everything...

  • carterehsmith 6 years ago

    Sure, "if" all of that old code (how many billions of lines of code?) was rewritten and redeployed since.

    If not, then... the above statement does not stand.

  • kevin_thibedeau 6 years ago

    Some of us still have to use outdated C++ compilers where the ::std namespace doesn't exist and all the modern goodies are but a dream.

  • kazinator 6 years ago

    C++11 was just around the corner then, in draft form. The article shows awareness of it.

virtualritz 6 years ago

I recently picked up a C++ codebase I wrote maybe 30% myself and which was last touched in 2013. It's a plug-in for a DCC app. The 3rd party API it communicates with was changed so refactoring was inevitable.

Before I started refactoring I decided to make everything 'less ugly' by moving it to C++17. Which would also help me get back into the code, after eight years.

I spent two days on this. Then I decided: fuck it. I will RIIR™.[1]

It will mean that it will take me at least two months to get a beta of the product with feature parity vs. maybe a week to port the old codebase to the new API.

But on the other hand the C++ code is littered with stuff that 'just works' but can explode in your face and which eventually needs some safety net written around it. Which will likely take at least two months to do properly.

The truth is: after one and a half years of Rust C++ feels painfully ugly.

For context: I started with C++ with Borland's TurboC++ (moving from C) when I was 15. I used C++ for almost 30 years by now. It's about time.

[1] Yes, I did read http://adventures.michaelfbryan.com/posts/how-not-to-riir/

unlinked_dll 6 years ago

Most of what the author complains about w.r.t callbacks is fixed with std::function and lambdas (member function pointer syntax is necessarily weird, because methods aren't just normal functions). I definitely don't miss the days of std::bind. Nowadays you just do something like

    using Callback = std::function<int(int)>; // or whatever
    Callback cbk = [&](int i) { return instance.method(i); };
I've also seen some real evil hacks that rely on casting 0 as a pointer to a class and relying on the ABI's spec for vtable layout to calculate the pointer of the function as an offset from the `this` pointer. Because that's easier to remember than the syntax for pointer-to-member functions.
  • WesternStar 6 years ago

    It is notable that this code forces a conversion to std::function which IIRC can actually require more space than the lambda it's attempting to store. For lambdas just use auto they don't have a type don't worry about their type.

  • AnimalMuppet 6 years ago

    What's so awful about pointer to member? I've used it a couple of times, and didn't think it was particularly weird. I mean, yes, I had to look up the syntax, but it was rather straightforward.

    • gpderetta 6 years ago

      there is nothing wrong with them, but pointers to member do not close over this, so you still need some form of binding to use them as callbacks.

      • AnimalMuppet 6 years ago

        Well, when you invoke a pointer to member, you need an actual object (a this) to invoke it on. So when the member runs, it has a this.

        But it sounds like what you want is a "handle" or some such term, by which you can invoke a member function on an object, and all you need to do so is the handle. That's a different problem than pointers to members are trying to solve, but you can do that quite easily with a function object. That's essentially a roll-your-own closure, and since you can define whatever data members you want, you can close over anything.

        One thing you have to watch out for, though, is lifetimes. C++ is not garbage-collected, and so it will not preserve an object just because another object has a pointer or reference to it. If you create a function object that captures a pointer to member, and a "this" to invoke it on, and the "this" gets destroyed, and then you use the function object, you're going to get chaos.

  • PaulDavisThe1st 6 years ago

    Using lamdba's requires grappling with entirely new syntax compared to anything else in C++ (or C).

    Using various bind equivalents just requires you to know what you're doing:

    boost::function<int(int)> cbk = boost::bind (&SomeObject::some_method, instance_of_some_object);

    ...

    cbk (22); // invoke callback

    In addition, the author's complaints about nobody using ptr-to-method is absurd. Even in 2010, anyone using libsigc++ or its (few) equivalents was using them, which meant that any GUI app written using GTKmm was full of them. What's not to love?

  • a_t48 6 years ago

    There's also

      Callback cbk = std::bind_front(instance, Instance::method);
    
    in C++20. I'm going to ignore std::bind as it has weird conventions. :)
crazypython 6 years ago

You can make C++ pretty: it's called D. https://dlang.org/comparison.html

Here's the D<->C++ intercompatibility project: https://wiki.dlang.org/Calypso#Current_status

gpderetta 6 years ago

> The problem is that the default C++ string class is so utterly godawfully stupid. No garbage collection? Check. No refcounting? Check.

the irony is that in 2010, many std::string implementations were in fact reference counted (including libstdc++). This was generally considered a major mistake (because it doesn't work well with threads and when it does is a major performance pitfall) and prohibited in C++11.

MrBuddyCasino 6 years ago

Apenwarr‘s comment to this thread: „ I have since stopped trying to program in C++ at all. C is still ok, sometimes.“ [...] „I want to like Rust; maybe someday.“

https://twitter.com/apenwarr/status/1232848468156256256?s=21

Ar-Curunir 6 years ago

The followup article (https://apenwarr.ca/log/20100721) talks about a potential C/C++ replacement, and seems like a lot of points match up with Rust

ggambetta 6 years ago

  [The [] operator] is an absolute 
  failure of engineering!
  Do you want to know what real
  engineering is? It's this:

  map_set(m, 5, "foo");
  char *x = map_get(m, 5);
So like map::insert and map::at? Did these not exist in 2010?
dathinab 6 years ago

Didn't got much better since then with regard to that aspect. IMHO C++ tried to adapt many features which had shown succesfull since then, but instead of properly putting them into the language they often got implemented in a way which I would describe as "somehow hacked in to try to avoid to actually introduce new features" but others might describe as implemented in a very C++ish way in symmetry to other features. Anyway the result is often sup-par. Not downright shit but worse then it should be and more important making the features work less good (from a complexity+usability POV) then in the languages they where copied from.

AtlasBarfed 6 years ago

"C++ isn't a language, they say, it's a language construction kit! Build the language of your dreams in C++! And it'll be portable and scalable and fast and standardized!"

This is the power and achilles heel of Lisp as well.

  • nineteen999 6 years ago

    Difference is, C++ actually has more than single digit mind/marketshare outside the safety of the HN eggcup.

    • TheOtherHobbes 6 years ago

      Market share, maybe.

      Mindshare - Lisp has its zealots. C++ is tolerated rather than adored, because it's more of a Katamari Damacy of stray CS than a language with a coherent focus.

      How many other languages have a Turing complete sublanguage built into them just to handle templating?

      • nineteen999 6 years ago

        > How many other languages have a Turing complete sublanguage built into them just to handle templating?

        On the bright side, C++ doesn't have obscure keywords like "cdr" and "car" that refer to specific hardware elements of an obsolete computer built in 1954.

        • praptak 6 years ago

          Car and cdr are a shallow critique of Lisp, the equivalent of "omg, significant whitespace" critique of Python.

          • kazinator 6 years ago

            Significant whitespace is more of an issue than what two functions should be called.

            Significant whitespace means that we can't reliably use a traditional whitespace-insensitive diff to to compare changes in Python code that seriously change its meaning, such as change how many statements are in the scope of an if.

          • nineteen999 6 years ago

            I was aiming for funny and accurate, not deep. Sad, for some, I guess that its heyday is long past, and that it will never ever rise to compete even with C/C++ commercially again, no matter how hard some people kick the dinosaur corpse.

        • msla 6 years ago

          No, C++ has obscure keywords like << which mean vastly different things, and can change, based on some invisible context.

        • ridiculous_fish 6 years ago

          Nope, only trap representations, in case you are using an Itanium for some reason.

          • nineteen999 6 years ago

            Trap representations are an abstraction though, even if IA64 is one of the very platforms where they are used. CAR and CDR are literally named after CPU registers of the IBM 704.

      • hannofcart 6 years ago

        "...because it's more of a Katamari Damacy of stray CS than a language with a coherent focus"

        This is the funniest and most accurate description of C++ that I have ever heard. +100 for the game reference.

      • WalterBright 6 years ago

        > How many other languages have a Turing complete sublanguage built into them just to handle templating?

        D does. It's just that nobody uses it for that, because CTFE (Compile Time Function Execution) is so much better.

        https://dlang.org/spec/function.html#interpretation

      • adev_ 6 years ago

        > Mindshare - Lisp has its zealots. C++ is tolerated rather than adored

        “There are only two kinds of languages: the ones people complain about and the ones nobody uses.” ― Bjarne Stroustrup

        A language does not need to be adored, it just need to do the job and have users.

        Every language has its quirks, if they are not obvious enough, it just means your language is too young for now.

        Languages/Frameworks with their zealots, evangelists and fanatics are generally the ones that will probably disappear in less than 10 years.

        "coherent focus" (often mean dictator driven) never survives the test of time.

      • AnimalMuppet 6 years ago

        I don't know about templating, but... is Haskell's Hindley-Milner type identification system Turing complete? For that matter, Lisp's macros are definitely Turing complete, and they are... a templating system on steroids, maybe?

        About mindshare: Lisp has zealots, but they are few, even if they are loud, and they are on HN more than many other places. C++ is perhaps not loved, but in some circles it is very well respected, the way a chef respects a very sharp knife. The value lies precisely in the sharpness.

        • gpderetta 6 years ago

          > is Haskell's Hindley-Milner type identification system Turing complete?

          I'm out of my depth, but I'm pretty sure HM is not Turing complete by design as otherwise it wouldn't be able to guarantee that inference completes. An HM type system is about as powerful you can get without being Turing complete.

          There are extensions to the type systems of languages that use HM making them Turing complete, but when using those extensions inference is not guaranteed or doesn't work at all.

        • Munksgaard 6 years ago

          The beauty of Lisp's macros are that they're specifically _not_ expressed in a separate language: They're ordinary Lisp expressions that work directly on the AST.

          • lispm 6 years ago

            Even better, they don't need an AST - they work with arbitrary s-expressions and thus in many cases simple list processing operations can be used for code transformations.

      • kccqzy 6 years ago

        > C++ is tolerated rather than adored

        For lower-level programming, while my favorite is Rust, if I cannot use Rust for some reason (say the team against it) I'd use C++.

        To be sure, what I mean is I would use my favorite subset of C++, and I adore that subset. The parts of C++ outside of my chosen subset are merely tolerated.

      • tomnj 6 years ago

        I haven’t understand the details, but supposedly Java generics are also Turing complete: https://arxiv.org/pdf/1605.05274.pdf

  • badcede 6 years ago

    Lisp gets there by being simple and having the optimal notation for it.

cryptonector 6 years ago

  > But in python, it works perfectly (even for
  > user-defined types). How? Simple. Python's parser
  > has a little hack in it - which I'm sure must hurt
  > the python people a lot, so much do they hate
  > hacks - that makes m[5]= parse differently than
  > just plain m[5].
  > 
  > The python parser converts o[x]=y directly into
  > o.setitem(x,y).
The name for this is "generalized variables", at least in Lisp land. The idea is to allow complex assignment left-hand side (LHS) expressions and turn assignments into calls to setter functions, including whatever complex data structure traversals might be needed.

Lisp has generalized variables via "setf macros", which turn assignments into the right set of calls to setter functions. Setf macros do this at compile time and generically for any getter/setter functions that have been registered with a bit of ceremony.

(Lisp also has destructuring-bind, which lets you write a data structure with variable symbols in it such that the corresponding variables will be bound to the data find in the corresponding places of a real data structure value. The two features, destructuring and generalized variables, are similarly magical.)

jq can do crazy generalized variable assignments like '.a[].b[0] = 1', but this is a run-time thing. (The LHS is evaluated in a context that allows only "path expressions", and in a reduction, while the RHS expression is run in the body of the reduction to update the input value at each path matched by the LHS.)

Icon implements generalized variables by letting procedures return "places" -- references to assignable locations --, so you can assign to the return value of procedures. This may seem quite surprising when you see it, but it works beautifully.

_wldu 6 years ago

"About the same time we were starting to work on Go, I read, or tried to read, the C++0x proposed standard and that was convincing to me." -- Ken Thompson

Source -- https://www.youtube.com/watch?v=sln-gJaURzk

pizlonator 6 years ago

I love programming in C++ and I love this rant.

If you ever design a language you should hope that it gets popular enough that people bitch about it this hard. That’s a real triumph.

sagarm 6 years ago

I 100% agree with the title, but some of the complaints in this article have been addressed since the article was published by C++11, C++14, or C++17. Others are just weird.

> If you've heard anything about C++, you've probably heard that there's no standard string class and everybody rolls their own.

Is this still true? std::string seems perfectly reasonable now, especially now that they've given up on supporting ropes and integrated the small string optimization. Yes it doesn't specify an encoding.

> No garbage collection? Check. No refcounting? Check. Need to allocate/free heap space just to pass a string constant to a function?

Nothing else in the standard library is garbage collected or refcounted by default. Why would std::string be the lone exception? You can opt into refcounting for any type using std::shared_ptr.

The objection about allocating/freeing heap space is about APIs that take const std::string& being passed C-string literals. Legitimate complaint, but it's addressed by std::string_view now.

> ...rant about lack of []= operator...

[] mutating the map does surprise people, so that's definitely a legitimate complaint. And it is annoying to have to use find() in a const context...

but simple operations like counting are simpler and more efficient in C++ than in Python because the +=, -=, etc operators work:

  for value in list:
    counts[value] = counts.get(value, 0) + 1
vs

  for (auto& value : list)
    counts[value]++;
> So actually C++ maps are as fast as python maps, assuming your compiler writers are amazingly great, and a) implement the (optional) return-value optimization; b) inline the right stuff; and c) don't screw up their overcomplicated optimizer so that it makes your code randomly not work in other places.

This is just comical. Python is not playing in the same performance league as C++, regardless of whether inlining or RVO happens. RVO of course is a general optimization for returning objects by value from functions, not a special case to optimize setting map items. It's still relevant, but less important since C++11's move semantics.

sarabande 6 years ago

Replace the two double negatives and you get a better title:

"You can't make C++ beautiful, but you must try."

____Sash---701_ 6 years ago

You mentioned that there's no standard string class for c++, and that python is your goto, would recommend looking at Dart Lang, extension methods have recently been added, it also compiles to native exec binaries. https://dart.dev/

asdfasgasdgasdg 6 years ago

That C code wouldn't work if the strings in the map are mutable (which they are in C++). It wouldn't work if the strings were allocated dynamically either. Once you write all the code required to get it working that way, the C starts comparing less favorably to C++.

  • fctorial 6 years ago

    > That C code wouldn't work if the strings in the map are mutable (which they are in C++). It wouldn't work if the strings were allocated dynamically either

    I don't see why not? You'll have to decide the ownership semantics if they're dynamically allocated but that's it?

trewr234234 6 years ago

The biggest issue with C++ for me is the confusing memory model. You have smart_pointers, you have move semantics, and then you have libraries like OpenCV doing their own refcounting (and also using std::shared_ptr). The C++11 features like lambdas are definitely welcome but at this point C++ epistemic footprint is just too large.

diegoperini 6 years ago

Can a native English speaker kindly explain the title? With that many negative tenses, it's really hard to parse the motivation of the author.

  • jimktrains2 6 years ago

    It's in the vein of "even if you can't do something, you should to try anyway"

    Not ugly is more of a less strong "pretty" and can't not is more of a "should" (rather than "can"), so the title can be read as "you can't make c++ pretty, but you should try (to make it pretty)".

dana321 6 years ago

C++ is mostly an abstraction layer over C.

I would rather use C++ because i would have the option to use abstractions like std::map, std::string, std::vector, std::any etc. as they would save a lot of time and code complexity.

IMHO the worst thing about using C/C++ is getting other libraries to play with your project well even if you are using cmake or vcpkg, its not enough. You have to have solid knowledge of each operating system class to get everything working nicely over the long-term.

  • saagarjha 6 years ago

    > C++ is mostly an abstraction layer over C.

    That depends on how you write it. It’s possible to write C++ that bears no semblance to C whatsoever. (I have done so for effect; I once assigned some students to write a C program but wanted to provide a reference implementation with source so I gave them one in C++ that wouldn’t translate directly at all.)

    • dana321 6 years ago

      Internally, i mean, thats how it was originally built and it generates code much in the way that C would, if you would have manually coded it. Maybe not jump on my head without thinking first!

      • saagarjha 6 years ago

        Not trying to jump on your head; I apologize if you got that impression. But while C++ was originally C preprocessor/transpiler, modern C++ has diverged quite a bit from from this. I mean, take a look at this and tell me how it would be possible to convert it to C in a straightforwards way: https://github.com/regular-vm/libencoding/blob/master/encodi...

        • dana321 6 years ago

          Internally, that's what C++ is doing..Converting the abstractions into code. There is a lot of complexity hidden from view (you might see some of it when you get some esoteric template bug in your ide)

          Btw, that code is using std::unordered_map which is notoriously slow compared to an ordinary std::map for a small amount of elements. And the unordered map of register names each with a pair, why? Why not use std::vector for fast lookup?

          The crux of what i'm saying isn't anything new, Bjarne Stroustrup admits that C++ is mainly a high-level abstraction language. There is some new things, but really, the things that are new like << etc. are operator overloads which are just more abstractions.

        • dfox 6 years ago

          For me the main issue with C++ is the idea of no overhead for features you don't use which is not bad idea, but is complete nonsense when the measure of “overhead” used by the language authors is some combination of how the hypothetical C code generated by hypothetical cfront behaves and how the resulting code would run on early 80's minicomputer...

rkv 6 years ago

> No support for null strings? Check

Why would you ever want this? Gives me Java nightmares.

Quekid5 6 years ago

I certainly agree that Modern C++ is vastly bett... actually this is just someone posting an old polemic and trying to stir resentment/controversy. IGNORE THIS CRITICSM OF C++... there are much better and more relevant ones which are worth taking seriously. This is not worth taking seriously.

(I wonder how many of these posters are real or fake. Given the karma and post of this poster I'm inclined to... wait. Last comment was in 2017. Yet... hasn't too many posts since that, I think it's about 4. Probably a bit out of the loop, buy maybe not malicious.)

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection