125 comments

[ 5.0 ms ] story [ 201 ms ] thread
I'm so done with C++. When a fundamental keyword becomes a semantic pitfall, you've created an esoteric niche for enthusiasts, not a useful tool.
I think for most of us mere mortals, the only normal use for =delete is making a class non-copyable. And for that, my muscle memory is already wired to inheriting from boost::noncopyable anyway.
I put

private : myclass(const &myclass )=delete;

Just to be safe :-)

Funny thing there is that one could read that as saying that the copy constructor's deletion is private and therefore still accessible in the public interface. I wouldn't put it past C++ to do something as crazy as that.
IIRC this will make the compiler error less helpful than it could be? I think you'd get an "attempt to use private member" instead of "attempt to use deleted member". End result is probably the same, but it feels like you're not saying what you mean with the code.

Edit: NVM, looks like all major compilers will say that the copy ctor is deleted. https://godbolt.org/z/jTd3714Tb

clang-tidy will ask that you put in the full set.

  Foo() = delete;
  Foo(const Foo& orig) = delete;
  Foo(Foo&& orig) = delete;
  Foo operator=(const Foo &other) = delete;
  Foo operator=(Foo &&other) = delete;
(add const to taste)
> I'm so done with C++.

But what to use instead? Especially in an industry like game engine development, where C++ is basically all there is (and almost all there’s ever been)?

Are we forever going to be stuck with every company trying to reduce complexity by defining their own custom subset of C++?

The main alternative is likely rust.
Are you saying that for game engine development, Rust is the second most popular option?
I didn't say "You can't use it," I said "I" am done with it.

Have fun! Write some cool games!

Thanks for the encouragement, it’s appreciated.
Someone is going to say Rust, and I might agree with them at some point in the future.

I periodically check https://www.areweguiyet.com/ to see the state of things, but C++ is still king here.

I cut my teeth on C++ but was then employed to write code in C. After almost two decades of doing this, C++ looks vastly different to what I remember from 2001 and kind of ugly to me. I'm sure, however, if I had stuck with C++ for the last twenty years it would feel like home.

The places where you must use C++ are a dwindling niche. Large niches in their own right, certainly, but niches. Once you leave those niches there is an explosion of possibilities.

I'm coming up on year 25 of my career and I haven't touched C++ since school, if one can indeed call what is covered in school C++. Especially nowadays. And C and Java I've barely touched. I certainly couldn't put them on my resume with a straight face.

I say this because you kind of sound like you're making some sort of plaintive plea, as if C++ is somehow the only viable option in the world and who could even dream of stepping outside of it? And I'm telling you that while that may be true in an ever-decreasing set of niches, it is not true in general anymore and hasn't been for a long time.

in the future, I think Jai looks pretty good, as a language designed for game dev, considering they're basically battle testing it by shipping a large 3d game in it... if the compiler ever gets released. I think 7 years of streamed development from Jonathan Blow?
> Are we forever going to be stuck with every company trying to reduce complexity by defining their own custom subset of C++?

Yes, probably? In 30 years of professional coding and a decade of game engine development, I’ve never seen that not be the case, all the companies I’ve been at and all the companies I know about limit their C++ to a subset to try to control complexity.

I’m curious if you’re suggesting it would be better to increase complexity by allowing all of C++? There are definitely features I don’t trust everyone with.

Complexity combined with engineer hubris is one of the big problems with C++. While working in games, I witnessed many people overengineering and being too clever by half, and costing the team time and money. One of the most memorable bugs I ever tracked down while working in games was a release-build only crash where a programmer had tried to get fancy with a copy constructor and bungled it unknowingly. We had a team of 10 people working over a weekend trying to catch it and I had to write a custom debugger to trap the call stack. The cost of his trickery was easily in the several tens of thousands of dollars at least. It only takes that happening a few times before you realize C++ is a foot-gun in many (most? all?) hands.

> I’m curious if you’re suggesting it would be better to increase complexity by allowing all of C++?

No, definitely not. I’m wondering if there might be a language on the horizon that’s suitable for game engine development but which is substantially less complex than C++.

Or, if not, is there an effort to rally round a particular subset of C++ instead of everyone defining their own? In your experience, have the C++ subsets used in different companies been very similar, or quite different?

> No, definitely not. I’m wondering if there might be a language on the horizon that’s suitable for game engine development but which is substantially less complex than C++. Or, if not, is there an effort to rally round a particular subset of C++ instead of everyone defining their own?

If it doesn't exist, it should be created and called --C.

(C-- is already taken) https://en.wikipedia.org/wiki/C--

> Are we forever going to be stuck with every company trying to reduce complexity by defining their own custom subset of C++?

The problem with doing this is the same as with the idea that one "90% of people only use 10% of Excel's features" so we should be able to make a simpler Excel and capture 90% of the market: all those people use a different 10% subset. It's the same with C++; everyone wants a "simpler" language, but no one agrees on what should be kept and what should be thrown out. C++ is the language that results from putting together everything that everyone needs (and then dealing with the resulting conflicts and contradictions).

What is the semantic pitfall here?
For almost everyone, none. delete doesn’t remove the method from the overload set, it leaves it there in order to give the caller an error saying don’t do that.
It also prevents the compiler from emitting a default implementation for the deleted method.
C++ has a keyword, "delete" which originally refers to an operator that destroys objects, thus calling their destructor and giving back resources (most obviously memory) allocated for their lifetime.

But, adding keywords to C++ is expensive. You can't use these as symbols, you can't name a class, a variable, a function or anything "delete" because that's a keyword and so it's reserved. A new keyword would clobber existing symbols, and that's painful, so, C++ tries not to do it, instead repurposing existing keywords.

So as well as the operator named "delete" now "delete" refers to this feature where you can tell the compiler that this particular overload mustn't be used, if it was looking for an overload, it won't use this because you explicitly said not to (even if it could have otherwise conjured a default) and if somebody else called it by mistake now they get a compile error saying not to.

I don't think they would naturally have written =delete if not for being conscious that doing so is "free" (the word is already reserved for something else) whereas some other word would have caused compatibility problems.

What, you think the syntax is funny? I guess, but that's not a semantic pitfall.
Previously delete referred to an operator

Today delete is both the operator and this entirely unrelated feature, so that alters the meaning, a matter of semantics.

Natural languages have plenty of such ambiguity, but C++ has a Committee (yes I know about French, no the Académie Française doesn't actually get to decide how French works, that's not how natural languages work) which could have chosen to use a different word and did not.

(comment deleted)
A semantic pitfall would be something where you think the code means one thing (a patch of grass) but it really means another (you fall into the spikes). =delete is neither; it is not possible to confuse it for operator delete, and I don't see even a hole to get your foot stuck in, let alone any spikes.
Fair. I wouldn't have chosen the phrase "semantic pitfall", however after some thought I think it would classify as a "hole to get your foot stuck in" because it's unexpected in an artificial language to have this. If you're a learner and you covered "delete" the operator last week, then missed a day, and now there's = delete on the slides, do you raise your hand to ask what's this completely new syntax? No, right, it must somehow be the delete operator you learned about, as otherwise it would have a different name... wouldn't it?

But sure, C++ does have actual hidden spike traps, where unwary programmers are going to hurt themselves badly by mistake and this is not one of those.

> it is not possible to confuse it for operator delete,

I haven't written C++ in many years and my interpretation of the code examples before reading the rest of the article was that it was somehow binding it to the delete operator as a function.

Do you also complain that the operator named '*' is overloaded? As a binary operator, it means to perform a multiplication operator. As an unary operator, it means to load the value located at that memory address, which has nothing to do with anything close to multiplication. That is a more gross alteration of semantics than the use of a keyword in completely different slots (as an expression versus as a declarator).
I'm not a fan of your example with the asterisk or of the reference operator sharing a symbol with the binary AND operator.

I don't like the use of + for concatenation much either, even though it's overloaded to do that in some languages I really like, and it's even special cased despite the lack of overloading in one language I think is fairly good (Java) and some others I've used but think are garbage.

But I'm willing to cut some more slack for symbols because they're short.

I think erase for example would have been a better choice here (I haven't spent long thinking about this, the committee had months), but C++ had to worry about the backwards compatibility penalty and that's... sad.

Actually it would have been a contextual keyword, meaning it could be any word the committee wanted it to be, and it is only recognized as such in this particular context (i.e. at the end of a function declaration). They could have chosen anything they wanted, but decided that '=delete' was fine.

Other examples of contextual keywords are 'final' and 'override', which may also occur on function declarations.

I guess they mean the "syntax-semantic mismatch pitfall"? :)
A pointless semantic argument about the word semantic... Thank you C++!
> A new keyword would clobber existing symbols, and that's painful, so, C++ tries not to do it, instead repurposing existing keywords.

It's even more painful than that. A new keyword could conflict with a macro. So even contextual keywords (something which is a keyword only in a place where arbitrary symbols are not allowed, so there's no conflict) could be problematic.

If a language feature being weird is enough to declare that you're "done with it", you won't have too many languages to work with in the end. Maybe some LISP, I guess?

You don't become a good craftsman by rejecting every tool that isn't perfect. You become one by knowing your tools and their flaws and strengths well.

> You become one by knowing your tools and their flaws and strengths well.

Yes, that's why I said "I" am done with it, not "you should be done with it." There's no need for me to use it ever. Except when forced to with mbed, TFLiteMicro, and the occasional port of an Arduino driver to C.

I've been at this since before C++ existed. I've watched it come in to the world, and I've watched it mutate, Akira-like, into an academic omphaloskepsis.

EDIT: Fun story: I learned C++ in ... 1990? ... by watching a series of videos on VHS tape taught by none other than Bjarne Stroustrup himself. I think it was a bonus that came with the purchase of the first version of the Borland C++ compiler for OS/2.

> I'm so done with C++. When a fundamental keyword becomes a semantic pitfall, you've created an esoteric niche for enthusiasts, not a useful tool.

you think quux wouldn't be able to find semantic pitfalls in literally every language you would use instead ?

Some language designs have more semantic pitfalls than others. All else equals, those would be preferable.

C++ is an old beast with many many footguns.

There's a more modern subset hiding inside, these days, but let's not pretend every language has the same pitfalls that C++ has.

sure, but there's also a deep culture in the C++ blogosphere to go and seek the deepest and darkest corners and exhibit them as it's a very fun puzzle game. the same people applied to Python, Java or LISP would yield endless articles on pitfalls of those
If they are endless where are they?
Search for Java Puzzles books
In the early 90's there were two print magazines: "The C++ Report" and "The Smalltalk Report". The former, even with the far simpler language it was then, was chock full of programming arcana and 'what does this code do?' type puzzles. The latter, by contrast, had effectively zero such content. I loved both languages but this difference was always very stark.
I must say that I'm always a bit in awe when I read modern C++ and I realize that I barely understand what's going on even though ~10 years ago I considered that it was the language I knew best. And even back then it was already a sprawling monster of a language. Nowadays the idea of having to maintain a large C++ codebase scares me. Too many footguns and arcane knowledge required.
Eh? '=delete' is a bad example of "footgun" or "arcane knowledge". There are those in C++, sure, but this isn't one of them.

You can happily never know the existence of '=delete' and nothing about your code changes. And if you ever hit a libraries usage of it, like the standard library, you get a pretty clear error message at compile time instead of an actual footgun in C++98 like memory corruption or runtime crashes.

'=delete' is basically the entire reason why std::unique_ptr is safe and std::auto_ptr is a footgun. It reduces the ammo aimed at yourself & the amount of knowledge you need to know (like the knowledge to never put auto_ptr in a vector)

I was talking generally, not for this particular feature. But it would still be sort of arcane knowledge for me since when I last actively used C++ `=delete` either didn't exist or was not in common use, so if I encountered it in some codebase I'd probably guess what it does but still have no idea about the implications.

Like, are there other =<something> construct? Is it overridable or overloadable by the user? Can I use it to "erase" any member of a class, for instance to hide a parent member from an inherited class?

I suspect that the answer to all of these questions is "no", but I can't know for sure without deep diving into some C++ ref.

In the C++ I knew there was nothing resembling this syntax, and `delete` was only the keyword you used to free memory.

> Like, are there other =<something> construct?

Yes, there's "= 0" to mark a virtual method as not implemented in this class (that is, it must be implemented by a subclass), which is older than "= delete". Conceptually, it sets the corresponding slot in the virtual method table to null instead of a pointer to the (non-existent) method, except that it actually doesn't do that, it sets that slot to a pointer to a "pure virtual method called" function from the standard library, which AFAIK prints an error message and exits the program (IIRC, this is because there are some situations in which you can actually manage to call such a pure virtual method, like calling it from a function called within the constructor or destructor of the base class.)

A concrete object must implement all virtual methods including those marked as pure virtual. All =0 means is that someone else lower down the inheretence chain will implement this and the caller using the class type can rely on it existing.

If you try to instantiate an object without all methods implemented you'll get a compiler error about the class being "abstract". You should never have a case where there's a null in the vtable.

If there is some specific edge case around ctors then forgive me - this is C++ after all! But in practice you don't have to worry about this which is true for most of the C++ minutia.

> If there is some specific edge case around ctors then forgive me - this is C++ after all!

Yes, the specific edge case is around ctors and dtors. During the ctor or the dtor, the vtable is the vtable of the base class, not the derived class, so if you call a virtual function within the ctor or dtor of the base class (don't do that), the function which will be called is the one from the (possibly abstract) base class. See for instance this FAQ entry: https://isocpp.org/wiki/faq/strange-inheritance#calling-virt...

I would argue that's just "knowledge" not "arcane knowledge". To me arcane knowledge is stuff like the xvalues, prvalues, etc... Where it's both complex and obscure. Where you can go down a headache-inducing rabbit hole. This isn't really that, it's pretty simple, and really only does that single thing. It doesn't have complex interactions with anything else: https://en.cppreference.com/w/cpp/language/function#Deleted_...

As a sibling mentioned, it's not even a new syntax as '= 0' has always been there.

In addition to =0 mentioned elsethread, there is also =default which will force the generation of a default function that would be otherwise suporesed.

You can use =delete to delete any overload of a function, be it class member or namespace scope.

Languages evolve; =delete and default were added to get rid if a bunch of common hacks and the language is better for that (at least for delete, default has a bunch of pitfalls of its own unfortunately).

> I'm so done with C++. When a fundamental keyword becomes a semantic pitfall, you've created an esoteric niche for enthusiasts

but it always was!

There is no semantic pitfall in = delete. = default on the other hand...
Until one delves into all the cool niche features being added into Java, C#, F#, Python,...., even C.

Unless we are talking about v1.0 there always such cases when a language is around long enough.

How is it a pitfall? The article clearly elucidates several uses of the "=delete" construct. I don't see any pitfalls, just some new uses of the feature. And I expect most people to be the same: most people only ever use "=delete" on the copy constructor and the copy assignment operator, now this article tells them you can actually use it on other things too. There's no pit to fall into, just new ways to use a feature.
It's just a piece of programming knowledge being explained. It's very far from a 'semantic pitfall' or an 'esoteric niche'. Don't be such a drama queen.
I'm with you. The only thing = delete means to me is that I'm happy C++ isn't part of my dayjob.
It's well known that no one really programs in C++. They program in whatever subset of C++ their organization allows, their brain can handle or both.

Personally I found Google's C++ dialect to be sane, even pleasant. No exceptions, no mutable reference parameters (side note: this was a giant language fail that foo(f) could be a const reference or a non-const reference and there's no way to know without looking at the declaration), pervasive use of union types (absl::Status/StatusOr are the open source versions) and, here's the big one, individual teams were expressly forbidden from creating their own templates.

Now compare this to Facebook's dialect: exceptions allowed, mutable reference parameters allowed (side note: people weren't strict with const-ifying reference parameters so you actually didn't know if something was actually mutated or not), functions that routinely just return bool (so helpful) and you can create your own templates.

But seeing this post makes me see the wisdom in not only how cut down Google's dialect is but also that stopping teams creating their own templates is the only sane choice.

The depth of knowledge required to create templates that don't behave unexpectedly (eg dangling references, redundant copying, useable with move semantics, etc) is so large that only specialists should engage in it.

There is a certain breed of programmer who will use a feature of a language because it's there. They will view esoteric features, unnecessary complexity and brevity over readability as not only virtues but goals with which they can tell the world how smart they are.

If you write code for you, you're either doing it alone or you're not a team player. You write code for the next person who comes along and has to figure out why it's broken or just how it works, not to be as clever as you would like to think you are.

> here's the big one, individual teams were expressly forbidden from creating their own templates.

What do you mean by this? General template code was forbidden or just templated containers, STL style?

Means exactly what it says. You cannot create templated functions, classes, etc.
That seems extremely restrictive. I get being careful around heavy use of templates, and even more with containers where allocation/copies/moves can be really tricky; but there is a lot of space for using templates in a simple but useful way.
These are allowed. I have no idea where the parent post gets the idea that you can't ever write a template.
L5’s usually write code that needs L6’s to maintain.

L7’s should write code that only needs L4’s to maintain.

I love this so much. This may be one of my favourite comments ever.

Can I ask if this has a source? From a particular company? Meme? Quote? Paraphrased from something?

I know some versions of it, like Feynman saying that if he can't explain it to a freshman, he doesn't understand it.

Likewise, if you write code that only you and someone smarter than you can understand, you don't understand it.

That does bring a risk though. If you write simple code (e.g. in Go) that does what it does without any trickery, a more inexperienced job interviewer may look at it and scoff because you're OBVIOUSLY not a very good developer if you stick to simple code.

But that's a good litmus test, if they're like that, run away. They don't write code to solve a problem, they write code to flex and impress themselves, or to provide them with job security, or CV boosts.

And l3s and l8s write code that needs l7s to maintain.
Google's "dialect" both makes sense with other things I've heard about working for them and makes working for them less appealing. The flexible generic and compile-time resolution paradigms offered by templates are a lot of the reason it's compelling to use C++ over other languages, and while it's a totally reasonable language to use for speed if you stick to gluing together STL containers/algorithms, making anything complicated under those rules sounds very unpleasant.
> individual teams were expressly forbidden from creating their own templates

When were you at Google? I've been here a lot of years and never ever heard this. My codebase has a fair amount of templated code, including some metaprogramming magic. Nowhere has any tool said "please don't do this", nor do I think it should. Complex template code can be an absolute nightmare to maintain, but there is a group of C++ experts that are available to help if it is indeed the best solution to a problem and you want to devise a maintainable implementation.

The closest in the style guide I can find is "Avoid complicated template programming," which is clarified to include metaprogramming magic. This is very different than what you write and not even "forbidding" it.

2010-2017

Certainly at that time, creating your own templates was explicitly disallowed by the C++ style guide (ie readability requirements). The public style guide mentions template meta-programming specifically [1]:

> The techniques used in template metaprogramming are often obscure to anyone but language experts. Code that uses templates in complicated ways is often unreadable, and is hard to debug or maintain.

This certainly applied to Google3. Non-Google3 C++ code bases could and did have their own standards and style.

General note: templates were created but by library/framework teams who specialized in that. The way I like to put it, creating templates was a job for branch nodes not leaf nodes.

[1]: https://google.github.io/styleguide/cppguide.html#Template_m...

I can’t contribute to the discussion about Google’s rules. I just want to mention that simple uses of templates for generic data structures are very different from template meta programming. I can see how a style guide might allow the former and ban the latter.
This restricts metaprogramming, not you putting "template <typename T>" in your code for a generic function. I don't know how you got such a weird read of the readability requirements; it is definitely not as strict as you put here (and I'm currently at Google and commonly write C++).
"Template meta-programming" and "templates" are very different. Further, the style guide says "avoid" but does not forbid it. Clang-Tidy doesn't throw up any warnings when using things like std::enable_if.

