As I reported in the last column [1], the big library news at the October 2002 standards meeting was that the first two library extensions were approved for the Standard Library extensions technical report:
- Doug Gregor's proposal for polymorphic function object wrappers. [2]
- Jaakko Järvi's proposal for tuple types. [3]
Both come directly from the Boost project, which is a collection of C++ libraries. [4] Last time, I promised that this column and the next would each cover one of these two accepted library extensions. This month, I'll give an overview of tuple types.
Tuple Types: A Simple Motivating Case
Have you ever wanted to return more than one result from a function? For example, consider this function:
// Example 1(a): Integer division, // yields a quotient only // int IntegerDivide( int n, int d ) { return n / d; } // Sample use: cout << "quotient = " << IntegerDivide( 5, 4 );
What's wrong with this? Nothing much, maybe. After all, we're doing exactly what the built-in integer division does, including rounding down the resulting quotient.
But what if we want to do more—specifically, what if we also want to provide a way to get the other important result of the division, namely the remainder? We're already using the (single, one and only) return value to encode the quotient, and there's no easy way to also provide the remainder without changing the function's signature in some way. One way we could do it is to add another "output" parameter:
// Example 1(b): Integer division, // yielding a quotient and remainder, // one as the return value and one via // an output parameter // int IntegerDivide( int n, int d, <font color="red">int& r</font> ) { r = n % d; return n / d; } // Sample use: int remainder; int quotient = IntegerDivide( 5, 4, remainder ); cout << "quotient = " << quotient << "remainder = " << remainder;
That's kind of clunky, but it's the kind of thing we've all done many times. It seems a little weird that one part of the output is returned via the return value, while another is returned via an output parameter. One might argue that the following is more consistent:
// Example 1(c): Integer division, // yielding a quotient and remainder, // this time via two output parameters // void IntegerDivide( int n, int d, <font color="red">int& q, int& r</font> ) { r = n % d; q = n / d; } // Sample use: int quotient, remainder; IntegerDivide( 5, 4, quotient, remainder ); cout << "quotient = " << quotient << "remainder = " << remainder;
That may be more consistent, but perhaps vaguely unsatisfying. With a little thought, we remember why: Ralph Waldo Emerson insists on reminding us that "a foolish consistency is the hobgoblin of little minds." Rats. This version works, but I wouldn't blame you if you had an uneasy, queasy, cheesy feeling about it.
What to do? Well, it's usually at just about this point that we might remember that we have a tool in the Standard Library that may be appropriate: std::pair. After all, there are functions in the Standard Library that return multiple things, notably iterator ranges, as a single value—and they do it by yielding a pair<iterator, iterator>. The same approach could work here! Let's try it:
// Example 1(d): Integer division, // yielding a quotient and remainder, // this time both in the return value // <font color="red">std::pair<int,int></font> IntegerDivide( int n, int d ) { return pair<int,int>( n/d, n%d ); } // Sample use: pair<int, int> quot_rem = IntegerDivide( 5, 4 ); cout << "quotient = " << quot_rem.first << "remainder = " << quot_rem.second;
As it turns out, this is a promising direction, and one that can be improved, generalized, and refined.
Tuples In Action
Some languages, including Haskell, ML, and Python, support tuple types directly. C++ doesn't, but hey, this is C++, the do-anything systems- and library-building power languagewe can make our own tuple types, as a library! (And our own lambda functions, and our own but I digress.) What Järvi and others have done is to package up a tuple type representing a "bundle-o-types" as a sort of generalization of pair, but much more so. A tuple object (object of tuple type) is similarly a "bundle-o-values."
The code for a tuple-ized IntegerDivide is very similar to the pair-ized one in Example 2(d), but don't be fooled:
// Example 2(a): Integer division, // yielding a quotient and remainder, // via a type return type // <font color="red">tuple<int,int></font> IntegerDivide( int n, int d ) { return tuple<int,int>( n/d, n%d ); } // Sample use: tuple<int, int> quot_rem = IntegerDivide( 5, 4 ); cout << "quotient = " << quot_rem.get<0>() << "remainder = " << quot_rem.get<1>();
The syntax isn't quite as elegant as for pair, but that's a pretty small nit compared to the great flexibility that tuples offer compared to simple pairs.
For one thing, tuples aren't restricted to just two members; they can have any number of members, and, therefore, can bundle (and unbundle) any number of values. Just to illustrate why that might be useful, even for something as simple as the example we just looked at, consider this variant:
// Example 2(b): Floating-point division, // yielding a quotient and remainder, // but also an underflow // <font color="red">tuple<float, float, bool></font> // quotient, remainder, underflow FloatDivide( float n, float d ) { // }
Try that with std::pair! (And because I know full well that some readers may be unable to resist doing exactly that, let me avoid some e-mail by adding: Yes, you could chain pairs manually, in this case for example std::pair<float, std::pair<float, bool> >, but I think we can all agree that would be the basest hackery.)
Now, you won't always want to work with the tuple as a bundle-o-values, so there are ways to "tie" ordinary standalone variables to a tuple. This is useful for bundling and unbundling values. For example, returning to the plain old quotient-and remainder version:
// Example 3: Bundling and unbundling // using "tie" // tuple<int,int> IntegerDivide( int n, int d ) { return tuple<int,int>( n/d, n%d ); } // Sample use: int quotient, remainder; <font color="red">tie( quotient, remainder )</font> = IntegerDivide( 5, 4 ); cout << "quotient = " << quotient << "remainder = " << remainder;
By the way, we don't have to write our own manual outputting like this if we don't want to. Tuples have their own stream insertion and extraction operators that work perfectly well:
// Example 4(a): Streaming tuples // tuple<int, int> quot_rem = IntegerDivide( 5, 4 ); cout << quot_rem; // "(1 1)"
Or, if you want to get a little fancier, you can control the open and close brackets and the delimiter:
// Example 4(b): Customizing streamed tuples // tuple<int, int> quot_rem = IntegerDivide( 5, 4 ); cout << tuples::set_open('[') << tuples::set_close(']') << tuples::set_delimiter(',') << quot_rem; // "[1,1]"
Next Time
For more details about tuple, see also the freely available Boost implementation at www.boost.org for your reading and programming pleasures.
In the next column, I'll report on function facility, and after than on the April 2003 C++ Standards meeting, held in Oxford, UK. Stay tuned.
References
[1] H. Sutter. "Trip Report: October 2002" (C/C++ Users Journal, 21(2), February 2003).
[2] D. Gregor. "A Proposal to add a Polymorphic Function Object Wrapper to the Standard Library," ISO/ANSI C++ Standards committee paper (ISO/IEC JTC1/SC22/WG21 paper N1402, ANSI/NCITS J16 paper 02-0060).
[3] J. Järvi. "Proposal for adding tuple types into the standard library," ISO/ANSI C++ standards committee paper (ISO/IEC JTC1/SC22/WG21 paper N1403, ANSI/NCITS J16 paper 02-0061).
[4] www.boost.org