
Breaking all the Eggs in C++ - ingve
http://scottmeyers.blogspot.com/2015/11/breaking-all-eggs-in-c.html
======
ryandrake
I like the proposed ideas, but removing things and making breaking changes
might be going too far. Compilers should be smart enough to help. I wish g++
and clang had a "-best-practices" compiler flag: -Wall is not enough. But
since you will never get everyone to agree on what subset of C++ is best
practice, and what dark corners should be avoided, maybe a set of -Wbest-
practice-this and -Wbest-practice-that flags that allow you to pick and
choose. Warn yourself when you do things that are not warning-worthy but you
nonetheless want to avoid.

EDIT:

For examples, I'd love to have -Wold-style-cast on all of my compilers. The
nullptr example in the article is good too, maybe call it -Wold-literal-null?
It would be nice if my compilers could give me hints when std::move might be
appropriate, if I might be missing a copy or move ctor, or when I could
consider using initialization instead of assignment for members. Maybe this is
more of a job for the static analyzer and not the compiler--dunno.

~~~
bro-stick
The other big change that would be beneficial in (?:Obj)?C(?:++)? would be
const-by-default, requiring something like `var` for anything that's truly a
variable, since `mutable` has a specific meaning in (?:Obj)?C++. It would be
consistent with the theme of least-privilege, and allow for more optimizations
of one-time and per-loop iteration values.

Another great, modern C++ feature: `constexpr` functions, methods and compile-
time constants have the potential to put most, but not all, macros out-of-
business... Macros are powerful for DRYing C/C++ similar code sections which
cannot be expressed with templates, lambdas or function pointers, for macro
pasting identifiers together and for project configuration management for (ie,
kernel hardware support). Defines also don't occupy space but also lose
semantic value in debugging because they're computed at compile time and
spread out wherever they're used... debuggers encountering `constexpr`s should
offer both the computed value and a way to debug or debug (not program code at
all) how that value was constructed: gdb and lldb should be able to handle
that with plugins.

Here's also how we compile most our code, using both gcc and clang, on both
stable and head:

    
    
        clang++ -std=c++1[14z] -Weverything -Werror -Wno-c++98-compat -Wno-weak-vtables ...
    
        g++ -std=c++1[14z] -Wpedantic -Wextra -Werror -Wformat=2 ...
    

I would also really like to see C++1[7z] concepts implemented by major
compilers, because this will make our code a whole lot simpler by reduce the
hacks for runtime and compile-time type checking, and make generic programming
much cleaner by being able to constrain requirements via arbitrary bool
constexprs.

~~~
humanrebar
> const-by-default

const isn't the same as immutable. 'const' implies a (mostly) read-only view,
not read-only data. That is, C++ does not guarantee that the memory does not
change, just that some parts of the code will not change it (and even then,
there are ways to get around 'const').

It would be marginally better to be const by default, because there would be
less code to consider when analyzing changes to data, but the cost of this
feature is much larger than the benefits we'd see from it.

And, to top it all off, dealing with a lot of immutables really requires
different syntax and different style. Read through the rust book and then try
to do the examples in C++. You can do pattern matching and variant types in
C++, but they are ugly and probably slower than what we do now.

------
MaulingMonkey
Python 3 broke backwards compatibility and everyone stayed on 2 as a result.
Why is breaking backwards compatibility "for the hell of it" in C++ not going
to have the same problem? Codebases stay in stasis, stuck on older compilers,
for far less drastic reasons. And I've found that modernizing a codebase is a
lot easier if you can compile it to test your incremental changes.

Talking about a magic wand that makes source-to-source changes on a 100MLOC
codebase trivial is pure fantasy - requiring you to ignore that even in the
best version control systems, the resulting ~10MLOC merge is going to cause _a
lot_ of merge conflicts (some of which will be incorrectly resolved) and
history clutter. To say nothing of all the poor bastards who are satisfied
with the occasional tarball per release. Who are still doing better than the
people who can't be bothered to backup the code they wrote directly on the
server it will run on. And nevermind the fact that at least some of your
downstream customers are still using some old compiler that they're stuck with
- the only option for some legacy embedded platform they can't eliminate yet,
made by that company that went out of business.