My codebase overlaps with the majority of your tenure at Google. We are in google3.

I do agree with the guidance that things like SFINAE should be avoided in general, but I believe you've way overstated the rigidity of the guidance.

My C++ readability changelist contained a template. I don't think this part of the style guide is what you thought it was.
I'm a current Googler with C++ readability and I can confirm, you're absolutely allowed to write templates in "leaf node" projects and it's encouraged, especially in cases where templated code reduces error-prone boiler plate. Heck, there are whole Grow classes dedicated around learning how to use variadic templates.
Could you possibly be misremembering?

I think creating e.g. template<F> F MyComputation(const F& x) would be allowed e.g. for supporting both float and double. It would also be required when using the Ceres optimization library.

> Personally I found Google's C++ dialect to be sane, even pleasant. No exceptions,

Google's C++ dialect itself calls this out as a bug, not a feature:

"On their face, the benefits of using exceptions outweigh the costs, especially in new projects"

https://google.github.io/styleguide/cppguide.html#Exceptions

EDIT: Personally I like no exceptions normally but there's definitely classes of problems where exceptions are overwhelmingly superior, such as anything hitting files or a wire (eg, IPC). Serialization code is incredibly tedious to do error checking after every read or write call. This is where I think the "throws" proposal strikes the right balance: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p070...

> and, here's the big one, individual teams were expressly forbidden from creating their own templates.

There's no such rule. It just says avoid them if you can, otherwise go for it: https://google.github.io/styleguide/cppguide.html#Template_m...

And if there was such a rule, then Abseil wouldn't exist. You don't end up with your own template library by telling people to never write templates.

On exceptions, I respectfully disagree. Even if you don't use exceptions in C++, just allowing them (and thus supporting them) imposes a cost, both in terms of extra code but more importantly on the programmer to make sure what they write is exception-safe.

For a large number of people, exceptions is essentially interchangeable with "output log message and exit".

> And if there was such a rule, then Abseil wouldn't exist.

You misunderstand me. What I call "leaf nodes" don't (or shouldn't) create their own templates. By "leaf nodes" I mean, say, the payments logging team. They're created by specialists. Specifically, abseil is created from "//base", which is a mature battle-tested set of libraries at this point.

Likewise, anyone can create something in base or just modify something that's there but any such addition or change is going to go through an awful lot of scrutiny and testing.

That's the point.

You don't start by building something general in a base library. You start by solving your local problem, and if that turns out to be a general problem it should then be promoted to a base library. Battle-tested set of libraries started out their life as random utilities in "leaf nodes" - otherwise they wouldn't be "battle-tested". See also https://blog.codinghorror.com/rule-of-three/
Those are two well-known approaches to problem solving: GP is proposing top-down, you're advocating for bottom-up. There are pros and cons to each, and in the end it is more of a matter of preferences.

Just to give an example of someone else advocating for top-down problem solving, Bjarne advocates strongly for designing the libraries that you will need before writing specific code for your problem. He has some example videos where he goes through a code review taking some piece of procedural business code and showing how it should have been written to take advantage of existing libraries and creating new ones before.

> individual teams were expressly forbidden from creating their own templates.

Just want to underscore what others are saying, that this is not true and was not true during the time you claimed to work there. I was at Google 2014-2016 and I'm there now. Templates are common and not banned at all. I committed template-heavy non-metaprogramming code to google3 in 2014 and also throughout this year. I have never had C++ readability.

"Template Metaprogramming" is soft-banned in non-library code, meaning that it is disallowed if the readability reviewer looking at your code decides that what you are doing is "metaprogramming".

There are other style guides that apply to small subsets of google3, some of which forbid certain kinds of template constructions (variadic, SFINAE, etc). Maybe you're thinking of one of those? I'm not aware of any that forbid all templates.

> It's well known that no one really programs in C++. They program in whatever subset of C++ their organization allows, their brain can handle or both.

Not to tangent too much but this is exactly how I see javascript. I learned it one way, some code I need to use learned it a different way, and I have to choose whether to attempt to integrate it as-is, or spend the time to "port" it to my understanding.

> functions that routinely just return bool (so helpful)

I don't get it? What's wrong with returning a bool?

This applies to function parameters and return values: you almost never want to use a bool. Instead you want to use an enum with two values. Booleans are hard to read in most cases. Obviously there are trivial cases where this isn't the case (eg bool isDigit(char) is completely fine).

Contrived example:

    bool connectToHost(string hostname, bool useSsl);
First problem: if it fails, why does it fail? Some will be tempted to throw an exception. Better is to return some kind of status.

Second problem: you add another version of SSL, now what? If you'd used an enum, it's just another value. If not, you now need to retrofit it.

Better version:

    absl::Status connect(string hostname, EncryptionType encType);
People will often make mistakes when a function has 3 or more booleans and put the wrong value in the wrong parameter. With strongly typed arguments, this becomes a compiler error.

