top | item 4722836

C++11 and Boost - Succinct like Python

201 points| daivd | 13 years ago |fendrich.se | reply

164 comments

order
[+] viraptor|13 years ago|reply
"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.

[+] fusiongyro|13 years ago|reply
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.

[+] daivd|13 years ago|reply
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.
[+] fafner|13 years ago|reply
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...
[+] Jabbles|13 years ago|reply
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.

[+] antihero|13 years ago|reply
Having this all the time seems really verbose compared to exceptions:

    if err != nil {
        return nil, err
    }
[+] unoti|13 years ago|reply
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...
[+] Kurtz79|13 years ago|reply
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.

[+] ajross|13 years ago|reply
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++.)

[+] redcircle|13 years ago|reply
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.
[+] reddit_clone|13 years ago|reply
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.

[+] songgao|13 years ago|reply
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.

[+] daivd|13 years ago|reply
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.
[+] drivebyacct2|13 years ago|reply
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...
[+] ctrlaltesc|13 years ago|reply
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.

[+] huhtenberg|13 years ago|reply

  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.
[+] daivd|13 years ago|reply
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.
[+] alpatters|13 years ago|reply
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.
[+] bcoates|13 years ago|reply
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...
[+] daivd|13 years ago|reply
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.

[+] microtonal|13 years ago|reply
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.

[+] unwind|13 years ago|reply
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.

[+] daivd|13 years ago|reply
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.

[+] michaelfeathers|13 years ago|reply
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.
[+] potkor|13 years ago|reply
Especially if the benchmark measured programming and debugging time.
[+] mitchi|13 years ago|reply
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...
[+] talloaktrees|13 years ago|reply
I know c and python, but not c++. While reading the code section, i totally thought this was a wonderful satire of c++
[+] daivd|13 years ago|reply
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.
[+] arctangent|13 years ago|reply
Equivalent code written in D would look much more like the Python code than this.
[+] SiVal|13 years ago|reply
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.

[+] sodiumphosphate|13 years ago|reply
I keep wishing for this magical language to manifest, like a C++ compatible Boo. I only wish I were capable of building one myself.
[+] simmons|13 years ago|reply
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!)
[+] stinos|13 years ago|reply
"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();
[+] reinhardt|13 years ago|reply
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"
[+] zvrba|13 years ago|reply
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
[+] dfbrown|13 years ago|reply
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;
    }
[+] pif|13 years ago|reply
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?
[+] bcoates|13 years ago|reply
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.

[+] tomjen3|13 years ago|reply
It is because C++ is a large language and it has some features (e.g. macros) that can be horribly misused.
[+] j_baker|13 years ago|reply
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.

[+] daivd|13 years ago|reply
Is there really such a bandwagon?
[+] activepeanut|13 years ago|reply
Is it possible to build Boost for iOS with c++11 enabled in llvm-clang?
[+] gilgoomesh|13 years ago|reply
On iOS 5 and newer (required for libc++), using clang 3.0 and newer (required for decent c++11 support), yes.
[+] bitcracker|13 years ago|reply
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 :-)

[+] malkia|13 years ago|reply
And here goes your productivity - the real killer application for your link times.
[+] nimrody|13 years ago|reply
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.