And maybe that kind of cost would be worth it - for the right reasons, given
the right motivations. But I feel like I'm looking at a laundry list of mostly
minor, tiny issues, that are a "solved problem" in the form of existing
warnings. I can crank them up to errors as I modernize a codebase. You can
enable them by default in your build systems and IDEs. You can mandate them
across your organization, and in your contracts. You can incrementally apply
them per library as you get around to doing more maintenance on them.

~~~
humanrebar
> I feel like I'm looking at a laundry list of mostly minor, tiny issues, that
> are a "solved problem" in the form of existing warnings. I can crank them up
> to errors as I modernize a codebase.

So the perspective you might be missing is that there absolutely _are_ broken
or useless parts of the C++ standard. There _has_ to be a way to fix those if
we want:

1\. Continued improvements to the C++ language

2\. Compilers that aren't needlessly complex to implement and support

3\. Fewer weird edge cases and therefore fewer bugs when people miss them.

4\. A language that can be taught to you instead of drilled into you.

A case in point is trigraphs. They are an almost entirely useless feature that
requires an extra compiler pass. They cause real bugs that confuse and
surprise all but the most grizzled C++ devs (myMap["crazyUserName!!!??!?!"]).
It was like moving heaven and Earth to get them slated for removal in C++17.

It is not acceptable that things like this are this hard to get deprecated and
removed. There needs to be an accepted, uncontroversial way to identify cruft,
mark it deprecated, and eventually remove it from the language.

So... why the minor, tiny issues? Because we're not ready for the big ones
yet. Let's get some minor issues under our belts and then we'll figure out how
to deal with bigger ones.

~~~
MaulingMonkey
> So the perspective you might be missing is that there absolutely are broken
> or useless parts of the C++ standard.

I'm not missing it. I feel C++ is a broken enough language that I generally
prefer to use others. That said, C++ is my day job, so I get to feel the burn
and wish it weren't so broken.

As a result, I'm all for getting rid of useless parts of the C++ language. But
backwards compatibility is not useless! I'm willing to sacrifice minor,
"theoretical" backwards compatibility for relatively minor reasons - I don't
think I've seen any code broken by >> being interpreted differently in C++11
in templates, for example. I only know of one case where trigraphs were used
intentionally - and compilers have been disabling them by default and warning
about them for quite some time now (to the point where I don't really care if
the C++ standard is fixed, because C++ in the wild is fixed.)