People will extend booleans to add a third value. In Java, for example:

    Optional<Boolean> foo
But it doesn't end there. As I like to say, for when three values for your boolean just aren't enough:

    @nullable Optional<Boolean> foo
Just start with enums.
> People will often make mistakes when a function has 3 or more ...

of anything. This is why non-compatible numeric subtypes are so valuable. Code like "quantity_sold = price" should not compile. You can do the same thing for other types using the same template but IME that's somewhat less common (but does happen: user_name = address!!).

It would be nice to get a variation on std::pair that works in a similar way to Rust's std::result<value, error>, because std::optional is just a dressed up boolean. I did just swipe the std::pair declaration and do that, but having it official would be nice. Especially for the non-throwing version of the standard libraries.

> People will often make mistakes when a function has 3 or more booleans

A function with three or more positional parameters in a language that isn’t badly broken (that is, one that supports keyword and/or structured parameters, which pretty much all significant languages do) is, IMO, a code smell. Even if more specific typing and IDE pulling up signatures can makes it less likely to make usage errors, readability is impaired.

I’m a big fan of `enum class IsFoo : bool { no, yes };`. I find it to be a great balance between type safety and conciseness. If you want your own unique Boolean-like type, that one line is all you need (unless you want operators, etc.)
There's this pretty abstract idea of boolean blindness, which basically boils down to the idea that a boolean by itself must always be associated with an interpretation and said interpretation is external to the boolean. Afterall a boolean by itself is just a single bit of data, 0 or 1, how to interpret that bit is entirely independent of the bit itself.

This means anytime you operate on a boolean, you must somehow recover its interpretation from somewhere, and the argument is that such recovery is error prone and fragile.

The preferred way, presumably, is then to encode the interpretation directly into the type, so for example you use an enum along with pattern matching, or you write a type that wraps a boolean with some value (like a Maybe/Optional type). In effect anytime you have a boolean, you also carry with it its interpretation side-by-side so you don't need to go figuring out how to recover it.

This article goes into it in more depth:

https://existentialtype.wordpress.com/2011/03/15/boolean-bli...

I personally think it's insightful and useful, but there are also downsides to it as well, for example when you need to operate on multiple booleans, it becomes unergonomic and bloated to deal with pattern matching over multiple enums or having to write out a lot of redundant code instead of operating on boolean operators.

Basically consider these two options:

Option 1: boolean blindness.

    bool isEven(int value);

    ...

    if(isEven(x)) {
      print("Even");
    } else {
      print("Odd");
    }
Option 2: Enum

    enum Parity {
      EVEN,
      ODD
    };

    Parity getParity(int value);

    ...

    switch(getParity(x)) {
      case: Parity::EVEN:
        print("Even");
        break;
      case: Parity::ODD:
        print("Odd");
        break;
    }
You can decide which if the two options above is more appealing to you in terms of reading and writing.
> They program in whatever subset of C++ their organization allows

So, no-one writes C++ except for some organization that tightly controls their work? :-( And no large organizations allow for some coding autonomy in writing, say, the insides of libraries and components?

> Google's C++ dialect

With changing language versions, dialects change. For example, union types: With C++17 you have variants; which are still a bit painful compared to other languages' union types, but are usually an improvement over just using unions and crossing your fingers you've always been careful with them.

> side note: this was a giant language fail that foo(f) could be a const reference or a non-const reference and there's no way to know without looking at the declaration

C and C++ have a lot of implicit type conversions. It's fair to be against that, but it's not like this specific aspect is a "giant fail" in itself; it makes sense, consistensy-wise.

> Facebook's dialect:

Those aspects you describe are indeed annoying, but, again - are we talking about recent code?

> The depth of knowledge required to create templates that don't behave unexpectedly ... is so large that only specialists should engage in it.

I disagree. If you're not obviously careless, your templated types will be reasonably well-behaved.

I don't think picking some C++ dialect is wise. These features are there for a reason. Funnily enough a lot of comments that respond to this are all of the style 'yes, but nowadays google allows this, o and this too, and that as well....' Sure, there are things one should not do, like having raw pointers all over the place and so on. Such things should be caught in code review.

Actually, I totally hate the google style guide. The prescriptions about indentation there seem to be optimized to make the code as unreadable as possible. An indentation of only two is already too little and adding that the opening brace is at the end of the line it becomes very difficult to see what block ends where.

Most languages get by with way fewer features and somehow people still manage to write programs in them. If you ask 100 programmers which language has the worst feature creep, 99 will say C++ and maybe one person APL ;)
Well, in a sense all of the other languages have fewer capabilities. C++ is more or less the only language that attempts to be a bridge between low level and high level programming in one language. I see that as beneficial. Many projects benefit from using both high level and low level programming. The alternative of writing it in multiple languages is not always that nice either. The kind of bridge code that one writes between two languages is often not very nice. Plus, a build that involves one languages is simpler than a build involving multiple ones.
> no mutable reference parameters

Not anymore.

> It's well known that no one really programs in C++.

I interview C++ programmers for my day job, and no, this isn't true at all.

I think you're just repeating internet memes.

> It's well known that no one really programs in C++. They program in whatever subset of C++ their organization allows, their brain can handle or both.

The parent comment is merely saying, "It's well known that no one really uses all the features of the C++ programming language productively" in a rhetorically interesting manner.

It's never been necessary to use delete to make a class uncopyable. It was always possible by just declaring (but not defining) a private copy constructor/assignment. I'd still do the same thing honestly, it's fewer keystrokes and not pointlessly backwards-incompatible.

Better yet, for the majority of cases, just inherit from an uncopyable class like boost::noncopyable.

If you declare but don't define a private copy constructor/assignment then you hit vague linker errors elsewhere that will likely send a user on a chase trying to figure out how their include paths don't match their linker paths and which library they forgot to link against.

'= delete' makes it exceedingly clear that what they are doing is not allowed, rather than "oh god dammit cmake what did I miss now..."

Since '= deleted' was added in C++11, there's really not any good "backwards-incompatible" arguments to be made about avoiding it. C++11 support is extremely broad at this point and has been for many, many years now.

> If you declare but don't define a private copy constructor/assignment then you hit vague linker errors elsewhere that will likely send a user on a chase trying to figure out how their include paths don't match their linker paths and which library they forgot to link against.

I think you missed the importance of private. Users of it won't get it to compile for them to get a linker error.

A private copy constructor can still be copied in the class itself. If you omit the definition, you still get a linker error message, but linker errors are among the most obtuse to actually discover. (Where did the copy construction actually happen? Who knows! You get, at best, the function that called it).

A deleted copy constructor produces better error messages. And it's not really backwards-incompatible at this point--deleted functions exist in compiler versions old enough to get a COVID-19 vaccine.

Another worry is that an engineer will see the link error and "fix it" instead of getting why it was done and diagnosing the misuse of the class.
I suppose this is possible for people who never programmed C++ before C++11, I haven't really encountered this incident to know. The biggest annoyance I've encountered isn't a bug - rather, it's the annoying IDE warning that the member is unimplemented!
In theory yeah, in practice it's incredibly rare for a class to look like it should be copyable to its own maintainer AND for the maintainer to try to copy it from its own methods AND for a linker error to be obscure for them to figure out when they do their first debug build. In fact I'm not sure I've ever encountered this situation. Usually it's pretty obvious if your class should be copyable or not from the outset so it's not common to make the mistake to begin with.
> not pointlessly backwards-incompatible.

if the only reason for using the C++11 standard were the =delete syntax, by all means, just use boost::noncopyable. But if you are using features from C++11, then there is really no reason for not using the new syntax.

My original post: Private constructors still match for overload resolution, and can cause your compiler to barf when a explicit deletion would allow the compiler to find an appropriate legal match.

I was wrong. delete leaves it in the overload set. I've been using C++ for a decade and I'm still tripping over some of the stupider semantics.

You edited before I got the chance to make a snarky reply :-) For the record, the following snippet will not compile because the deleted constructor will be matched during overload resolution.

    class C {
    public:
        C(int) = delete;
        C(long) {}
    };
    
    int main() {
        C x(3);
    }
So I read the article, and maybe I'm thick... but what does `=delete` mean? I saw a bunch of examples about how it will trigger errors with l/rvalues in some scenario, but I'm really no clear on the meaning of it.

Would someone mind explaining the meaning? Is it some sort of lifetime opt-in thing? Is it restricting how a method can be called? What's going on here? What is `=delete` telling the compiler?

A very basic and quite high level explanation is as follows.

In several cases the C++ compiler will generate default implementation for specific methods.

However there are cases where that isn't desirable, and until =delete came to be, the workaround was to mark the method as private without implementation.

So =delete deals away with such hack, although as mentioned this is a superficial overview of its purpose.

Ok, that's the C++ I learned many moons back (C++98/03). And it makes sense why that exists. Thanks.
You're definitely not thick! This is getting a bit into the weeds of lower-level C++, but it is useful. So what delete is doing is explicitly saying that a given method (with a particular signature) is defined to not exist. It's usually used in cases where the compiler would automatically create a method for you, or use a method you wouldn't want to be used.

The example they give is the reference wrapper, which (as the name might suggest), is used to wrap a reference to some thing. To reiterate, with hopefully a bit more of what is going on.

If the creator of this method had just created a function:

template<class T> auto cref(const T&) -> std::reference_wrapper<const T>;

Then by C++ overloading rules, you could call this function with either an const ref LValue, or an RValue of type T. (In their example, they used a number). Which for 95% of functions is totally fine, and desired behavior! However, in this case, it would be bad, because remember, we are trying to wrap a reference to some value (e.g. basically a pointer under the hood), so a pointer to some ephemeral value is likely not what we want.

In order to solve this, the creator explicitly adds: template<class T> auto cref(const T&&) = delete;

