← Back to context

Comment by arcticbull

7 years ago

The issue isn't that it gets new features it's that they're half-assed because you can't impose new restrictions on old code. The power of these features in modern languages comes from their ability to protect you from yourself. That has more to do with what you can't do than what you can. Bolting more legs onto C++ doesn't protect you from anything, it just increases the surface area of the language.

They haven't introduced the features other language have, they've introduced poor rip-offs that fail to work as one might expect having worked in any of the other languages they're derived from.

The problem is it's not different than it used to be, the cracked foundations are exactly the same as they've always been always been but there's a bigger and more complicated house on them. C++ is starting to feel like the Winchester mystery house.

The C++ philosophy is not about protecting you from yourself. Its about allowing you to express your idea in as low or high level terms as you require. This lets you write a highly optimized tight loop and then abstractly describe less performance sensitive parts.

In the past it was thought that people would use safer high level languages and then drop down to C for performance. That vision just doesn’t seem to work out in practice - except at the cross process level.

The trade off to this is language complexity. If you want a simple language C++ isn’t for you - and that’s OK.

  • C++ for me was and with every newer standard is really a meta language. It is used by many big projects to build their own "language". Such a language is almost completely ad-hoc and does not enjoy compiler checks, because it lives in project guidelines. It may enjoy some checks thanks to whole template machinery. But error messages barf about templates not a real intention behind them.

    Every big project is almost entirely different. They use different sets of features - some overlap more, some less. Many developers like to use C libraries, because they are easy to wrap in their version of C++. When you shop for libraries you often have to think if their version of C++ will work with yours. There is some consensus around STL and Boost, so at least that is relatively straightforward.

    • There seems to be a more modern trend of wanting all code to look the same. I've worked in large projects that have existed over many eras (including as far back as K&R C). The "Refactor when it becomes a problem" seems to work amazingly well. Global refactors and project wide styles seem to always fail miserably - inevitably a new era comes before the previous standardization effort completes.

      E.g. in the C->C++ transition most malloc's were left alone. If you wanted to add a ctor/dtor then you would go refactor them as necessary. It also encouraged you to encapsulate your work moreso than you would have otherwise.

      4 replies →

  • > That vision just doesn’t seem to work out in practice

    Well, about half of the Python ecosystem seems to disagree.

    • Python is likely the best example of this working, however even in Python the boundaries between high and low performance sections are very formal. It's a lot simpler to go in and optimize a piece of code the profiler has pointed out with C++.

      I really don't understand the animosity non-C++ developers have towards the language. You can still use Python, Rust, etc if you want. Nobody wants to force you to use C++.

      30 replies →

> they've introduced poor rip-offs that fail to work

This sounds like hyperbole to me. I've worked in other functional languages professionally for many years, but when I write C++ with consistent use of std::function and lambdas, I get many of the same benefits and a very enjoyable workflow. Is it the same as Haskell or Clojure? No, because they are totally different languages. But within the C++ world, they offer a great productivity benefit that I don't think satisifies the definition of "rip off".

  • Not all of them, to be sure, lambdas and in particular their capture list syntax is solid. By no means is that universal though, for instance, move semantics.

    std::move doesn't move anything, it casts an object as a movable reference (equivalent to static_cast<T&&>). The compiler doesn't preclude you from accessing the old object (or say anything at all) and there's no guarantee it actually did get moved, it's just a hint. That's not real move semantics, it's a shoddy knock-off.

    You also have to manage a new constructor, new reference type and the opt-in because the default remains not using move semantics. Worse yet, there's varying agreement on what you can or should do with the source of a moved value, the STL containers will all continue to let you use the old value, it's just empty now. That's not standard behavior because there is none, and it's de facto now because it's in the STL. What a nightmare. [1]

    std::variant is supposed to be associated values on enums, but of course, it doesn't do that either. You don't match, you create a new struct with a bunch of operator() methods on it [or overload a lambda?!] and throw it at std::visit. You can only have one variant of each type. Then it throws exceptions if you start mucking about in ways you shouldn't. There's no context. It's dreadful. [2]

    [1] http://yacoder.guru/blog/2015/03/14/cpp-curiosities-std-move...

    [2] https://www.bfilipek.com/2018/06/variant.html

    • I think the way you are thinking about move is all wrong.

      In any kind of correct code, the difference between a move and a copy is only performance. If a copy were to happen where a move was requested then the code is just as correct, so I find it strange to get so hung up on it not being “real”.

      Also, if move is the only available option, and move cant happen, you get a compiler error. If performance is correctness for a type that is expensive to copy, make copy not an option.

      Also, there is a move constructor by default so it’s not opt-in, you opt out only if you start screwing around with copy / assignment / destructors which you usually shouldn’t need in modern code anyway.

      Sure, the state of moving on the moved from object is unspecified, but really, I can’t think of a time when I’ve written code that would care. It’s kind of a non-problem.

      If you really want to reuse an object after a move I question your motives, but you should just reinitilaise it by assigning a freshly constructed value and the result of that is of course standard.

      1 reply →

    • Lambda captures make it pretty easy to create nasty memory safety issues via dangling references. Sure, you could create such issues with the equivalent manually written closure-structs, but lambda captures pave the dangerous path.

      1 reply →

    • I would consider your counter examples rather compelling, thanks for the details. I never particularly enjoyed move semantics, so you have a point there.

  • There is still quite a bit of friction at the seams to the "core" language though. If you write a plain templated function, you have to wrap it in a lambda (or some other functor type, or std::function I guess) before you can do anything with it except calling it. Pattern matching is also quite verbose currently.

    Whether this matters really depends on the code you're trying to write of course.

The fact that you can't make old code use the new features in no way makes the new features "half-assed". It may make the old code that, but not the features.

Or did you want them to deprecate large swathes of the previous standard?

As for the rest of your rant... it's mostly just a rant, with very little substance.