But getting rid of NULL and 0 as pointers? Breaking overrides missing the
override keyword? Every C++ codebase I know will break massively. It'll make
C++ification of a C codebase even more of a massive pain in the ass. Pass.
Make them enabled warnings by default instead. Enable warnings as errors when
you can. People are already doing this for you:
[http://codepad.org/R5XyUby2](http://codepad.org/R5XyUby2).

\--------------------------------

RE 1: Continued improvements to the C++ language are occuring. C++11, C++14,
and C++17 are all introducing new stuff, and providing better options for
securing your code. This does not require breaking backwards compatability.
Unfortunately this goes against points 2, 3, and 4, for reasons that have
nothing to do with what's been listed here.

RE 2: I don't see anything in this list that would help significantly there.
If anything, mandating more diagnostics about override keywords will
complicate the compiler further.

RE 3: So lets add some sequence points that make parameter evaluation order
defined. Lets get rid of those type aliasing rules that every large codebase
runs afoul of. Lets do some high impact stuff that will catch some really
nasty edge cases that, realistically, will fix more codebases than it breaks.

...but none of that seem to be on this list.

RE 4: A lost cause that's getting worse. Adding a bunch of caveats that "oh,
except in C++2x you can't do this thing that's in decades worth of C++
tutorials, examples, and codebases - including most of what you've seen
online" isn't going to make the teaching problem any easier. It's going to
make it worse.

\----------------------------------

"So... why the minor, tiny issues? Because we're not ready for the big ones
yet. Let's get some minor issues under our belts and then we'll figure out how
to deal with bigger ones."

We've already got lots of experience under our belts with fixing minor issues.
auto, >>, template exports, throw() statements - all the way back to the very
first standard, which eliminated implicit conversions from int -> enum, void*
-> t* , and for loop scoping - with all the breaking of C or prestandard C++
that this entailed.

What will it take for us to be "ready" to deal with things like parameter
evaluation order being unspecified - and thus at the mercy of your compiler
version, flags, and phase of the moon? Fixing that would break no code that
wasn't already broken, and eliminate a massive source of unspecified weirdness
that I still struggle to explain to other C++ developers who have been coding
for _years_.

I still have to carefully explain the difference between operator precedence
(how a() - b() - c() is parsed), and sequence points (which define the
evaluation order of a(), b(), and c() - or more accurately don't). They point
to && and ||, and I explain how they - along with "," and "?:" \- are special
cased by the C++ standard to define additional sequence points that just
happen to match the operator precedence rules - unless you overload them, so
never do that - and by the end of it, I've either blown their minds or they
still don't quite believe me.

Showing them how std::cout << i++ << i++ << i++ << "\n"; changes behavior
depending on compiler flags tends to help - although that's cheating as
there's additional unspecified (or undefined?) behavior for modifying the same
integral multiple times without an intervening sequence point...

------
santaclaus
I'm surprised to see no mention of the Sutter and Stroustrup core guidelines
[1]. Wouldn't a statically checkable implementation of these ameliorate most
of Meyer's concerns?

[1]
[https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC...](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md)

~~~
something123
You should watch the cppcon2015 keynotes

[https://channel9.msdn.com/Events/CPP/CppCon-2015/Writing-
Goo...](https://channel9.msdn.com/Events/CPP/CppCon-2015/Writing-Good-C-14)

static checking of the core guidelines is the _entire reason_ they are writing
them. This isn't just a nice literary exercise

The static checker is found here (obviously still under development)
[https://github.com/Microsoft/GSL](https://github.com/Microsoft/GSL)

------
yoodenvranx
This highlights a problem I see with stackoverflow (and also google in
general) more and more often:

In the beginning of stackoverflow all of its content was up-to-date but now
that it is running for a few years a lot of stuff is out of date.

If you google for "c++ pointer 0 or NULL" you get this page from 2008 as the
top result: [http://stackoverflow.com/questions/176989/do-you-use-null-
or...](http://stackoverflow.com/questions/176989/do-you-use-null-or-0-zero-
for-pointers-in-c)

With C++11/17 these answers are now out of date but this pagr will continue to
be at the top of google for the forseeable future.

And exactly the same happens for all other technical stuff as well... I learn
Javascript at the moment and at least 50% of all solutions I find on
stackoverflow and other pages are heavily out of date.

And this problem will get worse in the next 10 years. Perhaps it is time to go
back to curated link directories?

(I hope it is clear what I want to say, I am on mobile and busy with other
stuff)

~~~
alkonaut
This is really a flaw in Google, not stackoverflow (maybe at SO too, depending
on how their search is ranked).

The relevance of a page at StackOverflow is less relevant if it is 5years old
than if it is 1 year old, even if the page rank based on links is higher. I'm
not sure if/how Google implements this, but a good heuristic would be to
classify content as modern tech and have a page rank decay based on age.

For an even more advanced implementation, very "new topics" should decay even
faster. Articles about Windows 10 or the Rust programming language should have
a half life of 6 months now, while older topics can have longer tails.

~~~
vikiomega9
I think of this as a SO feature where out of date questions would point
forward to the newest related question. This could easily be a moderated
feature. One would think this preserves the evolution of the language is some
sense. It is incentive also for people to capture such knowledge :)

~~~
alkonaut
Yes, although that only works within SO.

When I google for a rust term now, I'd rather hit a 6 month old blogpost than
a 2 year old SO question, ranks being equal. With C# it's the other way
around. The only difference between the two is that the search trend history
shows that we are on year 15 of C# searches so the difference is small
compared to the age of the technology.

------
akavel
As much as I like the premise of the article, I see two serious problems, that
would need to be resolved for the "magic wand" tool to be useful, and it's not
very obvious for me how:

1\. What about _templates_? If there's a "template<T> void foo(x &T)", and it
does "x++" inside, and I sometimes use it on int, sometimes on bool, how would
the magic wand upgrade only the bool cases to "x = true"? (And please don't
tell me "then you have borked codebase, u go fix it, man!" In C++, you have
codebase you have, sometimes decades of legacy code you hope to upgrade bit by
bit "when time, boss and priorities permit".)

Similarly, if the template does "x = 0;", how could the magic wand upgrade
only the cases where T is a pointer, to "x = nullptr;"?

2\. One more important, yet not mentioned feature of the magic wand tool,
would be that it'd have to be _flawless_! If the tool has bugs, it could break
my codebase without me knowing, till it's too late. Especially tricky here is
how would the tool behave on code with _undefined behavior_ ; after years of
working with C++, I have a nagging suspicion that virtually _every_ sizable
C++ codebase does actually have some undefined behavior lurking somewhere.

~~~
eru
> [...] I have a nagging suspicion that virtually every sizable C++ codebase
> does actually have some undefined behavior lurking somewhere.

You are being too optimistic. Any C++ code base over at most a couple hundred
very carefully crafted lines will have undefined behaviour and implementation
specified behaviour.

------
kartikkumar
I completely agree. Not being an expert in C++ but someone that uses it almost
daily, it's so incredibly frustrating to have to constantly battle the fact
that for every choice, there are a million ways of doing it, and they're not
all equal, but you only know that if you're an expert, or discover it when you
fall flat on your face months later and have to refactor. Feels like I'm
constantly shopping and left to figure out which deals suck and which are
gems.

Break all the eggs! C++ could be so much more effective if the backwards
compatibility requirement could be overridden on the most basic of stuff
that's still part of the language even though consensus, i.e., best practice,
was to drop it out years ago.

------
cjensen
The problem is that this messes up version control history. For example, I
work on a code base which is 20 years old. It's nice to use 'svn blame' and to
get the log message associated with a given line of code and give me insight
into what I was thinking.

That said, it's not an insurmountable problem. Usually I'm looking at a
particular line, and if history says it was touched in a compiler cleanup then
I just have to click a few more times to get 'svn blame' of the version
preceding the cleanup.

~~~
makecheck
Arguably this is a feature revision control systems should have; a kind of
"trivial change" marker to filter certain annotation/log entries by default.

~~~
humanrebar
You should probably do this at inspection time, not commit time. Maybe a list
of commits from the command line,or a commit ignore file.

------
cpeterso
gcc and clang have optional warnings for some of these issues, without needing
to use clang-tidy.

For NULL, 0, and nullptr, gcc and clang already treat NULL as a magic __null
value and -Wconversion-null (enabled by default) can warn about using NULL as
a non-pointer value. gcc also can break 0-as-a-null-pointer with the -Wzero-
as-null-pointer-constant flag. I had hoped to fix these warnings in Firefox
([https://bugzilla.mozilla.org/show_bug.cgi?id=777515](https://bugzilla.mozilla.org/show_bug.cgi?id=777515))
bug there were thousands of warnings because gcc warns about using NULL, not
just 0.

For override, gcc has -Wsuggest-override and -Woverloaded-virtual warnings
that may help here. clang has -Winconsistent-missing-override.

For uninitialized variables, gcc and clang have -Wuninitialized. However, gcc
-Wuninitialized has historically reported a lot of false positives.
Fortunately, recent gcc version moved the flakier uninitialized warnings to a
new -Wmaybe-uninitialized flag. In my experience with Firefox, -Wmaybe-
uninitialized warnings are false positives about 90% of the time. On the other
hand, clang has a -Wsometimes-uninitialized which is reliable.

For bool++, clang has -Wdeprecated-increment-bool.

btw, clang does not document its warning flags, but some resources I've used
are the [http://fuckingclangwarnings.com/](http://fuckingclangwarnings.com/)
reference, searching through clang's source code at [https://github.com/llvm-
mirror/clang/blob/master/include/cla...](https://github.com/llvm-
mirror/clang/blob/master/include/clang/Basic/DiagnosticGroups.td), and clang's
-Weverything flag (which truly is _everything_ , including contradictory
warnings).

------
ArkyBeagle
If NULL is bad, simply invest NULL with the same ... behavior? semantics? as
nullptr.

I cannot for the life of me help but think that this is simply a distinction
without a difference.

I work in the embedded space, and in each case when I've used C++ instead of
'C', me and at least one other senior ( and I mean age senior - +40 ) guy end
up bemoaning that if we'd just had a 2-hour meeting on which subset of 'C'
we'd have used, it would have taken half the time.

Oh so obviously, everybody has a different experience, and celebrate the
diversity and all that, but I am really happy that I can always fall back on
good old 'C' and get something done.

EDIT: s/invert/invest

~~~
aidenn0
Embedded is about 20 years behind the curve with regards to C++ best
practices.

~~~
magila
Worked for 6 years writing embedded C++, can confirm. The industry is
dominated by stodgy old devs who still think C++ means C with classes.

~~~
aidenn0
Well a lot of late '90s non-embedded C++ style was a very poor fit for
embedded; everything dynamically allocated, lots of templates.

~~~
ArkyBeagle
It wasn't that bad.

------
ufo
C++ is so full of dark corners due to its history and backwards compatibility
requirements (both to itself and to C) that I'm not sure whether breaking
backwards compatibility to solve just a handful of issues is worth the
trouble.

~~~
bkjelden
The problem with a release that breaks backwards compatibility is that no one
would use it.

Very few new projects are written in C++. It's the old products with 1M+ LOC
codebases that most C++ devs live in.

~~~
vilya
You're right with your first point, but wrong with the second: a lot of new
projects are still written in c++. Pretty much every non-indie video game, for
example.

~~~
such_a_casual
>Pretty much every non-indie video game, for example.

So code that relies on a lot of existing software then (ie the game engine and
other in-house libraries that any company would have). You have proved his
point instead of countering it.

~~~
okatsu
Any large enough game will use C/C++ at its core because short of using
assembly, it's the only real choice we have. C#, Lisp or whatever else are
nice for gameplay scripting, but underneath there is a need for control that
most languages will not give you. Same thing for anything with hard real-time
requirements.

~~~
anonymoushn
The JVM is fine for running low-latency automated trading systems. I wonder
why it's not suitable for applications with timing requirements that are
hundreds of times more relaxed?

I don't know if the JVM is better or worse for this than some other runtime
like Go's or the CLR or whatever, I just wrote about the JVM because it is the
one that I have used in this context.

~~~
humanrebar
> The JVM is fine for running low-latency automated trading systems.

1\. There is a lot of expert tuning to get that sort of performance with
respect to latency. That tuning would need to be reproduced for every target
system.

2\. Those trading systems can have ridiculous amounts of RAM to give the JVM
GC lots of room to work with.

3\. Reading packets from a wire and writing other packets to a wire is a
different problem than reading controller inputs and modelling complex 3D
worlds.

...that's not to say you can't have AAA Java games. To some degree that
already happens with Android games, at least if you're willing to stretch the
definition of AAA. (If you define AAA as "uses C++", the nomenclature becomes
fairly moot).

~~~
plonh
Minecraft is Java. It has awful performance (but it's simple enough and mixed
desktops are powerful enough)

------
steven2012
The thing I hate most about C/C++ is how they overload the same keywords to
mean different things.

So the idea of:

int x = void;

is inherently wrong to me. Why not just create a new keyword with a clear and
explicit meaning?

That said, breaking changes are simply wrong. C/C++ is what it is, stop trying
to make it into Java, Go or whatever. Trying to change it into a language that
it's not is simply going to cause confusion and chaos, and kill the language.
The things Meyer points out are all correct, but just because it's right
doesn't mean it's right for the C++ community.

Every language has it's own pluses and minuses and thinking you can squash all
problems in all languages and make it "perfect" is a losing battle.

~~~
corysama
> Why not just create a new keyword with a clear and explicit meaning? That
> said, breaking changes are simply wrong.

Ummm... What is your suggestion for a new keyword that would not be a breaking
change?

~~~
mikeash
The typical way to do this is to create a new keyword that starts with __, or
_ and a capital letter. This is not a breaking change because this space is
reserved for the language and implementations. You can then add a header that
#defines something less ugly to these new keywords.

~~~
marvy
Like what C did with bool? Ick. Gross. Ugly. Me no like. I understand why they
did that in this case: by the time they got around to fixing the lack of bool,
they had to work around everyone else's workarounds! But that was an extreme
case. Let's not do that more often than we must.

~~~
mikeash
I like it. It effectively adds a new keyword to the language that's only
available if you include a header. The technique isn't pretty, but the result
is pretty nice IMO.

~~~
marvy
But I don't want to include a header just to use bool. I'm willing to include
a header for big things (like strcmp), but bool is such a small convenience
that it should be available without an include, because otherwise it's less
work to just use an int than to go to the top of the file and add an include.

Plus, I think the idea of putting each keyword behind an include gets annoying
if you do this for a significant fraction of your keywords. (It's neat that
you can, but... should you?) For instance, imagine if throw, class, and
protected were all behind separate includes?

------
overgard
I don't get the urge for this -- its basically obsessing over other people's
sloppiness. Make it a warning -- if you care about code quality you have
warnings as errors anyway -- and you don't need to break everything in the
process.

------
eru
Actually, all nullable variables should be explicitly tagged as Option values.

