Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

The Header <tuple>


July, 2005: The Header <tuple>

Pete Becker is a software developer employed by Dinkumware Ltd., where he works on standard library implementation and documentation for C, C++, and Java. He is project editor for the C++ Standard, and currently writing a book about TR1. He can be contacted at [email protected].


I've heard that some primitive tribes only have three numbers: one, two, and many. By that standard, C++ template programmers who use the Standard Library are somewhat primitive, because they are often limited to writing code that uses one or two things, but not more.

For example, writing a template function that simply returns its argument is easy:

template <class T>
    T copy(T val)
    {
    return val;
    }
    std::cout << "Last value is: " 
             << copy(0) << '\n';

Writing a template function that takes two arguments and returns them both is a little more complicated, but still, not very hard:

template <class T0, class T1>
    std::pair<T0, T1> copy(T0 val0, T1 val1)
    {
    return std::make_pair(val0, val1);
    }
    std::cout << "Last value is: " 
              << copy(0, 1).second << '\n';

Writing a template function that takes more than two arguments and returns all of them can be done using only the Standard Library, but the result is rather difficult to use [1]:

template <class T0, class T1, class T2>
    std::pair<T0, std::pair<T1, T2> >
    copy(T0 val0, T1 val1, T2 val2)
    {
    return std::make_pair(val0, 
        std::make_pair(val1, val2));
    }
    std::cout << "Last value is: " 
      << copy(0, 1, 2).second.second << '\n';

The ISO Technical Report on C++ Library Extensions [2], better known as TR1, provides a generalization of std::pair, named std::tr1::tuple, that holds anywhere from 0 up to an implementation-defined number of values of arbitrary types. It also makes some additions to the existing header <utility> so that std::pair can be used in some of the same ways as the new template tuple. This month I look at the new TR1 header <tuple> and at the changes TR1 makes to the header <utility>.

Header Contents

TR1 introduces a new header named <tuple> with the following declarations:

  namespace std {
  namespace tr1 {
  template <class T1, class T2, ..., class TN>
    class tuple;
  template <int Idx, class Tuple> 
                    class tuple_element;
  template <class Tuple> class tuple_size;
  template <int Idx, class T1, class T2, ...,
           class TN>
    RI get(tuple<T1, T2, ..., Tn>&);
  template <int Idx, class T1, class T2, ...,
           class TN>
    RI get(const tuple<T1, T2, ..., Tn>&);
  template <class T1, class T2, ..., class TN>
    tuple<V1, V2, ..., VN>
      make_tuple<const T1&, const T2&, ...,
                const TN&);
  template <class T1, class T2, ..., class TN>
    tuple<T1&, T2&, ..., TN&>
      tie(T1&, T2&, ..., TN&);
  template <class T1, class T2, ..., class TN,
    class U1, class U2, ..., class UN>
      bool operator==(
        const tuple<T1, T2, ..., TN>&,
        const tuple<U1, U2, ..., UN>&);
  /* and, similarly, operator!=, operator<, 
        operator<=, operator>, operator>= */
  } // namespace tr1
  } // namespace std

Now, obviously, those aren't the real declarations: You can't have an ellipsis in a template argument list. This is shorthand for a family of templates that take from 0 to N template arguments. So, for example:

template <class T1, class T2, ..., class TN>
    class tuple;


means that you can write:

  tuple<> t0;    // tuple object 
                 // holding no objects
  tuple<int> t1; // tuple object 
                 // holding one object
                // of type int
  tuple<int, int> t2; // tuple object 
                     // holding 
                     // objects of 
                     // type int
  // and so on, up to the 
  // implementation-defined maximum 
  // number of arguments

The Class Template tuple

The full definition of the class template tuple looks like this:

template <class T1, class T2, ..., class TN>

<blockquote>
  class tuple {<br>
  public:<br>
    // constructors<br>
    tuple();<br>
    tuple(const tuple&);<br>
    template <class U1, class U2, ..., <br>
             class UN><br>
      tuple(const tuple<U1, U2, ..., UN>&);<br>
    template <class U1, class U2><br>
      tuple(const pair<U1, U2>&);<br>
                              // N == 2<br>
    explicit tuple(P1, P2, ..., PN);<br>
                              // 0 < N<br>
<br>
    // assignment operators<br>
    tuple& operator=(const tuple&);<br>
    template <class U1, class U2, ..., <br>
             class UN><br>
      tuple& operator=(const tuple<U1, U2,<br>
                      ..., UN>&);<br>
    template <class U1, class U2><br>
      tuple& operator=(const pair<U1, U2>&);<br>
                               // N == 2<br>
  };<br>

Again, this is shorthand. The details of how this is done are up to the implementor.

Each tuple type has a default constructor and a copy constructor. It also has a constructor that takes a tuple type that holds the same number of objects. With this constructor, each contained object is constructed from the corresponding contained object in the tuple object that was passed to the constructor. If any of the contained types can't be constructed from the corresponding argument type, this constructor won't compile. A tuple type that holds two objects also has a constructor that takes an argument of type std::pair<U1, U2>; again, if the elements in the tuple being constructed can't be constructed from the corresponding elements in the argument, this constructor won't compile. And finally, a tuple type that holds one or more objects has a constructor that takes N arguments; when the contained type is a reference, the corresponding constructor argument is simply that type; otherwise the constructor argument has type const T&, where T is the type of the contained object. Listing 1 has examples of these constructors.

Similarly, each tuple type has an assignment operator that takes a tuple object of the same type, and an assignment operator that takes a tuple object with the same number of elements. The latter won't compile if the individual elements aren't assignable. And, as with the constructors, you can assign an object of type std::pair<U1, U2> to a tuple that holds two objects.

The Function Template get

You probably noticed that, unlike std::pair, the tuple template does not give names to its stored elements. Instead, there is a pair of template functions that take an index and a tuple object and return a reference to the object contained in the tuple argument at the given index. This index is not an argument to the function; rather, it's a template argument. The declarations of these functions look something like this:

template <int Idx, 
         class T1, class T2, ..., class TN>
    RI get(tuple<T1, T2, ..., TN>&);
  template <int Idx, 
        class T1, class T2, ..., class TN>
    RI get(const tuple<T1, T2, ..., TN>&);

Each function returns a reference to the element at location Idx in the tuple object. Note that index values begin at 0, so the element at index 0 has type T1 [3]. When the type of the selected element is a reference, the return type RI of each of these functions is simply that type; otherwise, the first function returns a reference to the type of the selected element and the second function returns a const reference to that type.

You call this function by passing a tuple object. The compiler deduces the types of the stored elements from the argument itself. It can't deduce the index that you're looking for, so you have to supply it: get<0>(tpl) returns the first element of its argument, get<1>(tpl) returns the second element, and so on, as in Listing 2.

The Function Template make_tuple

Often you'll have a set of values that you want to store in a tuple object; instead of writing out the template arguments, you can use the function make_tuple to create a tuple object. Each element of the resulting tuple has the same type as the corresponding argument to make_tuple, with one exception. We haven't looked at the TR1 template reference_wrapper yet, but, briefly, an object of type reference_wrapper<T> acts pretty much like a reference to T, except that it can be copied. When you pass an object of type reference_wrapper<T> to make_tuple, the corresponding contained object has type T&, and refers to the object that the reference_wrapper<T> object referred to. Most of the time you'll use the template function ref to create a reference_wrapper object that refers to the object passed to ref [4]. Listing 3 has examples of make_tuple.

The Function Template tie

The function template tie creates a tuple object that holds references to its arguments. You could do the same thing with make_tuple by wrapping each argument with a call to ref, but using tie is much more convenient. In addition, when you call tie, you can pass the object ignore in any argument position and the resulting tuple object will ignore assignments to that object. You'll typically use tie and ignore to extract values from a tuple object into individual variables, as in Listing 4.

Comparing <tuple> Objects

Naturally, there will be times when you'll need to know whether two tuple objects hold the same values, and sometimes you'll also need to check the relative order of two tuple objects. TR1 defines the usual set of six relational operators.

Two tuple objects are equal if their individual elements are equal. Two tuple objects are unequal if any two corresponding elements are not equal. In both cases, the implementation compares elements from left to right, applying operator== to the two elements. It stops comparing at the first two elements that are not equal. Listing 5 shows how this works. Note that the type S has its own operator== but not an operator!=; the implementation of operator==and operator!= for tuple only uses operator==.

A tuple object is less than another tuple object if, for the first two elements that are not equal, the element from the first tuple object is less than the corresponding element from the second tuple object. In this case, unlike the equality tests that we just looked at, two elements are equal if neither one is less than the other. This means that user-defined types only have to define an operator< to support operator< for tuple objects that hold them. Listing 6 shows the use of operator<.

The rest of the inequality operators for tuple objects are defined in terms of operator< for those objects. That is, tpl0 > tpl1 is defined as tpl1 < tpl0, tpl0 <= tpl1 is defined as !(tpl1 < tpl0), and tpl0 >= tpl1 is defined as !(tpl0 < tpl1).

The Class Templates tuple_size and tuple_element

From time to time you may need to know how may elements a tuple type holds, or you may need to determine the type of an element held in a tuple type. The two template classes tuple_size and tuple_element give you this information.

The template tuple_size<Tuple> has a nested object value, which is a compile-time constant equal to the number of elements in the template argument Tuple. That description is pretty fuzzy, because the TR doesn't require any particular implementation; the rule is that when you use the name tuple_size<Tuple>::value, you get a compile-time constant.

The template tuple_element takes two template arguments. The first is the index of the element you want to know the type of, and the second is the tuple type that you're asking about. It has a nested type name named type that is a synonym for the type of the requested element. Just as with the template function get, index values begin at 0. Listing 7 shows the use of these templates.

Adding a tuple-like Interface to std::pair

We've been looking at the contents of the TR1 header <tuple>. TR1 also makes some changes to the Standard Library header <utility>, to make std::pair look a bit more like a tuple. You can call the template functions get<0> and get<1> with an argument that's an instance of pair, and you can specialize the templates tuple_size and tuple_element with a pair type.

Template Metaprogramming with tuple

The templates tuple_size and tuple_element give you compile-time access to the details of a tuple type; combined with the template function get and some fairly simple metaprogramming you can manipulate any or all of the elements contained in a tuple object without having to know, when you write the code, how many elements any particular tuple holds. Listing 8 shows how to print out the value of every element in an arbitrary tuple object. It requires a bit of explanation.

It begins with a forward declaration of a template struct name writer. Using a forward declaration in this case is a matter of style. It's not necessary, but without it I'd have to write the full template before the specializations, and I prefer to write it after them.

Each version of the template writer takes a template argument whose value is one more than the index of the element that it will display. It has a template member function named put that takes two template arguments. The first argument is the type of the output stream that the function should write to, and the second argument is the type of the tuple object whose element will be written out. It takes two objects of these types, writes out the appropriate information, and returns a reference to the output stream.

The put member function of the first specialization, writer<0>, handles empty tuple types. It doesn't write anything out, because there is nothing to write.

The put member function of the next specialization, writer<1>, simply writes element 0 of a nonempty tuple object.

The put member function of the general template writer<N> writes the first N elements of its tuple argument. So writer<2>::put writes elements 0 and 1, writer<3>::put writes elements 0, 1, and 2, and so on. It does this by first calling writer<N-1>::put, writing a comma, and then writing the value of element N-1. Of course, writer<N-1>::put writes the value of element N-2, and so on down the recursion chain. Each version of put calls the lower version before writing its element, so the elements come out in the right order.

Finally, the template function write_tuple takes a reference to an output stream and a tuple object, figures out the number of elements in the tuple type, instantiates a writer type for that number of elements, and calls its put member function [5]. Note that the writer type really is necessary: You can't write a partial specialization for a template function, which means that you can't write this function so that it recurses at compile time; instead, we have to go through a template class, which can be partially specialized, allowing the sort of recursion that we need here.

Coming Up

Next time, I begin looking at the additions that TR1 makes to the Standard Library header <memory> to provide a reference-counted smart pointer.

Notes

  1. [1] Of course, if you need to do this sort of thing you can write ad hoc templates to hold various numbers of things, generalizing std::pair by adding triple, quad, and so on. There's nothing inherently wrong with this approach, but an application that uses multiple libraries could run into problems from conflicting definitions of such things.
  2. [2] By the time you read this the technical report will probably have received final approval. At the April 2005 Standards Committee meeting, we got word that it had passed it's penultimate ballot unanimously. We voted at that meeting to send it out for final ballot.
  3. [3] This is mostly a typographical convenience. It's hard to write pseudocode that uses names like TN-1, which would be the last template argument if their numbers started at 0, so that it doesn't look like ordinary subtraction.
  4. [4] I examine this in detail in a later column; for now, with an object obj of type T, the template function ref returns an object of type reference_wrapper<T>, and the template function cref returns an object of type reference_wrapper<const T>.
  5. [5] This function really should be a stream inserter, not a named function, but there's no portable way to name the type of an arbitrary tuple object, so it's not currently possible to write an overloaded inserter for tuple objects. If you restrict yourself to a few reasonable sizes you can write a stream inserter for each size that you're interested in, in the same way you'd write an inserter for a pair. That's probably the best way to handle writing the contents of tuple objects at present, but that doesn't make an interesting example.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.