Statically Typed

because Hindley-Milner rocks

C++: Structs vs. Tuples, or Why I like Tuples More


An interesting question was asked on StackOverflow the other day regarding Tuples and C-style structs.  I answered as best I could but I neither had time to expand upon it nor felt that my answer was sufficient.  Questions like these are funny because as far as the C++ language has evolved, Tuples were never core a component.  In fact, until the advent of template meta-programming, Tuples didn’t even exist.  Instead, we were relegated back to the common C-style struct for POD and non-POD datatypes (every member being default constructible.)

Tuples in Other Languages

To state bluntly, there’s nothing wrong with C-style structs.  Most languages with imperative roots have one form or another.  They’re clear, concise and carry with them named member variables.  Those names are easy to remember and totally beat out anonymous tuples like the kind you see in Python:

something = foo.getThisTuple()[1]

Whenever I see that I think, “Do I really need to read the documentation on this class to figure out what is being returned within the 2nd parameter of the tuple?”  In this case “1” is a magic number but no one seems to bat an eye when they read it.  We could require that programmers supply named constants with the Tuples returned from functions but then they’d also need to know which constants to import.

Such are the problems with Tuples in other languages and if you attempt to use C++ Tuples in the same manner you’ll wind up with the same problems.

Tuples in C++

In C++, due to its statically typed nature, things with Tuples are different.  In C++ enums are compiled to the equivalent of ints.  When using (abusing?) enums with Tuples we can get what appears to another programmer as something just as readable and maintainable as a struct with named variables:

int wins = player.wins;
int losses = player.losses;
int rank = player.rank;

versus

int wins = player.get<POKER_WINS>();
int rank = player.get<POKER_RANK>();
int losses = player.get<POKER_LOSSES>();

The Tuple version is slightly more verbose (as with most C++ things) but accomplishes the same goals as a struct.  It’s clear what you’re “getting” from the Tuple.  There is no need for magic numbers or guessing games.  The enum carries with it the meta information on the contents of the variable.

However, the Tuple is a lot more flexible in how it can be used in conjunction with the standard <algorithm> library.

Flexibility of C++ Tuples

Let me illustrate a point of pain while working with C-style structs.  Let’s suppose I have two data structs:

struct Foo{
    int one;
    int two;
};
struct Bar{
    double one;
    int two;
};

and I want to take the least item from a list when comparing the second member variable.  How would I have to do this with this example?  Either I’d rely heavily on bind and arrive at some fairly nauseating code or I’d make a template function to handle it

template<typename T> bool compareT(const T& left, const T& right){ return left.two < right.two; }
std::min_element( myListOfFoo.begin(), myListOfFoo.end(), std::ptr_fun(compareT<Foo>));
std::min_element( myListOfBar.begin(), myListOfBar.end(), std::ptr_fun(compareT<Bar>));

The first choice  I only stated to cover my bases while the templated function work until we meet a struct that has a slightly different naming convention:

struct Baz{
    int one;
    int three;
};

Now I need to make a new comparison function, specialized to handle only objects which have member variables called “three.”  And another one for structs that had member variables called “four.” Or how about if I wanted to compare the “one” member variable?  You get the idea.  I’d have to write a new function/functor for each named member variable just to do the same types of comparisons.

How do Tuples ease the burdens?  First, let’s redefine “Foo” and “Bar” with their companion index enums to remove those pesky “magic numbers.”

typedef boost::tuples::tuple<int, int> Foo;
enum FooIndex{
    FOO_ONE,
    FOO_TWO
};
typedef boost::tuples::tuple<int, double> Bar;
enum BarIndex{
    BAR_ONE,
    BAR_TWO
};

Next, let’s see what the search for a minimum value transforms into:

template<typename Tuple, std::size_t N>
bool lessTuple(const Tuple& left, const Tuple& right){ return left.get<N>() < right.get<N>(); }
std::min_element( myListOfFoo.begin(), myListOfFoo.end(), std::ptr_fun(lessTuple<Foo,FOO_TWO>));
std::min_element( myListOfBar.begin(), myListOfBar.end(), std::ptr_fun(lessTuple<Bar,BAR_TWO>));

Which doesn’t look like I’ve had to write less.  It actually looks like I’ve had to write more to the tune of two extra lines of code.  And this observation is spot on until we need to find the minimum element based on the “one” variable.  Instead of having to write a completely new comparison function, I simply change the Tuple index in lessTuple.

Easing the Pain Futher

Let me extrapolate on the idea above:

template<typename Tuple, std::size_t N, typename Comparison>
class tuple_compare : public std::binary_function<Tuple,Tuple,bool>
public:
    bool operator()(const Tuple& left, const Tuple& right) const {
        return mComparison(left.get<N>(), right.get<N>());
    }
private:
    static const Comparison mComparison;
};
const typename tuple_compare<Tuple,N,Comparison>::Comparison
tuple_compare<Tuple,N,Comparison>::mComparisontypename tuple_compare<Tuple,N,Comparison>::Comparison();
//And now we specialize for any binary, default constructable functor...
template<typename Tuple, std::size_t N>
class tuple_less : public tuple_compare<Tuple,N,std::less<typename boost::tuples::element<N,Tuple>::type>{};

We’ve got a lot of functionality within a small amount of code that can be used with any Tuple. Element wise comparisons are easy and require absolutely no additional work.  In fact, every method from the standard <functional> library becomes available to any Tuple (with a little more work.)  Boost, itself, provides a tuple comparison library (boost/tuple/tuple_comparison.hpp,) meaning I’m not the first one to clue into this fact.

Conclusion

Finally we’ve got something that can replace the venerable C-style struct.  With C++0x just around the corner and much of the Boost library scheduled to become a standard part of the standard library, Tuples, I hope, will become a go to resource.  There are just too many things which can be done with Tuple types which would either necessitate reinventions of the wheel or copious usages of bind just to reuse code without them.

Advertisements

3 comments on “C++: Structs vs. Tuples, or Why I like Tuples More

  1. Avi
    May 6, 2012

    You probably wanted to say “Easing the Pain Further”, not “Easing the Pain Futher”.

    It’s better than a mistake I’ve seen some people make, where they’d have said “Easing the Pain Führer”

  2. Dobes Vandermeer
    October 21, 2012

    This brought to mind a question “what if struct could be treated as or converted into a tuple automatically?”. Then you’d have the benefits you listed above. Then I thought “in what order would the fields be translated? Declaration order?”. But in that case, if someone modifies the struct to have fields in a different order (normally that is fine for a struct) then you’d screw something up. The enum solution above seems to have a similar issue, though – you can’t “insert” fields into the middle of the tuple without changing everything… so, it could be troublesome. Anyway, just a few thoughts :-).

  3. gsamaras
    April 28, 2014

    So, if you don’t indent of using any std::algorithms, structs are the winners and tuples just destroy readability, right?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Information

This entry was posted on May 7, 2011 by in C++, Python.
%d bloggers like this: