- No exceptions. Instead they had util::Status (open sourced as absl::Status [1]) and a union type util::StatusOr<T>. I don't believe there's a C++ equivalent for this yet. There's a draft proposal for std::expected [2] but it didn't make it into C++20. I'm not sure what the future is. After many years I truly believe exceptions are a false economy for error-handling;
2. Teams can't write their own templates. This is for good reason. There's almost certainly no good reason to do this and you'll probably screw it up what with correctly handling rvalue references (eg perfect forwarding [3]) and the like.
3. No mutable reference parameters. These are really an anti-pattern and can give the caller no clue that something is modified. Worse, if you allow them you'll find people forget to const their references so you don't actually know if they're modified or not. Even worse, the compiler doesn't know either so it limits optimization opportunities.
It wasn't perfect. For anyone in the know I'll just say "int/size_t" and leave it at that, as one example.
Here are some practical problems:
1. The language is incredibly complex. This makes tooling more difficult. It also means any large C++ shop will out of necessity limit the features allowed such that every shop using a different C++ dialect;
2. As much as we now (since C++11) have smart pointers, you'll still find yourself using raw pointers. You then get into the issues that arise from this, primarily using pointers after their lifetimes and documenting who owns what the pointer points to;
3. The focus on threading primitives. I am now firmly in the camp that if you ever manually create a thread, you've screwed up. The potential pitfalls and errors from doing so are just massive. Most of the time you don't need it either. You should be using higher concurrency primitives or, in a lot of cases, use a cooperative async-await model as you rarely ever need to write truly multithreaded code;
4. (This applies to C too) The fact that types are platform dependent mean you need to use translation layers. So the standards only say a short isn't longer than an int and a long isn't shorter than an int and that's it.
5. Even initializing a variable is complicated [4];
6. std::string vs char * interfaces. Fun fact: there was a time in Chrome when every keypress on the Omnibar resulted in >25,000 string copies.
7. The ability to use uninitialized memory.
8. Stack vs heap allocation is obviously useful but the complexity cost is just massive. Just look at lambda capture [5].
7. The learning curve for C++ is just huge and that entails a lot of useless knowledge (eg explicit constructors, default constructors, copy constructors, move constructors, copy constructors vs assignment operators).
Almost all the time you don't write code for performance. You write it to work and for the next guy to know what the hell is going on and it's this last one where I think C++ falls so short.
I've known too many self-proclaimed C++ experts who revel in the complexity of C++ and consider it a virtue. It's like it allows them to show everyone how smart they are by using some esoteric feature or relying on some non-obvious default (eg a bit like the Javascript semi-colon ASI egoists).
I just never want to touch it again.
[1]: https://abseil.io/docs/cpp/guides/status
[2]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p032...
[3]: https://eli.thegreenplace.net/2014/perfect-forwarding-and-un...
[4]: https://en.cppreference.com/w/cpp/language/initialization