I'm sorry, but from the point of view of technical merit these opinion based rants are starting to feel really disingenious.
Scott Meyers has written lots of good stuff on how to use C++ properly.
Yes, C++ is the rocket-octopus in a razor armour, equipped with automatically foot targeting shotguns and all the rope to hang every developer twice.
Yes it the best tool everywhere? No. Is it my favorite language? No. Is it absolutely essential for value producing programmers around the globe? Yes.
The thing is, the rocket-octopus infestation is so wide that we've learned to use it to our advantage (after a few missing foots but hey - rockets!) and understood how to deal with it's kinks and warts. We've got pretty good tools to wrangle it (Visual Studio is actually pretty good, IMO), and boy, can it go places.
The ecosystem is the main reason it's as alive today as it is. I would much rather take my iridescent paradise bird F# to work, but the sad thing is, the poor thing does not survive yet everywhere where the rocket octopus does. My pet python is much more well behaved but unfortunately it still does not have hassle free rockets nor can it survive in high performance scenarios with even more wrangling than the octopus does.
You know what? I'm going to take the controversial opinion that C++ is both the best tool for everywhere and my favorite language.
C++ really has only one downside. It's hard to learn. Almost all complaints about it really boil down to not knowing the language well enough.
But so what? Because once you know it well...
Once you know C++ really well, you understand what it means to have no compromise.
It has the best performance and the highest level abstractions in one package.
It has both amazing safeguards against programmer error and the ability to break all of them if you need to.
It has great portability while still giving you full access to all he platform specific features.
You can feel just as at home writing a web browser or a website in C++.
I could go on.
You get all this good stuff and really it only really takes a few years of seriously using the language to get that good. What is a few years in a 40 year career?
Also, don't believe the bullshit of "nobody really knows C++" that a lot of people claim. You can find dark corners of any language. The reason they are dark corners is because they don't come up in real code.
Hm, no, it sucks. "I can do that with ease because I've done it so many times" is really a terrible argument to explain why something is good. If you've lost a leg, walking with a crutch becomes second nature to you, but that doesn't mean that's the preferred or most efficient way of walking for the human being.
Yes, you know by heart that all_of requires "foo.begin()" and "foo.end()" and the capture context "[]", but it doesn't negate the fact that many other, better languages, just know how to cycle an array without having to specify begin and end, know how to capture arguments when needed, don't require a useless (in this context) return, nor clutter a single line of code with nine differenct gibberish symbols like "(", ")", "::", ",", "[]", ";" "{", "}".
Take the ultra humble javascript: foo.every(a => a % 2)
When you need three times the characters to obtain the same result, and those charcters are a clusterfuck, it is not relevant if years of usage made you comfortable nonetheless with it.
Not to take away from your other criticisms, but in most languages how the arguments are captured has no relevance (in Javascript — and most every other managed language — the environment is always captured by reference and the GC handles lifetimes of "whatever"), whereas it is very relevant and important for C++. So I think that part of the criticism is unwarranted.
Though Rust didn't go to the same lengths[0] you still have to specify whether the environment is captured by reference (the default) or by value (`move` lambdas), because that can have a large and semantic impact on the system.
> C++ really has only one downside. It's hard to learn. Almost all complaints about it really boil down to not knowing the language well enough.
Did you read the link and the examples shown (std::chrono one, for example)
Yeah, in the time I go around trying to solve C++ self-inflicted tangles I can learn Go or code stuff in Python (or even Java/C#) which 99% of the cases is fast enough
I used to advocate for complex stuff, but now I think differently. Solve my problem, I don't care about religious issues.
I don't think the std::chrono example is fair at all.
In the C++ case, you specify which clock you want. What kind of clock is DateTime.UtcNow()? Is it monotonic? What's the resolution? It's maybe in the documentation, but if the author refuses to remember simple syntax, surely he won't want to remember such details either.
The author also chose an example where he abuses the default behavior in the C# case. The last line in the C++ example just vanishes if you use the default behavior in both cases.
Also std chrono is very strict with types: time points and durations can't be freely mixed, milliseconds and microseconds have different types [1] and you can't implicitly convert from a float based time to an integer based time.
Does C# do the same thing?
[1] the underlying type actually encodes the exponent as a non-type template parameter. Converting from ms to us correctly applies the multiplicative factor.
To some extent. Points in time (DateTime) and durations (TimeSpan) cannot be mixed either, although you can subtract two DateTimes and get a TimeSpan, or add a TimeSpan to a DateTime. But I guess those work in C++ just as well.
Internally they are all based on ticks, which are 100-ns intervals (in DateTime since 0001-01-01) stored as a signed 64-bit integer. Different quantities of time don't need different types because there's only one type that represents a duration.
But with any complex interface, wouldn't it be a good idea to introduce a less complex facade that only deals with common usecases, and for complex use cases use that underlying interface?
The whole point of the complexity chrono interface is to prevent mistakes caused by accidentally mixing different units. Providing a default unsafe interface would completely defeat the point.
Sorry, I might misparsing your replay, but are you saying that std::chrono is overly strict? Or that because C++ (and its C heritage) has allowed unsafe implicit conversions in the past, it should continue doing it in the future?
"I can learn Go or code stuff in Python (or even Java/C#) which 99% of the cases is fast enough
I used to advocate for complex stuff, but now I think differently. Solve my problem, I don't care about religious issues."
This sounds like it's written in the hobbyist/lone tinkerer/ deploying to self managed server context.
When discussing could anything replace C++ one is usually in the context of a massive business environment pending on millions of lines of C++ and native bindings, not figuring out how to write a handy personal tool.
For handy personal tools, Python is probably much better than C++ 99% of time, but when discussing, how to replace a language in an entrenched ecological slot with a huge number of interdependencies things are really not that simple at all.
> C++ really has only one downside. It's hard to learn
I don't think the article lists just one downside. For example It's also undeniably hard to parse, making it harder than necessary impossible to implement efficient refactoring tools and other parser dependent tools, for example.
> It has both amazing safeguards against programmer error and the ability to break all of them if you need to.
C++ has two main design constraints -- it comes from C and its abstractions should cost nothing. Those two have enormous benefits and drawbacks. Performance is one thing. Other languages that don't have those design goals/constraints have different benefits and drawbacks. It's easier to make a small shell util or in perl/python than in C++. It's easier to make a formally verified system in a functional lang than in C++ and so on. "The best tool for everywhere" meaning "if you need to use the same language to solve all N problems then C++ is best of the major and widely used languages" might be true, but it's also a pretty poor metric.
> For example It's also undeniably hard to parse, making it harder than necessary impossible to implement efficient refactoring tools and other parser dependent tools, for example.
I can think of several areas where Java support for tooling is unobviously superior when compared to C++.
The SDK for the Arduino is good example, so is the Unreal game engine, the tooling support in Xcode for iPhone Apps, the support for static analysis via Clang, the auto formatting support via clang-format, the support in Windows eith Visual Studio, the support for autocompletion in UNIX text editors such as Vim and Emacs with the YouCompleteMe daemon and client (which leverages clang).
So if Java is an example of superior tooling, it's not really an "obvious" one
I completely agree. I very much enjoy working in it, even after 2 years of exclusively Clojure development, I had no problems coming back to modern C++ with all its lovely functional tools and enjoy every moment of it.
> The reason they are dark corners is because they don't come up in real code.
Thank you!
It's amazing how much FUD is spread using examples that would never pass code review! (or even be submitted to code review unless you were playing a prank on your coworkers).
> It has the best performance and the highest level abstractions in one package.
The highest level abstractions, are you kidding me?
I am currently writing a little interpreter in C++. I would have used Ocaml, but the rest of the project is in C++. Well, just try to write an abstract syntax tree in C++. In languages such as ML and Haskell, this is easy:
type expression = Litteral of value
| Symbol of string
| Funcall of expression * expression
| Conditional of expression * expression * expression
| While_loop of expression * expression
| Sequence of expression list
And I'm pretty much done. Now in C++:
--------------------------------------------------------------------------------
#ifndef __Expression_h__
#define __Expression_h__
#include <cstdint>
#include <string>
#include "Value.h"
namespace Script {
class Expression;
class Expression {
public:
enum Tag {
litteral,
symbol,
funcall,
conditional,
while_loop,
sequence
};
union Val {
Value *litteral;
std::string *symbol; // or a true symbol?
std::vector<Expression> *composite;
};
Expression(const Value&);
Expression(const std::string&);
Expression(Tag, std::vector<Expression>*); // takes ownership
~Expression();
private:
Tag _tag;
Val _val;
};
} // namespace Script
#endif // __Expression_h__
--------------------------------------------------------------------------------
Don't forget the cpp file either:
--------------------------------------------------------------------------------
#include "Expression.h"
#include <cstdio>
using namespace Script;
Expression::Expression(const Value& val)
: _tag(litteral)
{
_val.litteral = new Value(val);
}
Expression::Expression(const std::string& sym)
: _tag(symbol)
{
_val.symbol = new std::string(sym);
}
Expression::Expression(Tag tag, std::vector<Expression>* exprs)
{
if ((tag == funcall && exprs->size() == 2) ||
(tag == conditional && exprs->size() == 3) ||
(tag == while_loop && exprs->size() == 2) ||
tag == sequence) {
_tag = tag;
_val.composite = exprs;
} else {
std::fprintf(stderr, "Expression constructor: Invalid Expression\n");
exit(1);
}
}
Expression::~Expression()
{
switch (_tag) {
case litteral :
delete _val.litteral;
break;
case symbol :
delete _val.symbol;
break;
case funcall :
case conditional:
case while_loop :
case sequence :
delete _val.composite;
break;
default:
std::fprintf(stderr, "Expression destructor: broken class invariant\n");
exit(1);
}
}
--------------------------------------------------------------------------------
I may use smart pointers instead of doing my own RAII by hand, but that's still a hassle. Some may point out that my ugly unsafe union type is not the way to to this, I should use a class hierarchy and polymorphism. But just imagine the sheer amount of code I'd have to write.
C++ is a low level language. It's high level pretense is nothing but a thin veneer that cracks under the slightest scratch. Or, someone show me how to implement an abstract syntax tree in less than 20 lines of code. I'm lenient: Haskell and ML only need 6.
(Yes, I'm using Sum types, a feature that's pretty much unique to ML languages. But no, that's not cheating: providing enumeration and unions but somehow failing to combine them into sum types is a serious oversight.)
I think you're deliberately making it more verbose than it needs to be. The class definition could be condensed into:
class Expression {
enum Tag { litteral, symbol, funcall, conditional,
while_loop, sequence } _tag;
union {
Value *litteral;
std::string *symbol; // or a true symbol?
std::vector<Expression> *composite;
} _val;
};
And you could probably use templates or macros to autogenerate most of those constructors/destructors automatically.
> I think you're deliberately making it more verbose than it needs to be.
I swear I am not. This is will be production code, for which I earn my salary. Your code is terser, but still fundamentally the same as mine. It's still unsafe (we could access the wrong union member), and it still has to work around incomplete definitions and the lack of trivial constructors (I wouldn't use pointers otherwise).
Automating my constructors and destructors would be amazing. Unfortunately, I don't know how to do it —not without writing over a hundred lines of cryptic boilerplate first.
The ML example was missing the second half of the ML niceness - efficient pattern matching at syntax levels which makes for a really nice and understandable code, especially when writing complex compound clauses.
"...could probably use templates or macros to autogenerate.."
... which kind of highlights that ML example even more. In Ocaml or F# that original definition would have been more or less enough to start writing nice, efficient parsing code.
The sum types are not the only thing, IMO, that ML-family has going for it. As for example F# contains pairs and lists as syntax level constructs (rather than library add-ons) lot of code can be written at a really high level before optimization - in the same language - if it's needed.
With Boost Spirit you can write expressions akin to EBNF in your C++ code. The downsides are compile times (improved in the new version) and difficult to parse error messages at compile time.
If you stick to your code you should at least:
* add a copy constructor or delete it explicitly
* pass in the vector as unique_ptr to the constructor. This way the "takes ownership" will be clear without the need of a comment.
* add your missing includes e.g. vector
* stop using _ as variable prefix, it's forbidden
* throw from the constructor, don't exit
Boost variant + boost vector will allow you to eliminate all the manual memory management (boost vector allows incomplete types).
Thanks for the tips, I'll apply them. Just a couple questions:
* Do you have a link about the _ (underscore) being forbidden? Our current coding rule make us use them right now.
* Is there a standard exception object I could throw? If I could just write something like `throw std::invalid_argument("what String");` that would be terrific. I don't want to construct a whole class just for that. (Maybe I have to, though?)
I'll also seriously consider boost. While it's a huge dependency, we are on Debian GNU/Linux, so it's probably not that bad.
You're welcome. The underscore thing is mentioned in 17.6.4.3.2 Global names, however it's more complex than what I said initially (this is C++ after all):
* double underscore or underscore + capital letter is reserved in any scope
* underscore is reserved in global scope
Your code is allowed in fact, sorry. My personal rule is that I never start an identifier with an underscore anywhere.
I missed the exc question: yes, invalid_argument is perfectly acceptable in my opinion. However, if you want to inherit from invalid_arg that is also possible - this would allow you to catch only this very specific type of error.
c++17 might include a variant template which would replace all that (or at least the backing storage for it). (std::variant<Value, std::string, std::vector<Expression *>>)
So the C++ way is either to write a code generator for your own DSL, or to write a library that would allow to write you grammar in an eDSL fashion. No pain, no gain ;)
Good point. But we should let them age a bit. Right now they're a bit young for production code with deadlines and all. I hate C++, but I like the certainty that comes with tools I know.
My code compiles as is, so you can try yourself if you want.
If I didn't use pointers, I would have 2 errors. One would be about the use of an incomplete data type (Expression), and the other about trying to put something that doesn't have a "trivial constructor" in a union (String, Value, and std::vector<Expression>).
I can't use references either, because I would have to initialise them in the initialisation list of my constructor, which I can't do until I know the tag. I could still initialise the tag first, but that's specified by the order in which the data members are defined, rather than the order of the initialisation list —a rather dangerous idiom to rely on, should anyone modify the code.
Note that in C++11 is perfectly fine to put a type with a non trivial constructor in an union.
You are right about the issue with std::vector and incomplete types. It will be possibly resolved in the next standard. In the meantime you can use something like boost::container::vector which explicitly support incomplete types.
It's extra work, but you could use std::aligned_union<1, std::string, std::vector<Expression>, ...> to declare space for the union and then use placement new/manual destructor calls to initialize and destroy.
well, you would, you're in games. fairly substantial amount of stockholm syndrome in game programmers, imo. ;-)
bjarne and others in the community have talked about the heavy cost of legacy and compatibility many times. it's okay to like the language but acknowledge that it's not as awesome as it theoretically could be. it's okay to be unhappy with the warts and weirdness -- after all, such things are what the committee needs to be mindful of when progressing the language.
there's no real reason a language can't outright replace c++ without having many (most? all?) of it's idiosyncrasies. rust is a decent attempt, though the memory safety ideology seems too high friction to properly replace c++. there might be others who give it a shot someday.
Opinion-based rants? No, ignorance-based rants, with a level of malevolence that doesn't appear in good faith.
From claiming that "if you make all header libraries you’ll have a lot of copied code – the binaries will be larger", to blaming IDEs for the opaqueness of Cmake and Autotools, from deploring namespaces because "the programmers will end up doing a using namespace" to comparing a trivial std::chrono::duration_cast code example from cppreference.com to a C# example that does much less, it's one of the worst pieces of FUD I've ever read.
The only valid point is C++ IDEs not being too good, but even that has deeper reasons than Visual Studio rightfully disliking incompatibly compiled libraries or "it sucks as an IDE".
For example, right now is the only native language available in all mobile SDKs.
So unless one wants to add extra layers to their tools for writing portable native code, with the consequent increase in development costs, C++ is the only game in town.
For example, Xamarin is great, but now on top of debugging Java, Swift, Objective-C, C#, one also needs to debug their glue code into the platform APIs.
I just picked Xamarin as an example of extra layers, I think they are great.
> Is it absolutely essential for value producing programmers
> around the globe? Yes.
This is dubious. Care to elaborate what you mean by that?
There is, of course, huge amount of effort needed to support existing critical software written in C++, but there's never (I'm using "never" rather than "rarely" very deliberately here) a need to start a new project in C++. Almost any alternative option is better.
This is a naive comment. C++ is a very good option for a wide range of scenarios where trying to shoehorn some other trendy language in its place would be at least the same level of headaches to just learning proper C++ development. And you can also depend on C++ stability and future availability, which also makes choosing its latest high-level constructs a wise option over other new languages that compete to also offer new idioms to the programmer. C++ isn't perfect, but neither is any other language. If you really know C++, it is a logical choice for many new projects across many platforms and environments.
Well, please tell me what language will be good for developing a specialized image processing system. Considering all good libraries are all C++ and maybe have Python bindings. deterministic destruction + templates + low-level memory management + the ability to ascend to full java-like OOP with interfaces really do make it my favorite language.
Yes it has weird syntax and ugly default behavior, but that's a price for backward compatibility.
"This is dubious. Care to elaborate what you mean by that?"
I meant mainly the existing status quo as you elaborated in the following sentence.
"there's never ... a need to start a new project in C++. Almost any alternative option is better."
I agree that for a lot of things C++ is the wrong language. For some use cases, such as libraries supporting real time graphics on multiple platforms, I'm not yet sure if yet there are better options (well, there is C but I'm pretty sure that arguing which is better of those is a matter of taste and experience).
"I expect the companies that are starting new C++ projects today to be put out of business by competitors using better languages."
I don't think that's how software business works. I'm pretty confident there are niches out there where even COBOL would be a just fine implementation language if it provided end value for the user.
Domain understanding, network effects and marketing seem to trump bleeding edge most of the time.
That's not to say there are no situations where selecting a better language would lead to far better outcomes if there is time pressure and the language is viable choice with the technical constraints of the project. I'm just highly skeptical that this is a rule that works in general.
For an extreme example where this rule starts to look dubious, embedded systems - are there any better languages even available?
The problem with all of these, with the exception of Rust and Ada, is that it's hard to do things like, get at pointers directly, for example. For all of the abstractions OCaml and Java bring, there is an often large performance penalty to pay, and when you're pass producing an embedded product, that's money you're leaving on the table because you're asking for more compute power than you actually need to get the job done. Ada is usually a pretty good choice, but it's hard to find developers outside of the defense industry; Rust on the other hand is often view (perhaps rightfully so) as still experimental and unproven.
> For all of the abstractions OCaml and Java bring, there is an often large performance penalty to pay, and when you're pass producing an embedded product, that's money you're leaving on the table because you're asking for more compute power than you actually need to get the job done.
In my experience the performance differences are often grossly overestimated (particularly if you compare equivalent development times). And increased development time or higher defect rates aren't free. What you say is true for a particular niche, but I think that niche is pretty narrow (and in that niche I think the risks of Rust, while real, are smaller than the risks of C++).
Alternative explanation: things are done a certain way due to technical constraints and market forces which you don't know about or choose to deliberately ignore.
At least in the storage space (which gets essentially no press on HN), I have come to the finding that the vast majority of core product codebases are C/C++. For the niche they fill, it seems really unlikely that even the still experimental Rust would replace them here.
That post is less "I'm getting away from C++ because it's a poor language" and more "I'm retiring from my role as C++ advocate because there are plenty of quality resources available".
Scott Meyers has written lots of good stuff on how to use C++ properly.
Yes, C++ is the rocket-octopus in a razor armour, equipped with automatically foot targeting shotguns and all the rope to hang every developer twice.
Yes it the best tool everywhere? No. Is it my favorite language? No. Is it absolutely essential for value producing programmers around the globe? Yes.
The thing is, the rocket-octopus infestation is so wide that we've learned to use it to our advantage (after a few missing foots but hey - rockets!) and understood how to deal with it's kinks and warts. We've got pretty good tools to wrangle it (Visual Studio is actually pretty good, IMO), and boy, can it go places.
The ecosystem is the main reason it's as alive today as it is. I would much rather take my iridescent paradise bird F# to work, but the sad thing is, the poor thing does not survive yet everywhere where the rocket octopus does. My pet python is much more well behaved but unfortunately it still does not have hassle free rockets nor can it survive in high performance scenarios with even more wrangling than the octopus does.