Hacker News new | comments | show | ask | jobs | submit login
C++11 and Boost - Succinct like Python (fendrich.se)
201 points by daivd 1604 days ago | hide | past | web | 164 comments | favorite



"you will notice that it is more or less as succinct as the Python code." ??? I hope that this person writes better python than this:

    const map<const string, const tuple<int, int, StrToStr>> TagDataMap {
      {"title" , make_tuple( 3, 30, stripnulls)},
      {"artist" , make_tuple( 33, 30, stripnulls)},
      ...
it will be succinct when the whole type can get reduced to "auto". Python would just do:

    TagDataMap = {
        'title': (3, 30, stripnulls),
        'artist': (33, 30, stripnulls),
        ...
Ord implementation is also crazy:

    int i = static_cast<unsigned char>(s[0]);
    return (boost::format("%1%") % i).str(); 
considering it doesn't even handle different string encodings.

I don't want to complain about C++. It's a great language and has its uses. But it's not succinct and it's far away from what scripting languages provide. Claiming otherwise is close to fanboyism. Not being succinct is not a bad thing. There are many other things that C++ does for you that Python doesn't.


I think you're being unnecessarily critical. The article begins with "I think that it is possible to write code in a style that is almost as painless as in a modern dynamic language like Python. I also think that is not so well known how much C++ has changed for the better, outside the C++-community. Hence this post."

The difference between make_tuple(...) and (...) is not significant compared to what it would have been before without list initializers at all and having to manually populate the map. That's something people who knew C++ of the past but not C++11 would be happy to see. The rest of the code is a great demonstration of these new features as well. That's the point: to demonstrate C++11, not undermine Python.

The article ends with some cogent criticism of C++. If this sets of your fanboy detector, you should turn it down a little.


But that's a completely different claim. I agree that C++11 is much nicer than old C++. I really like the improvements. I'd be glad to see that argument in his post instead.

That's not what he wrote though. The title is "C++11 and Boost - Succinct Like Python", the contents say "almost as painless as in a modern dynamic language like Python". That's what I can't agree with. There's still lots of supporting syntax that doesn't actually do anything for the end result.


The extra syntax is not there for nothing. It's adding type information. Thus allowing error checking or dispatching by type or optimisations at compile time. It is a cost in terms of syntax and readability but it's not for nothing.

So for correct programs the end result might be the same in terms of values. For buggy code and runtime speed that's not necessarily the case.


The extra code is there for a reason, but it's still there and it's not there in a language like Python.

Nobody would argue that C++11 code can be much cleaner than old C++ code. But to say it's almost as clean as Python goes to far, and isn't fooling anybody. So why not be satisfied beating old C++?


And this is where I decide the language is too far developed on the wrong foundation.

I cannot put up with type systems that don't have complete or near-complete type inference. I don't know why one would start a new project in a language that didn't support Hindley-Milner.


Just to elaborate on what danking00 is saying, the extra syntax in this case is not adding any extra information (to the compiler). The left hand type of the expression can be completely inferred at compile time, in this case. What that required syntax is adding is pain, but no gain (except for imperceptibly faster compile time).

In Haskell this would look like:

  TagDataMap = [
          ("title", ((3, 30, stripnulls)),
          ("artist", ((33, 30, stripnulls)),
          ...
Haskell will correctly infer that TagDataMap :: [(String, (Integer, Integer, String -> String)].

Ok, technically this is an associative list, not a Python dictionary, but it is a map and can be accessed like one. Hell, most people use dictionaries with less than 10 items, which are much slower than arrays most of the time


"the extra syntax in this case is not adding any extra information (to the compiler)." This actually isn't true in C++, because there could be many classes in scope that could take a list initializer like this. In Haskell, no literal with [] can "turn into" something besides a list, but that can happen in C++:

    vector<int> items = {1,2,3,4};
    int[] items       = {1,2,3,4};
These have different types, so how would C++ know which one you meant if you instead wrote:

    auto items = {1,2,3,4};
Again, in Haskell this isn't a problem because literals have essentially a single type. (Edge cases around integers and strings notwithstanding).

Edit: Just to clarify, in a Hindley-Milner system you could maybe get away with something like that, but everything you name in an HM system you must use, and that isn't the case in C++:

    void foo() {
      auto items = {1,2,3,4};
      return;
    }
I can then make two classes with list constructors:

    struct FooClass {
      FooClass(std::initializer_list<int> list) {
        cout << "Made a Foo!" << endl;
      }
    };

    struct BarClass {
      BarClass(std::initializer_list<int> list) {
        format_your_hard_disk();
      }
    };
Obviously there are consequences to choosing the right type, but the type of that value never leaks out of the function. Nevertheless, because side-effects can happen anywhere, even in a constructor, C++ cannot optimize that out.

This might be a convoluted example, and it may be flawed, but conjuring up others is not hard and demonstrates that C++ simply cannot ever have true HM type inferencing. Since the "real deal" is not possible, the language is complex and the standard is large, I would not expect to be able to live without annotations in C++-land. (Again, the FQA makes the horror of multiple non-orthogonal solutions to the same problems quite clear).


Your example,

    void foo() {
      auto items = {1,2,3,4};
      return;
    }
is the case of poorly designed syntax, in a truly Hindley-Milner system there is no expression which doesn't have a type. Now, we could add some syntax that screws that up, say:

    let v = I-HAVE-AN-AMBIGUOUS-TYPE in 0
But that's rather silly, isn't it? If a value isn't used then I would, personally, like my language to optimize it away. So why not ditch such silly syntax?

This is part of why I said that the foundations of C++ are too far-gone.

And this syntax isn't necessary to save on typing. In a language that supported syntactic macros (such as Scheme or Racket) we could write something like:

   auto items = my-vector-syntax(1,2,3,4);
which expands to something like:

    auto items;
    items.push_back(1);
    items.push_back(2);
    items.push_back(3);
    items.push_back(4);
If you're interested in true syntactic macros for non-sexp languages (though I do suggest getting over the parentheses, my color settings make them nearly indistinguishable from the background) look at Rust [1].

Actually, Rust also has type inference [2].

Hell, stop programming in C++ and start using Rust! [3]

[1] http://dl.rust-lang.org/doc/tutorial-macros.html [2] http://dl.rust-lang.org/doc/0.4/rust.html#type-system [3] http://www.rust-lang.org/


Having programmed Haskell for the last seven years (and C++ for zero) I have a fairly decent handle on what HM is all about. If all you had said is that the foundations of C++ are too far gone, there wouldn't be anything to discuss. But the foundations of C++ being what they are, there's no point being offended when unfixable things go unfixed. I love HM, but you can't just throw it in any old language simply because it's cool. The language's semantics need to allow for it, and they just don't with C++.


Correct, I do not suggest adding HM to C++.

I suggest using a HM language when you start a new project.


It's possible to do exactly that (a function that initialized a vector with its arguments) in C++ with variadic templates...

You might end up in trouble if you did something like

    auto v = vec(
                  vec(1.2, 3.4),
                  vec(0)
                )
not sure how well other languages deal with that.


"Hell, stop programming in C++ and start using Rust!" I plan to try to do that, when Rust has become more stable.


No need to downvote this; I'm an enthusiastic Rust follower and I as well recommend that you stay far away from Rust until it settles down a bit. Wait for 0.5 if you're adventurous, 0.6 if you expect some degree of feature-completion, 0.7 if you're fine with half-finished standard libraries, and even later if you expect a high degree of stability, ergonomics, documentation, or performance.


This is definitely true and demonstrates how competing design decisions can add huge complexity to a language. C++ made earlier design goals to allow heavily context-dependent overriding, which has side effects on what features it can add later.


Integers and Strings aren't edge cases. Their literals are polymorphic and typed.

C++ could also type its list initializers with some polymorphic type (similar to Haskell's Num) but didn't do so.

This is not inherent.


They're not worth discussing because they're a counterintuitive mess rather than a case study of the glory of HM. Maybe edge case isn't the right word, but they're definitely not something I would hail as a perfect resounding success.

There are no polymorphic literals in ML, just polymorphic math operators, which is enough of a blight on the standard that OCaml discarded it and forces you to use different operators for real and integer arithmetic. And there's only one kind of string in both MLs.

Haskell's Num hierarchy is troublesome. They traded usability for + with complexity for /. It's extremely unlikely that you could write a program in Haskell that does much arithmetic and have it build correctly on the first try without any manifest typing. This is one reason students of Haskell find things so confusing: type declarations are necessary at the top level simply because the extensions and complexity of modern Haskell break HM if you try using it everywhere. Also, the class system in there is not especially mathematically correct, which leads to the numerous replacement Preludes that try to do a better job but haven't caught on.

Strings are edge cases because they are not polymorphic unless you enable OverloadedStrings. Once you do, you will either replace the built-in string with something else (ByteString or Text) or find yourself in the same kind of trouble you'd be in with Num.

Let me be clear: I'm not saying that these problems are showstoppers. They're really minor annoyances once you're experienced, though they contribute to confusion for beginners. The point I'm trying to make is that you can't just drop HM into any old language and expect it to work. A greater point would perhaps be that all languages have warts simply because they're large, complex beasts (Haskell and C++ especially) and it's unproductive to point to a missing feature in one and demand some sort of perfected version of the other's.


> They're not worth discussing because they're a counterintuitive mess

Do you really believe Num overloading is a counterintuitive mess? I disagree completely.

> OCaml discarded it and forces you to use different operators for real and integer arithmetic

Which is pretty terrible.

> Haskell's Num hierarchy is troublesome.

Yes, but that's an orthogonal issue.

> This is one reason students of Haskell find things so confusing: type declarations are necessary at the top level simply because the extensions and complexity of modern Haskell break HM if you try using it everywhere

That sounds like FUD to me, a heavy Haskell user. Type declarations at the top-level are generally necessary to avoid the dreaded MR and for documentation purposes. Modern Haskell doesn't heavily use extensions that require type annotations on the top-level.

> Strings are edge cases because they are not polymorphic unless you enable OverloadedStrings. Once you do, you will either replace the built-in string with something else (ByteString or Text) or find yourself in the same kind of trouble you'd be in with Num.

It lets me use literals for Lazy Text, Strict Text, and String with the same syntax, which is nice.

My point is merely that giving a type to a polymorphic initializer is possible, and C++ chose not to.


The distinction between your point and mine is becoming miniscule, but I must defend my position on Haskell, as a heavy user myself. If Num doesn't seem to be a problem to you, it's because you supply top-level annotations that disambiguate it. Try removing all the annotations from whatever you did last week and see if it still compiles cleanly. I'd wager it doesn't. This isn't an issue in practice because we supply annotations in most cases, but don't be fooled: top level annotations, despite the rhetoric, actually are essential for modern Haskell programs to disambiguate. As for strings, the problem doesn't appear in practice because people mostly don't intermingle string types in the same module. That's what makes Num tricky; it's easy to find yourself with Ints and Integers together, wanting to divide them and get a float, and figuring out how to resolve these minor issues is significant for learners. Just ask my friends!


This is one of the files in a project I've worked on last week.

https://github.com/Peaker/bottle/blob/master/codeedit/Editor...

I removed all the top-level type declarations, and only one definition broke, because of the MR.

  showP :: Show a => a -> String
  showP = parenify . show
Once I removed the type declaration, I made it work again by adding a parameter to avoid the MR:

  showP x = parenify (show x)
and everything compiles smoothly.

Feel free to browse the bottle repo -- and try to build it without top-level declaration. Apart from a few functions in the entire project that use Rank2, you won't need any declarations.


Thanks for sticking with this conversation.

This is very good code.

One difference I see between our styles that may explain the differences in behavior we see is that you're quite meticulous about importing only the parts of modules you need, and you make heavy use of qualified imports. My style has been to import everything in case I need it later and only use qualified imports when absolutely necessary, and it must be creating the unnecessary ambiguity that I have to deal with. I will try to adopt your style and see if it cleans up my error messages, and I'll encourage my friends to do the same.


Even in Hindley-Miller type systems it is considered good practice to add types as documentation to top-level constructs (see Haskell). In Python it is also considered good practice to add argument and return type info in the doc string. In a dynamic language you would also have to add a unit test or two for cases for some of the things that the compiler can catch for you.

Looking at the complete picture makes a language with local type inference (like C++11) more or less as verbose as one with complete type inference.


> it is considered good practice to add types as documentation to top-level constructs

But with type inference your tools can do that for you (e.g. C-u C-c C-t in haskell-mode).


A good IDE can fill in the types in C++ too.


Is there a C++ IDE that can figure out the function signature after you have written something like

_ f(_ a, _ b, _ c) { YOUR; CODE; HERE; }

?


Wrong foundation? No. Foundation you don't want? Sure.

Try to remember that Hindley-Milner is very hard to do outside of functional languages like ML and Haskell.


Yes type inference is harder with C++'s language design (OO comes to mind as a particular problem), thus my claim that it's the wrong foundation to build on. Of course, people like challenging problems and are working to bring more type inference to OO languages [1].

Furthermore, I assert that this truly is the wrong foundation. For new projects that must have OO, Scala provides local type inference and object orientation. If you're really hurting for some manual memory management, look at Rust [3] or Habit [2].

If we can recover the features we love on a new foundation that provides new features like type inference or memory-safety, then we've found a better foundation, IMHO.

[1] http://www.cs.ucla.edu/~palsberg/typeflow.html

[2] http://hasp.cs.pdx.edu/habit-report-Nov2010.pdf

[3] http://www.rust-lang.org/


This is what caught my eye too as being really ugly and hard to read.

  const map<const string, const tuple<int, int, StrToStr>> TagDataMap {
      {"title" , make_tuple( 3, 30, stripnulls)},
Why can't the compiler figure out the types involved in this map structure itself? The user-defined functions are declared above, make_tuple will be declared in some library, and the others are string/int literals.


One thing you cannot do is infer `const`ness. This isn't a problem in Hindley-Milner systems because variables are not assignable--they're all `const`, all the time. Literals are inherently immutable but when you assign a C++ variable a literal value, sometimes you want a const variable and sometimes you don't.

I don't think C++ can figure out the map<..> part simply because lots of things could have list initializers that accept lists of 2 item lists. C++'s overloading and implicit conversion conflicts with perfect type inferencing. This is an example of the kind of thing the FQA talks about, where several of C++'s issues collide to produce counter-intuitive behavior.

I do wonder if you could get away with this:

    const map<const auto, const auto> TagDataMap { ...
I don't have access to a C++11 compiler from where I am to find out though. I am particularly unclear on the interaction between `auto` and other aspects of a type declaration--I don't know if you can nest `auto` like this deep inside some other type declaration. I don't see why you couldn't, but wouldn't be shocked either.


You can't do it with that syntax. 'auto' never infers constructor calls, just takes the static type of an LHS to capture a temporary into. You'd have to use a non-constructor template function that inspects the type of its argument to guess what kind of map you want.

  const auto TagDataMap = map_initializer( { "foo", { 1, 20, func }, ... } );
where map_initializer would be a template function that inspects the typedefs of its std::initializer_list<T> argument and generates an appropriate map.

In real C++ code, this would almost never be what you actually want, though, as the types in an initializer list in C++ do not imply the types being initialized, they are parameters to a constructor to be determined elsewhere.


Thanks for clearing that up. I'm just starting to play with C++11 and have some trouble getting to a supporting compiler from work.


It would help a lot if the keyword 'const' were replaced by a punctuation character or shorter simpler keyword like 'val'


that's not entirely true, take make_tuple for example, this doesn't add anything except verbosity (by comparison to Python)


> The extra syntax is not there for nothing.

You could say the same thing about everything old C++ compelled you to type. Or old COBOL, for that matter.

I'm more interested in what the C++ memory allocation style does to verbosity. Memory-handling styles are often more important than the traditional paradigms (functional, OO, etc.) in determining what's reasonable/pleasant to do in a given language.

https://sites.google.com/site/steveyegge2/allocation-styles


But aren't all allocation styles basically the same once you get GC? (I guess unless you broaden the scope of the word, and let it describe the lengths you go in Haskell to avoid allocation at all, aka triggering stream fusion and automatic deforrestation in general.)


There are other issues: Go has stuff like slice for efficiently allocating array data more finely than a Java array, perl has autovivication.


Yeah, I agree with the claim that this is not in the same realm of readability (seriously, just consider for 10 seconds what an at-scale C++11 codebase looks like versus a scaled up python code base. But I understand why they're different and I can't get enough of the statically typed kool-aid lately.


I am picturing Python drowning in isinstance checks to avoid constant crashing every time a new function is added.


I'm just imagining how many nights in a row I've committed more lines of code than exist in my project in a snapshot because it's so damn confusing, interconnected, and concurrent. The thought of doing it in a dynamic language is downright nauseating.


You misunderstand the purpose of ord. The mp3 metadata uses one byte to encode an integer between 0 and 255, which represents genre. It is not a string.


I did misunderstand it. But then the first point is even stronger. Those two lines are definitely not as short/clean/readable as "str(ord(s[0]))"


No, because they have some extra tokens in the syntax (mostly to deal with strong typing). But they're semantically identical, which I think was the author's point. You declare the data instead of assembling it, and you do it in the same order and in the same manner. There's more typing but the idiom is the same.

This is something that I think a lot of people miss about modern C++. They make the mistake of assuming you still program it using C idioms, when often you don't have to. They assume you have to "manually manage memory" when obvious and simple RAII techniques have basically obsoleted that for probably 90% of cases.

It's just not like that. If you have a problem that is "easy to express" in Python/Perl/Ruby/Javascript it is expressable in essentially the same way in C++.

The gotcha to my mind is more that the C++ environment is unforgiving -- a novice python programmer making a syntax error generally bangs around and gets it to work, maybe with some odd thought errors someone else will have to clean up. A novice C++ programmer is lost until they find an expert to explain the problem. So I don't think web engineers need to worry about losing their jobs to C++ hackers quite yet.

But at the same time, the intuition that C++ is "hard" is mostly wrong. A true expert can solve the same problems the scripting languages do, arriving at essentially equivalent code, in very similar time and with very similar (potentially higher due to typesafety) quality.


A true expert can solve the same problems

That's true, but I think ignores the main problem with C++. I'd say I know about three times as many things about how C++ as I do about how Python works, but I still feel like I'm more of an expert in Python than C++ just because C++ is such a stupendously large language now.


To be fair, I think that's absolutely correct and tried to point it out. It's a legitimate reason for sane people to choose Python over C++.

Nonetheless, the really are "true experts" in the world. These people tend to write most of the really important software, and they tend to work with each other on teams where "everyone" (or nearly so) is another expert. In those environments, I think a very strong case can be made for C++ as the best overall choice.

The real point is that simplistic arguments like "C++ isn't as clean as python" are off target.


I write a lot of C++ every day. I work with a lot of people that write C++ all day every day. I don't know anyone that would consider themselves an expert in C++. The language is just too big.


I agree, it is much uglier. Part of it is that Python thinks a character is unsigned, whereas C++ thinks it is signed. This arbitrarily works in Python's favor here. Part of it is that Python just handles this better.


I suppose he wanted to showcase boost::format. You can do it simpler with std::to_string:

    return to_string(static_cast<unsigned char>(s[0]));


For ord he could use std::to_string

    string ord(string const &s) {
      return to_string(unsigned(s[0]));
    }
He skips all the const-refs...


Yes, to_string would have been better

The C-style cast is shorter, but less safe, so you should not use it: http://stackoverflow.com/questions/1609163/what-is-the-diffe...

Yeah, I couldn't quite decide if I should include const refs. On the one hand, it is idiomatic, but on the other hand I didn't want to clutter the code with something that is just an optimization on paper.


I know the C-style casts are bad (although this is more of a C++-style-C-style cast. (unsigned) would have been the C-cast. But still bad). But in this situation it should be safe and for use in an example more readable.

btw. Please fix either the size of your code or the margins of your blog. You use only half the screen width but I still have to scroll sideways in the code listing.


How can a C-style cast from char to unsigned possibly be "less safe", and how could a different style of cast be more safe?


In this case it obviously doesn't matter, but in the general case it is better to be explicit on which type of cast you are doing. That way the compiler can statically stop you from doing stupid things. Read the link I gave.


Is it too late for me to bang my Go drum?

http://play.golang.org/p/53jSv32wSF

(You can't access the filesystem on the playground, so it doesn't run.)

* Look at that error handling. Mmmmhmmm, clear and explicit. If something goes wrong, I'll know about it.

* Apart from, of course, errors that I ignore, such as when converting the year from 4 chars to an int (line 54). I don't care if I can't parse that.

* Note the difference (lines 54, 56) between parsing the track (which is a byte), and the year, written as 4 digits. The byte can just be cast to an int, the string needs to be parsed by the strconv package.

* I have a little bit of defensive programming in the form of a panic(), which would tell me if something that I thought was impossible has happened.

* defer on line 19 makes sure the file closes if we managed to open it, regardless of when we return.

* type casts are explicit, even from int to int64 (line 20)

* I don't specify which interfaces a type implements, the compiler handles that all for me.

* Parse() returns map[string]interface{}, which allows me to store anything (in this case just ints and strings) in a key-value store.

* A type-safe printf! "%v" (line 67) uses reflection to look at the type of the argument and deduce the natural format. So I can pass it an int or a string and it works :)

* On line 86 I pass this weird new type to a printf function and it goes ahead and uses the String() method that we defined. If we hadn't defined that we'd get a printout of the type, which in this case would be the 128 bytes we read.


Having this all the time seems really verbose compared to exceptions:

    if err != nil {
        return nil, err
    }


At the end after line 24 do we need another line of code that reads data into the newly created byte array? If so, that says a lot about the readability of the code since I'm not a Go developer...


I'm not sure what you mean. Could you be more specific?

Line 24 makes a byte slice, line 25 populates it with data read from the file.


Turns out, my iPad was truncating the whole thing at line 24. Sorry, nevermind!


It's succint, all right (well, sort of).

But Python's major strength is readability, even more than coinciseness, or better, to provide both at the same time.

C was born as a terse language, sacrificing readability for coinciseness (the original examples in K&R are incredibly succint, almost elegant, but far from readable). I can't see many improvements in C++ (a language that arguably has worse coinciseness than C, without gaining in readability in the process)in that regards, even with the new version.

I work with C/C++ and I could easily understand a Python script even when I wasn't that familiar with the language, the same cannot be really said for that code in C++.

BTW, I'm not bashing C++, that has a lot going for it,just not readability and coinciseness.


I call shenanigans. That's true of simple scripts, I'm sure. But you can't seriously claim to me that you can understand decorator idioms, iterables or list comprehensions without a deep understanding of the language. What you say might have been true for Python c. 1998, it certainly isn't true today.

Broadly: reading code is just hard. It's much harder than writing code. There are no non-trivial codebases that can be considered "easy to read" (if you think there are, then you're fooling yourself), and the choice of language makes only mild difference.

(edit: I'm amused at the multiple people who had to jump in to explain "decorators aren't hard!" instead of responding to my core point. First, I know what a decorator is. Second, no one who isn't a reasonably proficient python programmer is going to have any clue what @classmethod does or why all the old code doesn't break without using it. It's non-standard, language-specific syntax in exactly the same way that an STL iterator is, and the only reason you think one is easy and not the other is because you know Python and not C++.)


Here's an example: A co-worker looked at a function I wrote that converts a table to a tree, and he said to me, "I thought Python was supposed to be readable, but I have no idea what this is doing."

See for yourself: https://gist.github.com/3988350


I agree with your co-worker. The indirection is kind of annoying to follow to be completely honest, so if your co-worker isn't proficient in Python I can understand how this would seem impossible to sort out.


Manually capturing free variables via:

  def __init__(self, x, y, ..):
    self.x = x
    self.y = y
Is so tedious and DRY-violating.


This is true. I'd prefer scala-style constructor syntax - though I'm not sure how you could retain python's wonderful symmetry between constructors and methods, and the distinction between allocation and initialization.


I don't know Scala, but the Erights language does something like (pseudo-syntax):

  def Circle(center, radius):
    circumference = 2 * pi * radius
    return object:
      to getCircumference():
         return circumference
      to ...
The "self" is the lexical scope. Simple and more effective than Python's method, IMO.


Ironically, I didn't understand Python's list comprehensions until I learned about Haskell's version (which is the original source of Python's), along with the explanation that Haskell's list comprehensions are taken straight from mathematics. Indeed, Haskell's version are almost ASCII-art mathematical set notations.

Even now, I still like Haskell's

   [fn(x) | x <- myList, x < 5]
over Python's equivalent

   [fn(x) for x in myList if x < 5]
Of course, I'm a mathematician, so that probably explains everything about my convoluted path of understanding these things, and my preferences to this day. :-)


List comprehensions are not deep magic. If you use SQL as your frame of reference, it's actually pretty easy to pick up. They also add a hell of a lot to readability once you know what it does.


Your critical phrase phrase being "... once you know what it does.". I didn't say it was hard, I said it required learning and experience to use. The OP claimed that non-expert python programmers could read it without that experience. The same is true for virtually all of C++ too.


That is a given, I agree.

But a complex program written in C++ (or using advanced C++ constructs) will scale much worse than a Python one, in my opinion.


I don't think that's necessarily true. In my opinion, both languages can be used by novices for simple scripts and by experts for complicated tasks. However, C++ seems to be getting easier for simple tasks (lambdas, initializers, auto, alias templates, etc.), while Python is getting more complex/powerful (metaclasses, exception tweaks, new I/O and function annotations — just to name a few).


RuntimeError: cannot add String and Integer


Absolutely agreed: to me, Python is a local maximum rather early on the readability vs. complexity graph. I think as complexity increases, an explicitly-typed language becomes much more understandable because local type annotations require less context switching to reason about when compared to an implicit language.


List comprehensions read like what they are (except for the stupid backwards order if you have multiple 'for's).

Decorators are pretty opaque; idiomatic python makes minimal use of them, and uses them only for things that don't change the meaning of the method inside. I've never used @classmethod and hope to never have to. I don't think it's fair to compare to an STL iterator, which is something you're encouraged to use as much as possible.

I don't know what you're talking about for iterables; aren't they obvious?


The iterable point was that the syntax is obvious but the behavior is not. They "look like plain data" sometimes, and some times they don't; and the only way to tell the difference is to know the characteristics of what you are iterating over. They're the same kind of too-clever-by-half design that C++ tends to confuse people with. People who know it love it, and people who don't think it's voodoo.


Decorators aren't difficult to understand. It tells you that the function is modified in some way. Iterables and list comprehensions are also easy to read, and I do not have a deep understanding of Python at all.


Right. I saw my first decorator before I knew what one was, and just from looking at it I knew enough to ask my friend "This thing looks like it's modifying the function somehow, what is it?".


Python, like all dynamically typed languages, has no readability once you start using multiple modules. Static types are important for declaring interfaces between components. I have pretty much written off Python, particularly now that Go exists. I've wasted too many hours tracing down data types in complicated Python programs, usually resorting to printing out the types at runtime to decode them. The problem with Python: it is great for simple programs, where the effort to writing the simple program is much less than the comparable program in C/C++/Java; but simple programs often grow into complicated programs, and then the effort of maintaining Python far exceeds the effort to maintain C/C++/Java. Go, on the other hand, is pretty nice for writing a simple program, and can then scale into a complicated program.


That is a strawman requirement.

It is sufficient if the C++ code I write is reasonably readable to a fellow C++ programmer. I really don't care if it is not readable to a casual observer.

It is nice that Python code is readable to even to non python people. But it is not a requirement for every language.


Well, I liked C++11 a lot. But now I think I'll replace it with Go whenever possible. I don't really mind so much the verbose syntax. What matters is how difficult it is to write efficient code.

I recently rewrote one of my C++ projects(which uses C++11 features and Boost.asio) in Go. It took me half the time, less than 2/3 lines of code compared to the C++ version. This is expected. But most surprisingly, despite that I tried my best thinking about how to make it as efficient as possible in every detail when writing C++ version, and that I'm quite new to Go, the Go version still achieves nearly 100% higher throughput than C++ version.

Maybe my C++ skill sucks. But I guess I'll just let it suck.


Go is certainly a fine language. One common instance where I would prefer C++, though, is when you are writing libraries for others to use. A language that runs on a VM can not easily be used as a library inside a language that uses another VM. You cannot easily write a library for Python in Go or vice versa. If you use a VM-less language like C or C++, your work can be used (through various wrappers, like SWIG or that Apache thing) by everyone.


If you want libraries for others to use, just use plain C. C libraries are trivial to use in almost all languages (from Ada to Yorick).


Doesn't Go compile to machine code as well without a VM in between?


Go has garbage collection, which I think some people confuse with using a VM.


It has a runtime statically linked. All linking is static and std call interfaces are not exposed so you cannot expose libraries to other language bindings.


I have worked on a large C++ program that uses Python for UI and some scripting components. I don't see why it would be harder for Go to use Python. Also, Lua is used everywhere.


Hey, hey my story is like yours. My go-to project (ha) is one that I've written in Boost.asio though it was back when C++11 was still C++-0x, but I also switched to Go for the project and saw gains in how easy it was to write efficient code. To be fair, threading is much easier now, but then again, it's still threading...


It's not essentially threads since multiple goroutines are mapped onto one thread (or more if set). It feels like threading but you could just use it as much as you need since it doesn't bring as much overhead as a thread :-)

Check this out: http://en.munknex.net/2011/12/golang-goroutines-performance....


Sorry, that was my point. The new concurrency stuff in C++11 still isn't the same as go's goroutines. Certainly my application would not work very well if a goroutine mapped to a thread. :o


Aha, I got you now :-)


What I find with C++ is that there are many languages inside it. Put 5 C++ programmers in a room and you'll probably end up with 6 different styles.

That the language may have some Python-esque tendencies (given very particular language, compiler and library versions, and a depth of knowledge not required in the Python equivalent) doesn't seem that interesting. Especially when you consider that 9/10 C++ programmers you meet don't actually code in that style and don't intend to.


  cout << join(pm | transformed([](propMap::value_type pv){...
I'm sorry, but as "succinct" as it is, this is basically an unreadable bullshit. Reminds me strongly of a macro abuse in C.


It is only unreadable bullshit the first time you read it. If you use ranges, filtered and transformed for these kinds of tasks, it looks idiomatic.


Yep. It looked idiomatic to me -- and I don't even know C++11! I don't care for the unqualified access to join and transformed, though, even if it is more Pythonesque.


you might not like the syntax of this particular boost lib, but it bears no relation to macro abuse in C. For one it is strongly typed. Of course it is unreadable if you don't know C++ or boost range. But it is not more complex than a python map or filter with a lambda, if you don't know python.


It is visibly worse than python. '|' - I thought that was bitwise or? - rather than an (admittedly technical) English word like 'map' or 'filter' (and '<<' has the same problem). 'transformed([](pv){...})' may be necessary for compatibility, but the brackets are harder to follow than 'transformed(lambda pv: ...)'. And the type declaration as "propMap::value_type" is just noise - it doesn't tell the reader anything about what the actual type is, and it isn't necessary for static typechecking either (it would be optional in a modern statically typed language e.g. Scala).


This is neat and C++11 is pretty exciting, but one thing that C++ doesn't need is the further propagation of tuples into non-generic code. Requiring make_tuple instead of allowing shorthand was the right decision.

Tuples in python are a reasonable tradeoff between not wanting to declare anything and the hassle of anonymous structure. This doesn't apply C++ where the equivalent is the POD struct:

  //In the olden days we could not initialize a map inline like this
  //Key -> metadata mapping. Unfortunately strutcts cannot be declared
  //inside a template declaration.
  struct TagData { int start; int length; StrToStr mapfun; };
  const map<const string, const TagData> TagDataMap {
    {"title"   , { 3,   30, stripnulls}},
    {"artist"  , { 33,  30, stripnulls}},
    {"album"   , { 63,  30, stripnulls}},
    {"year"    , { 93,   4, stripnulls}},
    {"comment" , { 97,  29, stripnulls}},
    {"genre"   , {127,   1, ord}}};
Creating a named struct pays off when it's time to use the Map, no extra locals or tie() needed to write clear code:

  //for loops over collections are finally convenient to use.
  for(auto td : TagDataMap){
    //C++ created a horrible precedent by making the data type of
    //a map pair<K,V> instead of struct ValueType { K key; V value; };
    auto tdd = td.second; 
    ret[td.first] = tdd.mapfun(sbuf.substr(tdd.start, tdd.length));
  }
http://liveworkspace.org/code/bcd52515fb7161858e974b7ff3c0aa...


Yes, of course a POD is better, but that would be cheating, since this is supposed to show that you can do the Python stuff, including tuples and tuple deconstruction, just like in the linked Python original.

Perhaps I should have mentioned that, though.


This post shows that C++11 is actually quite a comfortable language. Programming in C++98/03 was often a necessity for me (good for making performant cross-platform software), while I disliked the language quite a lot. The introduction of type inference, closures, range-based for loops, and uniform intialization makes many things that used to be tedious much simpler.

That said, I guess we have to live with C++03 for many more years. E.g. in one application that I co-developed, we use a library that can currently only be compiled without a substantial amount of effort on Visual Studio 2005 or 2008. The DLL compiled with older versions is not compatible with 2010 or 2012. So, we are basically stuck in 2005 or 2008 land for now.


Great post, it's always fun to see the cutting edge of C++ revealed. It's such a strange land. :)

To me, this post would have been 10X more interesting if there was some elementary benchmarking included. If it's about the same speed as the corresponding Python code, which I'd bet is easier to write for more programmers, then I don't quite see the point being as clearly proven.

If it's 100 times faster (or whatever), then it gives more credibility to the idea of writing code like this in C++ to begin with, and to learning all the new language features that makes it safe while being that much faster than Python.


I wrote this post.

The program is I/O-bound, so the only speed improvement comes from not having to start up the Python interpreter.

If the task was CPU bound, you would get a great performance boost (sometimes 100x over CPython), but that is well known.

There can be other reasons than performance for writing in C++. Used well, the strong static type system can catch many bugs. I suspect (but I cannot prove) that it could be almost as good as Haskell.

I use Python for most things, though, so I don't really advocating switching to C++ for every little scripting task.


I do both python and C++ in my job. They're both great at what they do IMO. Often, they are a good match for each other; several of the boost libs have been designed similarly to python equivalent. boost python also makes writing python extensions very easy.

For other reasons to write in C++ (or any static typed lang), I'd add type dispatching: calling different methods based on type. I also hate having a typo in a method name throwing at runtime.

I also often convert python implementations to C++, often using boost, usually for performance in one part of the code. You can quite often get the same implementation in a similar number of lines, especially since C++11 with boost.


It won't be as good as Haskell for memory safety. For example, this program, using only C++11 idioms, crashes:

    #include <iostream>
    #include <vector>

    int main() {
        std::vector<std::string> v;
        v.push_back(std::string("Hello"));
        v.push_back(std::string("there"));
        for (auto ii = v.begin(), ie = v.end(); ii != ie; ++ii) {
            v.clear();
            std::cout << *ii << std::endl;
        }
        return 0;
    }
Preventing this sort of thing requires strong guarantees about aliasing (to ensure that "v" can't alias the vector being iterated over), which the C++ type system can't help you with.


“C++11 idioms” include a preference for immutability, which leads to natural factorings. You don’t have to worry about things like iterator invalidation that way.

    #include <algorithm>
    #include <iostream>
    #include <iterator>
    #include <vector>
    using namespace std;

    template<class T>
    void print(const T& v) {
      copy(begin(v), end(v), ostream_iterator<string>(cout, "\n"));
    }

    int main(int argc, char** argv) {
      vector<string> v{"Hello", "there"};
      print(v);
    }
Also, Haskell is only so safe—at work we’ve taken to rejecting non-total functions in review, because they cause more hassle than they’re worth. By non-total, I refer more to “error” than non-termination, of course.


"const" doesn't help you in the presence of aliasing:

    #include <iostream>
    #include <vector>

    template<typename T,typename U>
    void print(const T& v, U& w) {
        for (auto ii = v.begin(), ie = v.end(); ii != ie; ++ii) {
            w.clear();
            std::cout << *ii << std::endl;
        }
    }

    int main() {
        std::vector<std::string> v;
        v.push_back(std::string("Hello"));
        v.push_back(std::string("there"));
        print(v, v);
        return 0;
    }
In general there are lots of ways to get non-const access to const data. You'd need a sophisticated form of whole-program alias analysis to eliminate the unsafety here. (Even if you didn't have the second "U& w" parameter, there are other potential ways that "print" could get non-const access to "v"; global variables, TLS, by accessing a member of "v", etc.)


I don't disagree about aliasing, but a print function shouldn't be taking ref to a non-const anything anyways, it should have no need to mutate the vector. You can't clear through a ref to const. Your general point holds but I would say I have probably written/found very few aliasing bugs in C++ over the years, it just doesn't seem to arise often, perhaps your experience differs. Alas there are few ways to prevent dedicated folks from writing really ill-advised code.


I see what you’re getting at. Still, if you do the sensible things—immutability, encapsulation of shared state, &c.—then you’re most of the way to safety. I would that C++ made it easier, though; that’s the real issue.


That does not look like C++11. You don't use initializer lists. You don't use new-style for-loops. I'll give you that those don't add much to safety, though. A functional style with Boost ranges, adapters and algorithms do, however. I clearly write in the post that you shouldn't use for-loops. Use ranges and pipes.


I don't see how Boost algorithms provide the aliasing guarantees you need to avoid invalidating iterators. It's a very difficult problem. Even if you use a library to pipe instances of your custom string class to cout and you trust that library, the method or function that sends your custom string class to cout could perform arbitrary mutations, which would invalidate the iterator.

For example:

    #include <algorithm>
    #include <iostream>
    #include <iterator>
    #include <vector>

    class mystring {
    public:
        std::string m_s;
        mystring(const std::string &s) : m_s(s) {}
    };

    std::vector<mystring> v;

    void operator<<(std::ostream &out, const mystring &ms) {
        v.clear();
        out << ms.m_s;
    }

    int main() {
        v.push_back(mystring(std::string("Hello")));
        v.push_back(mystring(std::string("world")));
        std::copy(v.begin(), v.end(), std::ostream_iterator<mystring>(std::cout, "\n"));
        return 0;
    }


D'oh, it (of course) makes total sense for this to be I/O bound, I didn't read the code too closely or I hope I would have figured out that much. Thanks for the clarification.


The simplifications are nice, but the problem is that the language still carries all of the heavy syntactic machinery that makes it happen, and there's nothing that prevents people from dipping down into it.


Especially if the benchmark measured programming and debugging time.


No thanks. I'll use D if I want a ton of features for free. This is not beautiful code. I could work around it I guess because I've seen much worse coming from C++ but it's really not my ideal of C++. But this is a small example, the source code of the STL offers much more madness. Not to mention the compiler messages you get for using templates...


I know c and python, but not c++. While reading the code section, i totally thought this was a wonderful satire of c++


But that is sort of the point. You should not write C++ like C. If a C-programmer thinks your code is nice and readable, you are not using C++. The C-stuff was just added to lure C-programmers over.


Equivalent code written in D would look much more like the Python code than this.


Periodically throwing some new ingredients into old soup to freshen it up can only extend the life of leftovers so far. There comes a time when, even with a handful of fresh veggies, it's no longer good soup. You need to toss out the leftovers, scrub the kettle, and start a fresh batch.

You will still need C++ for dealing with all the legacy C++ out there, but if you are starting a new project, you ought to be able to get the small, fast, efficient runtime advantages of a legacy monster like C++ from a much smaller, simpler language with a modern standard library.

Maybe it will be Go, but even if not, we need a simple, safe, productive language with modern features pre-installed (unicode strings, safe arrays, lists, maps) and a modern standard library that statically compiles to small, fast, native executables.

C++ with its "if you use the most recent 10% and pretend the old 90% doesn't exist, it's a great language" ethos is not what we need for new code.


I keep wishing for this magical language to manifest, like a C++ compatible Boo. I only wish I were capable of building one myself.


I've been using both C++11 and Python lately. While the new C++11 features add a lot of value, I still find it frustratingly verbose for some things. For example, finding an element in a collection:

    std::string type = "foo";
    auto it = std::find_if(
        channel_widgets.begin(),
        channel_widgets.end(),
        [type](const std::shared_ptr<Widget> &w){
            return (w->getType() == type);
        }
    );
    if (it == channel_widgets.end()) {
        std::cerr << "widget not found" << std::endl;
    } else {
        std::shared_ptr<Widget> target_widget = *it;
        std::cout << "widget found." << std::endl;
    }
versus (for example):

    type = "foo"
    matches = [x for x in widgets if x.get_type() == type];
    if matches:
        target_widget = matches[0]
        print "widget found:",target_widget
    else:
        print "widget not found"
I may be missing out on some C++11 feature for doing this better. (Or even some Python feature for doing this better!)


"I may be missing out on some C++11 for doing this better"

First write a function that searches an entire range with a predicate, so you don't have to write the .begin() and .end() anymore. That's just applying DRY. Then you could write a helper struct that wraps an iterator and the end iterator of the range searched, so you can give it an operator bool (). Return this struct from your find function and you get code like this:

  auto result = my::find_if( channel_widgets, predicate );
  if( result )
    do_stuf_with_result( result.it );
  else
    error();


Can't comment on C++11 but the Python version is better written as:

    type = "foo"
    try:
        target_widget = (x for x in widgets if x.get_type() == type).next()
        print "widget found:",target_widget
    except StopIteration:
        print "widget not found"


You can also use the `next(x for x in ...)` form where you can even supply a default value.


Um... maybe it's verbose because you're trying to be too clever? :-P How about

    auto it = channel_widgets.begin();
    while (it != channel_widgets.end())
      if (it->getType() == type) goto found;
    // error
    found: // whatever
I kinda hate myself every time I write such for-loop, but it's still more succint than any variant of find_if. Generally, I use "trivial" STL algorithms (find, find_if, for_each, ...) only if I can reuse the functor several times. Otherwise it's not worth the hassle.

PS: you can do it also without goto... use "break" after if, and after while you write

    if (it == channel_widgets.end())
      whatever; // not_found


A similar approach occurred to me as I was writing that example. (Using a range-based for loop, break on found.) I guess I'm mostly scratching my head trying to figure out find_if's place in the universe.


boost::range can makes a bit less verbose:

    std::string type("foo");
    auto range = channel_widgets | filter([type](const std::shared_ptr<Widget> &w) { 
                                              return w->getType() == type;
                                          });
    if (range) {
        std::string target_widget = *range.begin();
        std::cout << "widget found." << std::endl;
    } else {
        std::cerr << "widget not found" << std::endl;
    }


The only point that finds me in disagreement is the need for a code standard that decides well on which parts should be used and how. I don't understand: why limiting ourselves? Why choosing such a well-tested instrument and choosing NOT to use some of it?


Some parts exist either for backwards compatibility with existing code or to serve very narrow use-cases and should be used sparingly or not at all in new development.

There's also a ton of redundant libraries and language features, resulting in a lot of decisions being arbitrary choices between equivalents. Having the same decision made throughout the code makes everyone who has to read the code's life easier.


It is because C++ is a large language and it has some features (e.g. macros) that can be horribly misused.


I think people who jump on the "C++11 is as good as Python/Ruby/Javascript/other-language-of-the-week" bandwagon are missing the point, and that is that limitations are a good thing. Can C++ be made as concise and easily-readable as one of the above languages? I wouldn't doubt it.

C++ can be made to do lots of things, and it doesn't achieve that by being elegant. It achieves that by trying to do everything possible under the sun. Because of that, C++ is and always will be much more complex than the other languages.


Is there really such a bandwagon?


Yes, it apparently has. The largest problem I see in this new trend is portability. Certainly, if you have full control over the environment and require everything to be bleeding-edge you are fine. If not... Well, just have a look at boost source code and you will see how concise nested workarounds in #ifdefs really are.

If your environment is solely your own and you don't care about having your users build something, fine.


Is it possible to build Boost for iOS with c++11 enabled in llvm-clang?


On iOS 5 and newer (required for libc++), using clang 3.0 and newer (required for decent c++11 support), yes.


Can you explain how, or point me to a resource describing how to do it?


In other words: C++11 boosts Python :-)

I think only a hardcore C++ developer would claim that the author's sample is "succinct". Honestly, C++11 is still far behind the easiness of Python (or Scheme), even with Boost.

Funny, a decade ago Ada 95 (the "military" language for high-critical applications) looked like a monstrous over-designed beast when compared to C++. Today Ada 2012 looks elegant and even "small" when compared to C++11. How times have changed :-)


And here goes your productivity - the real killer application for your link times.


I work with C++ daily and it still has many rough edges:

* Horrible error messages

* Even simple programs take ages to compile due to massive header files.

LLVM/Clang help on both fronts but it's still quite difficult.

D2 seems much more promising if you can do without the libraries.


And then you do an accidental implicit conversion from the iterator type (const pair<...>) to the function type (pair<...>), return a reference to one of the fields and BOOM, everything explodes.


The changes to C++ are too little, too late. These changes should have been made years ago, and C++ has lost momentum and credibility. Is anyone comparing Python to c++? nope, only the other way around.


Too little, too late? There's no zero sum game here. Perhaps you're conflating Internet-popular (which doesn't mean shit) with useful?


Internet popular means libraries on github and good tutorials, means useful.


Are you suggesting C++ lacks available libraries? Because for all of its faults, lack of libraries is not one I've run into.

Many desktop and server applications are still written in C or C++, including the Web browser I'm writing this on. Reports of their deaths are exaggerations.


Unfortunately, many programming tutorials are written by an intermediate for a beginner. This means they can only take you so far. Thankfully, C++ is so old that there are quite a few superb books on both the language (and it's many pitfalls) and applying the strengths to build large scale, maintainable programs.

I'll take that over a tutorial any day.


C++ does not need the credibility. Hey, in what language is your Python compiler written in C/C++. When performance is the criteria C++ will shine. The world runs on C/C++, this comes from a C# developer.


CPython is written in C, not C++. Two very different languages.


>C++ has lost momentum and credibility.

Could you expand on this a bit? I'm not a C++ guy, but I was thinking of picking it up. By what metric do you mean it has "lost credibility?"


I think this comparison is completely misplaced. A language like C++11 simply doesn't even aim at being succinct like Python.


Succinct - I don't think that word means what you think it means


Also, s/Succinct/Python/


Nice try, Bjarne. I'll stick with Python :).


I hate to do it (who am I kidding, no I don't) but the battle in this thread for static typing, succinctness and an EASY language that is easy to wrap your head around... if those are things that make you happy to program in a language, please give Go a shot. Here, take 10 minutes: http://tour.golang.org


My eyes just exploded! ;-)


I find C++ much easier to read than Python. Here's how to reverse a string in C++:

string s = "string";

reverse(s.begin(), s.end());

And here's how to reverse a string in Python:

s = "string"

print s[::-1]

In my opinion, the C++ version is far easier to read and just makes more sense. Edit - This is just one small example, however, I find it holds true for the entire languages in general. Also, I do a lot of Python and C++ systems programming so I use both frequently and while I prefer C++, I think Python is the best scripting language available today.


Or you could use the "reversed" built-in function:

  s = "string"
  reversed(s)
It also has the plus of creating a generator, saving memory from a new array allocation.


`reversed(s)` returns an iterator. The much slower equivalent of `s[::-1]` is `''.join(reversed(s))`.

It's odd how Python has list.reverse() but not str.reverse(). Probably a conscious design decision sometime ago.


list is mutable while str is not. So reverse() should return a new copy, but it would duplicate the functionality of s[::-1] (which is quite idiomatic, although a bit obscure)


How many other languages do you know? You sound like you know C++ really well and sort of know Python, and aren't clearly distinguishing between abstract readability (as ill-defined as that is) vs. how well you personally know the code you are reading. What's probably wrong with your Python reading is that you have to look up what the third parameter in the slice more than the slice parameter actually being confusing.


In most programming languages the [] operator allows one to access elements in a container.

In this case, we are accessing the position "two colons minus one". I refuse to believe this makes any sort of sense for someone that has no deep knowledge of Python.


Lots of languages use [] with colon separators for array slicing, e.g. F#, Perl, Ruby. The idea that this syntax involves "deep knowledge" is laughable.

Your criticism is analogous to me looking at "int* foo = bar()" and saying, hey, we're multiplying a type by a name! How ridiculous!


The int pointer syntax is in fact ridiculous, so it does not support your point. Also s[x:y] is intuitively understandable compared to referencing two colons and minus one...


Well, that is ridiculous. It makes the language literally impossible to parts without context-sensive semantic analysis. Go Language fixes that bug by always having a type name first or a colon after introducing a variable.


Your username is a little too on the nose here. Plus you seem to have somehow entirely missed my point, to a degree I wonder if it is deliberate. Is this some sort of troll?


In the spirit of the article, boost range also has a reverse filter, so the following would return a reversed range similar to python's generator expression

using namespace boost::adaptors;

  s | reversed
or

  reverse(s)


In python you can also do

  ''.join(reversed(s))
It's still not the same as your example, because in C++ you reverse the string in-place. If you have a mutable array-like, you can do

  arrayish[:] = reversed(arrayish)
But to me the syntax with `s[::-1]` is already perfectly clear. There is the difference that `reversed` returns an iterator, while `s[::-1]` constructs a new string.


Actually, how to reverse a string in Python is just "s[::-1]" -- you omitted the printing from the C++ version.

In terms of readability, they both beg the question - is the reversed string ever actually held in memory at some point or can it occur lazily via latent reverse iteration?

I mean is the alleged readability of either language really answering any important questions?


That's python 2 syntax.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: