More adventures in C++

2013-02-21, Comments

bool operator<(version const & v1, version const & v2)
{
    if (v1.major != v2.major)
        return v1.major < v2.major;
    if (v1.minor != v2.minor)
        return v1.minor < v2.minor;
    if (v1.patch != v2.patch)
        return v1.patch < v2.patch;
    return v1.build < v2.build;
}

C++ programmers are sticklers for tradition and unlikely to be swayed by what’s in fashion. C++ suits those who want to control the machine, and who respect the rigour and discipline this imposes. C++ programmers are generally a conservative bunch.

Some history: C++ was standardized in 1998. The next major revision of the language was developed under the working title of C++0x, where the “0x” stood for the year the job would be finished. The X gave the standardizers some slack, but not enough. C++0x became C++11 which is now, thankfully, simply C++.

Although the language’s development has been painstakingly slow the developments themselves have been extensive and radical. What’s more, users are rushing to use the new features — even before they have access to compilers which support them! I’ve seen answers to C++ topics on Q&A sites which use aspects of the language the contributors cheerfully admit they have no access to. I’ve worked on a project which used elaborate shims to hide the fact that GCC 4.6 couldn’t compile C++ as well as GCC 4.7 does, and this despite the fact that GCC’s C++11 support remains, officially, “experimental”.

At home, I’m downloading compiler and library updates I’m in no position to use at work; and at work, I’ve already been sent on a C++11 training course. I’ve streamed high quality videos starring C++’s big hitters which promote the new C++, explaining its principles, its foundations, and even where it’s going next.

What exactly is it about C++11 that’s roused such a normally phlegmatic audience?

Before I try and answer that, I’ll venture to suggest new C++ isn’t going to win many new recruits. I don’t even think it will persuade those who have abandoned the language to return. C++11 contains all of C++98, a notoriously complex and subtle language, then adds a whole lot more. Yes, it is possible to write new C++ which is more compact and efficient than traditional C++, but you’ll also need to maintain old C++ code and build new expertise. And the language update fails to address some of C++’s worst characteristics: slow compile times and impenetrable compiler diagnostics.

No, C++11 is primarily a win for existing C++ programmers; those of us who already have a fair understanding of the language and its trade-offs, and who can appreciate the rationale behind the changes. For traditionalists and pragmatists, the transition isn’t hard — at least, no harder than any port between compiler revisions. For progressives, there are several immediate wins: the auto keyword has been repurposed, reducing repetition and making code more flexible; lambdas enable functions to be plugged directly into algorithms; smart pointers are standard, allowing accurate memory management; and on the subject of memory, rvalues and move semantics mean you’ll waste less of it on temporaries.

I could go on.

Rather than risk more generalisations, here’s a specific example of C++11 in action. Consider an object with multiple fields, a four part version number, say.

struct version
{
    unsigned major, minor, patch, build;
};

To compare version numbers, or sort them, or put them in a std::set, we’ll need operator<(). This operator must model a strict weak ordering. The canonical form looks something like.

bool operator<(version const & v1, version const & v2)
{
    if (v1.major != v2.major)
        return v1.major < v2.major;
    if (v1.minor != v2.minor)
        return v1.minor < v2.minor;
    if (v1.patch != v2.patch)
        return v1.patch < v2.patch;
    return v1.build < v2.build;
}

It’s not so hard to write this code for the version struct, where we have a clear idea of what it means for one version number to be less than another. It would be rather more tricky if we were dealing with points, for example, struct point { int x, y; };. Ordering points makes little sense but we might well want them as keys in an associative container, and we’d better have a suitable operator<().

No, no, no!
bool operator<(point const & p1, point const & p2)
{
    return p1.x < p2.x && p1.y < p2.y;
}

With C++11 — with the current version of C++ — we can use std::tie() to create a tuple of references, recasting operator<() into a form that’s easy to read and hard to get wrong.

Yes, yes, yes!
bool operator<(version const & v1, version const & v2)
{
    return std::tie(v1.major, v1.minor, v1.patch, v1.build)
         < std::tie(v2.major, v2.minor, v2.patch, v2.build);
}

bool operator<(point const & p1, point const & p2)
{
    return std::tie(p1.x, p1.y) < std::tie(p2.x, p2.y);
}

§

My thanks to Jonathan Wakely for sharing the std::tie() recipe on the accu-general mailing list and for letting me use it here.