Tuples and structs

The TR1 library for C++0X includes the template type tuple<..>. It’s available now from Boost.

# include <boost/tuple/tuple.hpp>
# include <boost/tuple/tuple_comparison.hpp>
# include <boost/tuple/tuple_io.hpp>

using boost::tuples::tuple;

tuple< double, char > tup_inst1;
tuple< double, char > tup_inst2( 3.22);
tuple< double, char > tup_inst3( 5.42, 'a');

Abstraction

A tuple is a lot like an old C-style POD struct. Both are heterogeneous aggregators, holding a collection of objects of various types. Neither are open ended, as the types and counts of the enclosed values are fixed at compile time.

Of course a modern C++ struct can also have methods, supertypes, static members, etc. A tuple doesn’t do any of that. It is bare and public, like struct circa 1980, before templates, when compilers were not expected to generate code.

So how are they different? Why bother with tuple when you can just use struct?

You don’t have to declare a tuple<..> type before you use it, like you do with struct. You just use tuples when you need one. They are declared on-the-fly, at the point of usage.

tuples and structs both provide a default constructor, a destructor, a copy constructor, and a copy assignment operator. tuple are much more flexible during copy however, converting compatible inside types when possible. A struct enforces its type, while a tuple is just bundling values.

A tuple also provides constructors that can explicitly set member variables. With structs you either have to write the (obvious) constructor yourself, or use compiler initialization lists when that’s possible.

tuple types also have pre-defined comparison operators and IO functions (operator <<).

Unlike tuples, struct types are (usually) named. tuples are thin wrappers around their innards. They are not modest. structs attempt more abstraction, both by being named and by using names to access members.

structs protect their identity, sometimes to a fault. You cannot declare an identical named struct twice in the same compilation unit. Identical anonymous structs are different types. This can be inconvenient.

  // You cannot use a compiler initialization
  // list, at least with MSVC9 and GCC3.4.
  // I suspect this will change in C++0X. After all,
  // tuples feel even more POD than structs.
  tuple< double, char >
tuple_inst1
  ( 3.22, 'a'); // = { 3.22, 'a' }

  // Tuples know how to share.
  tuple< double, char >
tuple_inst2
  = tuple_inst1;

  // Using an unnamed struct. The member vars
  // have to have names though.
  struct { double a; char b; }
struct_inst1
  = { 3.22, 'a' };

  // This fails -- type mismatch.
  // Even though the structs are identical.
  // Structs really protect their identity.
  struct { double a; char b; }
struct_inst2
  = struct_inst1;

A struct is an abstractor, a hider of information, while a tuple is just an aggregator. For tuples, structure determines type. For structs, the name determines type and provides abstraction. But sometimes you don’t want abstraction, or even a name.

Metaprogramming

You access tuple members through integer indexes (i.e. tuple_inst.get<2>()). They’re like heterogeneous arrays. Contrast that with struct member access, which is by name (i.e. struct_inst.cost_increase).

Index access is mostly an artifact of C++ templates, which can manipulate integers and types, but aren’t so good with names (although you can use enums to give names to index values). Lisp macros have finer-grained control, manipulating symbols (names) as well as constants and lists.

But index access does not feel unnatural because order is imposed anyway, in parameter lists and constructors. tuple<char,double> is not the same type as tuple<double,char>. Even so, there are occasions when it’s clearer to define an enum so you can say tuple_inst.get() instead of tuple_inst.get<3>(). But if you find yourself doing this, consider using a struct instead of a tuple.

The tuple type in Boost is built using a type list, a technique pioneered by Andrei Alexandrescu, creator of Loki and author of Modern C++ Design. A type list is a recursive set of templated types that can be aggregated into a larger type. I use a similar technique in the post Using templates to define an array class with constructors.

In the C++0X future tuple might be defined with variadic template parameters instead of a type list. The type list is just a technique, and it is not exposed in the interface. It is clever but not essential. A variadic implementation would be more straightforward.

Comments

Leave a Reply