
C++ Has Become More Pythonic (2014) - luu
http://preshing.com/20141202/cpp-has-become-more-pythonic/
======
nv-vn
None of these ideas seem outright specific to Python and I don't think it
makes sense to attribute all these changes to Python influence. C# and D, for
example, have had most of these features for a few years. One could just as
easily say "how C++ is becoming Haskell" or "how C++ is becoming Go" (both of
which have many of these features). I think a better title for this would be
something along the lines of "Python idioms in C++".

~~~
coldtea
> _how C++ is becoming Go_

Hmm? Go has almost none of these features.

~~~
nv-vn
It has tuples, local type inference, for loops on ranges, and anonymous
functions/lambdas (which act as closures/"capture" scope). Hardly "almost none
of these features".

~~~
coldtea
Go doesn't have tuples (multiple return isn't that). This leaves us with local
type inference, for loops on ranges, and anonymous functions/lambdas.

The parent also mentions: binary literals, raw string literals, uniform
Initialization, standard algorithms like map, filter and any, and parameter
packing. All of which Go doesn't have.

Plus, Go range loops are constrained to built-in types (slices, channels,
etc.), unlike C++/Python where you can implement begin()/end() or the iterator
protocol respectively and get it for any type.

So, from 10 things mentioned, whereas C++ and Python share ALL of them, Go
only has 2 of them and sort of has one more (the range-for).

I'd stick with: "almost none of these features".

~~~
catnaroek
Go doesn't even have local type inference:
[http://ideone.com/RQzz7E](http://ideone.com/RQzz7E). Contrast with a language
that actually does: [http://ideone.com/LtN1vQ](http://ideone.com/LtN1vQ). Type
inference, even the local kind, means figuring out the types of variables from
how they're used, not just from how they're initialized.

~~~
Raphael_Amiard
That's a completely arbitrary definition, and it is wrong. Type inference
limited to the initialization point is still type inference, and actually most
languages before Rust and Swift that have local type inference (C#, D, C++11)
are limited to type inference at the declaration/initialization point.

~~~
catnaroek
> That's a completely arbitrary definition, and it is wrong.

The definition is very simple - the inference engine must use _nontrivial_
inference rules to reconstruct the types. The rule “given P then P” alone
doesn't quite cut it, which means that ”given that 0 is an int, then something
that's initialized to 0 is an int“ also doesn't quite cut it. Calling what Go,
C# and C++11 have “type inference” is akin to calling Python a statically
typed language because it has a trivial type system with exactly one static
type.

> actually most languages before Rust and Swift that have local type inference
> (C#, D, C++11) are limited to type inference at the
> declaration/initialization point.

Then they just have unidirectional type propagation - which is perfectly fine,
just not type inference.

~~~
Raphael_Amiard
> The definition is very simple - the inference engine must use nontrivial
> inference rules to reconstruct the types.

Where exactly is this rule coming from ? What is the source of your definition
? And if it is the authoritative definition, why don't you go rewrite the
wikipedia page, which is then wrong ?
[https://en.wikipedia.org/wiki/Type_inference](https://en.wikipedia.org/wiki/Type_inference)

> The rule “given P then P” alone doesn't quite cut it

"Doesn't quite cut it" sounds like a very precise and scientific definition !
Also your categorization of C# is wrong, even by your own definition, because
of function types inference and of subtyping, which makes the algorithm non
trivial.

> Then they just have unidirectional type propagation - which is perfectly
> fine, just not type inference.

Again:

1\. This is wrong, even by your own definition. You can have local type
inference limited to the initialization point, and have non trivial resolution
rules. See this paper by Benjamin Pierce for an example :
[http://www.cis.upenn.edu/~bcpierce/papers/lti-
toplas.pdf](http://www.cis.upenn.edu/~bcpierce/papers/lti-toplas.pdf)

2\. Where is this definition even coming from ? In my book, unidirectional
type propagation _is_ a form of type inference, and it quite logically
follows: The type _is_ inferred. The fact that you chose to draw a line, say,
to flow sensitive inference (in the case of Rust and Swift) or to global
unification style inference (ala ML) _is_ a completely arbitrary definition,
and one that I have to this day never encountered. Indeed, I can't find any
online resource that agrees with you. Most language documentations, including
C#, C++ and Go, call this type inference. Most researchers call _any
mechanism_ where a language infers the type, type inference, even the
mechanism that allows you to call generic without specifying the type of the
instantiation, as in this paper :
[https://www.researchgate.net/profile/Erik_Meijer/publication...](https://www.researchgate.net/profile/Erik_Meijer/publication/221321900_Lost_in_translation_Formalizing_proposed_extensions_to_C/links/0c960538ae979a41f8000000.pdf)

I have absolutely _never_ encountered any definition of type inference which
draws this line, and for good reasons, because it doesn't make any sense.

~~~
catnaroek
You are right about C#: The type of `x => x + 1` can't be said to be anything
other than "inferred". I stand corrected.

But I disagree with the rest of your post. From the point of view of _type
inference_ , what matters is the nature of the _type constraints_ that the
type checking algorithm generates:

(0) Traditional type checking: All type constraints are of the form “T1 = T2”,
where both “T1” and “T2” are closed type expressions. There is nothing to
infer.

(1) Type propagation: All type constraints are of the form “X = T”, where “X”
is a type variable and “T” is a closed type expression. Again, there is
nothing to infer, but we might need to propagate “T” between places. Say, from
the RHS to the LHS of a variable initialization.

(2) Type inference: Type constraints are arbitrary type expressions, which
must be solved using a unification algorithm.

As for why type propagation doesn't count as type inference: [http://lambda-
the-ultimate.org/node/4771#comment-75771](http://lambda-the-
ultimate.org/node/4771#comment-75771)

------
Negative1
This is the kind of broad generalization that someone that knows a lot about
Python but very little about other languages would make. The truth is most of
these features have existed in other languages for many years, before C++
adoption and in some cases, before Python adoption.

~~~
sklogic
Not in "some cases" but in "all cases". Python did not introduce anything new
in this list.

------
massysett
auto is type inference. That's not "pythonic", that's something that
statically typed languages have had for decades. Indeed, it's nothing like
Python, where general practice is to pass around things derived from Object
and depend on runtime typing failures.

There's also nothing Pythonic about lambdas. Come on, lisp is ancient and it
has lambdas.

This article essentially says C++ got stuff that has been around for decades,
and Python has stuff that vaguely resembles them, so C++ is learning from
Python.

~~~
catnaroek
`auto` is just bottom-up type derivation, _not_ really type inference. Type
inference lets you recover the type of a variable from how the variable is
_used_ , not just from how it is initialized. Implementation-wise, bottom-up
type derivation only requires unidirectional type propagation, whereas type
inference requires solving arbitrary systems of type equations. The former is
a special case of the latter, where all equations are of the form “X = T”,
where “X” is a type variable and “T” is an arbitrary type expression.

No type inference: [http://ideone.com/fmm92M](http://ideone.com/fmm92M),
[http://ideone.com/HzyM2E](http://ideone.com/HzyM2E)

Type inference: [http://ideone.com/0Gv8fV](http://ideone.com/0Gv8fV),
[http://ideone.com/r9nHUF](http://ideone.com/r9nHUF)

~~~
dllthomas
It's clearly not type inference in its full generality (or all that close,
really), but it seems a bit odd to say "X is a special case of Y; A is doing
X; A is doing no Y".

~~~
catnaroek
Type inference is performed by scanning the program, generating a system of
type equations and solving it. If all type equations are of the form “X = T”,
then there's nothing to solve.

~~~
fauigerzigerk
Type inference is inferring a type where none has been specified explicitly.

There are certainly more or less advanced forms of it, but solving systems of
type equations is not the definition of type inference.

~~~
catnaroek
On what type inference is, from a PL researcher: [http://lambda-the-
ultimate.org/node/4771#comment-75771](http://lambda-the-
ultimate.org/node/4771#comment-75771)

~~~
fauigerzigerk
There will always be some who insist that only the most advanced form of X is
"really" X.

~~~
catnaroek
ML-style type inference is hardly “advanced”. And I'm even willing to count
more limited forms of inference as seen in Scala, Rust or Swift - what they
have is only local, but it's actual inference.

But what you're claiming is the equivalent of having a “number inference”
engine that can conclude that “x = 8” from “x = 4 * 2” - that's not
“inference”, it's just evaluating a single expression. Actually, what Go has
is even less than that, because Go's type checker doesn't need to reduce
anything.

------
fndrplayer13
As a developer who used to build systems in embedded C++ and now spends all of
my time building web backends in mostly Python, I have to agree with the
premise that C++ is becoming more influenced by Python. That's a good thing, I
think. I really think C++ is a wonderful language if properly curated and used
by a responsible team of developers.

One tiny thing that would be cool to see built into C++ would be an equivalent
to the Python range function. The boost version is nice for now, though.

Finally, I also see this as a nice compliment to Python and the power that it
offers as a language.

~~~
hliyan
I wonder if all these languages features are really necessary. I used C++
during the first ten years of my career and what I missed the most were better
standard library features and more third party libraries (we used to write
everything ourselves).

Language features are nice to have, but I never really missed any specific
feature. Almost every sort of syntactic simplification I wanted to achieve, I
could do so with a function, class or a template.

~~~
cLeEOGPw
It's not about necessity. It's about convenience. There's really not much you
can't do with C++, the only question is how much effort it requires. These
changes help reduce effort without sacrificing anything, spare it for
minuscule amount of compile time. Besides, you can always use old syntax if
you prefer.

------
sklogic
Funny how Python narrows the perspective of its practitioners. To brand as
"pythonic" all those decades old concepts that had been well known and
widespread long before Python is, well, among the most amusing symptoms of a
fanboyism.

~~~
cLeEOGPw
I think Python's, along with languages others mentioned here, popularity is
what influenced and partly driven the rapid change of C++. Lisp was around for
decades, yet only now, almost right after Python became popular, these old
concepts were incorporated.

~~~
paradoja
There's a comment by Fabio Fracassi ( [http://preshing.com/20141202/cpp-has-
become-more-pythonic/#I...](http://preshing.com/20141202/cpp-has-become-more-
pythonic/#IDComment923313723) ), member of the German delegation for the ISO
C++ committee, contending that view. At roughly the same time that Python has
been getting this features, many other languages have also been getting them,
and usually without crediting Python influence (but rather other languages').

------
netheril96
After C++11, the bottleneck of the speed of development in C++ is no longer
the language. But it has numerous other problems dragging down C++ developers
still: std::string is close to useless, no userspace and project-scoped
package manager, too long build times, hard to debug templates, etc.

~~~
catnaroek
> too long build times, hard to debug templates, etc.

These are symptoms of language issues.

~~~
tomlu
And hopefully somewhat mitigated by modules once they land and (if it happens)
everyone migrates.

~~~
catnaroek
Asking out of total ignorance: How exactly do modules interact with templates?
Is template specialization restricted to the module where the template is
defined? If not, I don't see how modules could help much.

~~~
pjmlp
Still ongoing design. This is the Microsoft view of them

[https://blogs.msdn.microsoft.com/vcblog/2015/12/03/c-modules...](https://blogs.msdn.microsoft.com/vcblog/2015/12/03/c-modules-
in-vs-2015-update-1/)

Regarding templates, the approach is similar to the deprecated export keyword,
so partially compiled templates and inline functions are stored in the
metadata database used by the compiler for modules.

~~~
catnaroek
I love how the second and third paragraphs describe modules as if they were
some sort of novel, perhaps even futuristic, language feature that no
programmer has ever seen before. “Oh, it's totally not like we're catching up
with what other programming languages had for decades.”

~~~
pjmlp
The problem is that in the C culture practically ignored everything that was
done in computing up to the late 70's.

Bjarne was of course aware of modules, being brought on Simula and other
programmer friendly languages, but then he had to make C++ code fit into UNIX
linkers that only knew about AT&T Assembly and C binary formats.

So no modules there, and along the years you got a culture of developers,
specially those that only learn on the job, that never learned about modules
and its history.

You see it happen also when cache friendly code and RAII get discussed.

Those things were also possible outside the C family of languages, before they
won the market. So most millennials think that they are some special language
capabilities only possible in C and C++.

~~~
catnaroek
> You see it happen also when cache friendly code and RAII get discussed.

To be honest, I can't talk intelligently about cache-friendliness at all. I
know that there exist models of computation (say, for complexity analysis
purposes) that explicitly take memory hierarchies into account, but I've never
seen them actually used on anything but the simplest data structures and
algorithms.

With respect to RAII, I think the main benefit isn't cache-friendliness, but
rather deterministic destruction - you can't delay relinquishing a scarce
resource that someone else might want to use.

> So most millennials think that they are some special language capabilities
> only possible in C and C++.

I'm a millennial, and have to admit with shame that I grew up thinking C++ is
the best thing ever. But it's possible to recover from it. :-)

~~~
sklogic
> _but I 've never seen them actually used on anything but the simplest data
> structures and algorithms_

Take a look at a typical performance-oriented GPU coding, cache is the major
analysis parameter there. Data structures are mostly 2D and 1D arrays, but
they may be scrambled in a very complex way, and the algorithms are
arbitrarily complex.

~~~
catnaroek
Thanks for the pointer. Will have a look.

------
nhaliday
Preshing is a smart guy but this seems to miss the point in a way typical of
C++ devs. Python—as with other languages—isn't just a list of features. It
also has a guiding philosophy (made explicit in Python's case in
[https://www.python.org/dev/peps/pep-0020/](https://www.python.org/dev/peps/pep-0020/)).

C++ doesn't seem to have a guiding philosophy besides "be a Swiss army knife
backwards compatible with C." That's OK but I would argue it prevents C++ from
every being "Pythonic."

~~~
santaclaus
> That's OK but I would argue it prevents C++ from every being "Pythonic."

This got me thinking, what would one call an idiomatic C++ style (which I
suppose is part of what the core guidelines are trying to push)? Cppthonic?
Bjarnic? Python is 'Pythonic,' I usually hear idiomatic Ruby described as the
'Ruby Way.' Is idiomatic Rust 'Rustic'?

~~~
mlvljr
How about "Herbal" for Herb Sutter's style?

Weed pun intented :)

~~~
marvy
A lot of your posts show up as [dead]

~~~
marvy
It means hardly anyone sees them.

------
discardorama
I'm sure I'm not the only one who doesn't like how these things are bolted on
and shoehorned into the existing constructs, resulting in some ugly-ass
syntax. Consider:

    
    
      auto triple = std::make_tuple(5, 6, 7);
      std::cout << std::get<0>(triple);
    

Ugh. And don't even get me started on "myList.push_back(5)" . "push" usually
means "add to the front"; why not use "append" ?

~~~
leetrout
Sigh - I know why you got downvoted but I came to post the same thing. I don't
program in C++ but specifically the

std::get<0>(triple)

It really doesn't look great. It looks like the template syntax using the <>.
I'm sure I would get used to it if I were using it every day but from an
aesthetic sense it doesn't win me over.

~~~
SamReidHughes
It is the template syntax.

------
21
I would like to borrow some simpler syntax from Python:

"if (a)", "for (...)" -> "if a", "for ..."

"if ((a > b) && (c > d))" -> "if a > b and c > d" (yes, I know C++ supports
"and" if you wish)

Or how about accepting a new-line as a ; at the end of a statement (ie:
optional ; at line end)

I also love nested tuple unpacking: "for i, (key, value) in
enumerate(dict.items())"

~~~
GFK_of_xmaspast
> Or how about accepting a new-line as a ; at the end of a statement (ie:
> optional ; at line end)

And have to use backslashes or something for multiline statements? Ugh.

~~~
andreasvc
Not necessary. Python allows any open bracket to signal that the line is not
terminated yet.

------
vitaut
Also C++ has Python-like formatting:
[https://github.com/fmtlib/fmt](https://github.com/fmtlib/fmt). Disclaimer:
I'm the author of this library.

------
jordigh
It certainly has for me.

Here is me translating as literally as I could a bit of nontrivial code (which
I originally learned from R's C code):

[http://inversethought.com/hg/medcouple/file/default/jmedcoup...](http://inversethought.com/hg/medcouple/file/default/jmedcouple.c++)

[http://inversethought.com/hg/medcouple/file/default/medcoupl...](http://inversethought.com/hg/medcouple/file/default/medcouple.py)

I'm really happy how almost every Python statement has a nearly equivalent C++
statement. The only one I had trouble with was list comprehensions, but they
can be very nearly translated with C++ stdlib algorithms and a few lambdas
(kind of looks more like map and filter than a list comprehension, though).

The best part is that the C++ code runs as fast and sometimes faster than the
original C code I grabbed this from! (The translation path was C -> Python ->
C++.)

------
Animats
It's nice to have a "do this to all that stuff" FOR statement, at last.
Remember what it was like declaring iterators over collections in FOR
statements before AUTO? That was the original motivation for AUTO. But it's
much more useful than that. It becomes the common means of declaring local
variables.

~~~
catnaroek
You could always use `std::for_each` and Boost.Lambda, even before C++11.

------
wglb
Props to python.

However, what idea has C++ not accepted? It seems for it to be a "new"
language, it should at least say "no" to _something_.

~~~
smcameron
C++ says "no" to readability. Everything that has been added to C to create
C++ was added in order to help out the person writing new code. _Nothing_ that
was added to C to make C++ is there to help the person trying to read the
code. Though I doubt it was deliberate, much of what makes C++ different than
C seems actively hostile to the person trying to read the code.

~~~
jordigh
You really find

    
    
       auto x = new int[64];
    

harder to read than

    
    
       int* x = malloc(sizeof(int)*64);
    

?

If so, I think it's just a matter of habituation and imprinting. Whatever you
learned first is easier and everything else is hard.

~~~
smitherfield
Both C and C++ have readability issues.

    
    
      ((void(*)())exec)();
    

or

    
    
      template <typename T>
      struct value_type {
        typedef typename T::value_type type;
      };
      
      template <typename T>
      struct value_type<T*> {
        typedef T type;
      };
    

Six of one, half a dozen of the other.

------
byuu
> With range-based for loops, I often find myself wishing C++ had Python’s
> xrange function built-in.

Built-in to the language would be nice, but C++ is always extremely spartan
with its standard library functionality. Thus:

[https://gitlab.com/higan/higan/blob/master/nall/range.hpp](https://gitlab.com/higan/higan/blob/master/nall/range.hpp)

37 lines (without the boilerplate header stuff) and you have:

    
    
        for(int x : range(20)) ...;  //iterate from 0 ... 19
        for(int y : rrange(myvector)) ...;  //iterate from myvector.size()-1 ... 0
    

Also supports xrange's offset/stride arguments (it's pretty much exactly
Python's xrange object in C++.) And you can add support to do ranges over any
class anywhere, eg range(myvector) will pull the length from myvector.size()
for you with a simple overloaded function.

Timing tests in extremely critical loops (like convolution kernels eating 100%
CPU usage) shows no discernable performance impact at -O2 and above over the
traditional C++-style for(int x = 0; x < size; x++) style.

------
twiceaday
I'm still waiting on Pythonic iterators (e.g. coroutines).

~~~
cheez
[http://www.boost.org/doc/libs/1_60_0/libs/coroutine2/doc/htm...](http://www.boost.org/doc/libs/1_60_0/libs/coroutine2/doc/html/coroutine2/coroutine/asymmetric.html)

------
wmil
I'll never understand why some people insist on backslashes in paths on
Windows.

Windows APIs have always supported using forward slashes, using
"C:\\\path\\\to\\\file" is just an ugly mess.

~~~
Quiark
It doesn't work everywhere, 3rd party libraries would have trouble for sure
and I think I had some issues even with Windows or some highe-level Microsoft
API too.

------
ioab
those are amazing features I didn't expect C++ to have. I wonder if 'auto' is
similar to type inference 'var' in C#.

~~~
catnaroek
C# does _not_ have type inference:
[http://ideone.com/fmm92M](http://ideone.com/fmm92M). Nor does C++ for that
matter: [http://ideone.com/HzyM2E](http://ideone.com/HzyM2E). This is type
inference: [http://ideone.com/0Gv8fV](http://ideone.com/0Gv8fV),
[http://ideone.com/r9nHUF](http://ideone.com/r9nHUF). Note how the type
checker figures out the list's element type from how the list is _used_.

~~~
SamReidHughes
Note how that's an anti-feature.

~~~
catnaroek
Whether a feature is a good or a bad thing to have is each programmer's
subjective opinion. (But, of course, mine is diametrically opposed to yours:
typeful programming would simply be impractical if you had to babysit the
type-checker all the time.) OTOH, whether a language has or doesn't have a
feature, is a technical fact.

~~~
SamReidHughes
It's not subjective at all. Features are good or bad for productivity. Maybe
you have a different goal or have calculated the expected value differently,
but with a well defined purpose it's not a subjective question.

~~~
catnaroek
> It's not subjective at all. Features are good or bad for productivity.

While productivity can be more or less objectively measured in the long run,
the effects of a language feature on productivity vary from one programmer to
another. There's no universal basis for evaluating this.

> Maybe you have a different goal

My primary goal is correctness. I don't really believe in 80% solutions.
Having an incorrect program is just as good as having no program.

> have calculated the expected value differently

 _Very_ differently. My calculation is based on the following objective,
unquestionable, technical facts:

(0) Determining whether the internal structure of a program is consistent (in
the formal logic sense) involves lots of long, tedious and mostly
unenlightening calculations.

(1) Computers are very good at performing long calculations. On the other
hand, humans suck at it.

(2) Type structure, at least in sensibly designed languages, is the most
efficient way known so far to communicate the intended logical structure of a
program to a computer.

With type inference:

(0) The programmer only has to provide just enough information for the type
checker to reconstruct the logical structure of the program.

(1) If the pieces fit together, the type checker will tell you exactly how.

(2) If the pieces don't fit together, the type checker will tell you exactly
why.