Now, if the user calls cref on an RValue like a number, they will get a nice error message saying that the function has been explicitly deleted. The reason this works is because "const T&&" is the better match for the type of an RValue, compared to "const T&", so this is the overload that the compiler will try to use, rather than the original version above.

Some other reasons to use delete, is if you want to do stuff like explicitly forbid the use of copy constructors, because you have some object that you don't want copies made of for whatever reason. By explicitly deleting the copy constructor, you can be sure that no copies are accidentally made somewhere in user code (which is especially useful when your code interacts with other libraries you don't control)

I hope that made sense, but the TLDR is that there are a lot of ways construct objects, delete objects, to copy / move data around, etc. If you are doing low-level memory management / optimization, you may want to explicitly tell the compiler not to do the normal "helpful" stuff it does for you, such as creating default constructors / overloads, and this is the supported way of doing that in the standard.

Yup that makes a ton of sense, thanks.
To explain from another direction, and add to the replies you're getting that explain it directly, C++ is a language that does its best to not break existing codebases.

When they want to introduce a new concept (in this case, a way to express that using an implicit function or overload is forbidden) they use one of the already-reserved keywords ("delete"). This sometimes leads to a clunky reading experience, but is better than breaking anyone's code that happens to use a variable or function named "forbidden" or similar.

Yes, this part is super relevant to answer WHY it is written the way it is.

C++ has a lot of nice ideas under the hood, but the syntax and reading experience definitely isn't ideal because of the desire for backwards compatibility (which includes the very strong desire to not introduce new keywords / tokens if there is any possible way to avoid it)

= delete; allows your users to get a nice compiler error messages when they call a function you don't want them to call, by mistake.

This can allow for instance to explicitely erase overloads and "disable" functions or methods. For instance imagine that you don't want a function to be called with a float, just with a double, because having floats creeping in somewhere would be a bug and you want to catch as many bugs as possible: then you can just do the following.

    void foo(float) = delete;
    void foo(double) { }
    
    int main()
    {
      // won't compile: 
      foo(1.23f);

      // will compile:
      foo(1.23);
    }
without the following line:

    void foo(float) = delete;
1.23f would be implicitely converted to a double instead of causing a compile error.

One case where I found it super useful is for instance to disable constructors taking a pointer when you have ctors taking a bool:

    struct foo {
      template<typename T>
      foo(T*) = delete;

      foo(bool);
    };

    int main()
    {
      void* x = nullptr;
      // won't compile: foo f(x);
      // won't compile: foo g("blablaba");
    } 
this (rightly) causes a compile error on both the creation of f and g, while if foo(T*) = delete; was not there then conversions from ptr to bool would happen:

    struct foo {
      foo(bool);
    };

    int main()
    {
      void* x = nullptr;
      foo f(x); // compiles :'(
      foo g("blablaba"); // compiles :'(
    }
How do you handle failures in constructors without exceptions?
In my company we do so by disallowing constructors to fail. If an object needs to be initialized with the potential of failure you need to call an init() function that returns an error code after construction.

Yes, that makes RAII basically impossible.

IMO the most important part of RAII is really the other side, "destruction is resource divestment" - and that still works. That is the main purpose of lock guards, refcounting wrappers, file objects and so on.
Why not a factory function that returns std::optional<T> instead? Or better a variant of T or error code?

The construct-then-init pattern is so annoying to deal with.

The safest & fastest way I'm aware of to use a unique_ptr factory. It involves adding an error flag to the class; the factory checks the error flag and resets the unique_ptr if it's true.
C++ constructors cannot express fallibility. Rust factory functions cannot placement-initialize (write into a pointer) an object too big to fit on the stack (aside from the perma-unstable `box` syntax, which I don't know if it even works or not). A unique_ptr factory cannot stack-allocate an object.

I don't know if it's possible to create a safe interface to fallible initialization, which is agnostic to whether the object will be constructed via return onto the stack, or onto heap memory. I'm interested to hear about proposals though (I've discussed this in the Rust community discord, but that isn't as public or concrete as a long-form article).

> C++ constructors cannot express fallibility.

No, they can. They can throw exceptions. My suggestion moves unsafe-construction onto the heap. Not ideal for a whole host of reasons, including a need to null-check the result -- but you can then move the object to the stack, and drop the unique_ptr. But if you're tying one hand behind your back, contortions will be necessary to accomplish the mundane.

You can have a named factory function returning an optional<T> (or your preferred fallible type).
Right, right... I still haven't adopted the '17 features yet.
First of all, I'd avoid exceptions in constructors, regardless of what programming language you use.
Yes, it's a good idea to know the implicit rules for constructors and assignment operators in C++.

No, it's not a bad idea to be as explicit as possible all the time when writing code, because code is for humans first, and computers second.

I always found =delete to be very nice documentation of what I can expect of a struct, class etc in terms of valid uses.

And C++ is huge - assuming all the people on your team understand all of it, and the corners and implicit behaviors is really not a great idea. So you should remove any potential source of confusion, or ambiguity in a source tree - as much as possible.

I haven't seen this syntax before and I just knew it would be c++.
The longer I've been away from C++ the happier I've been.