Usage
Tuples live in namespace tuples, which in turn is inside namespace boost. Include
"boost/tuple/tuple.hpp" to use the library. The relational operators are defined in
the header "boost/tuple/tuple_comparison.hpp". Input and output of tuples are
defined in "boost/tuple/tuple_io.hpp". A few of the key tuple components (tie and
make_tuple) are also available directly in namespace boost. In this section, we'll
cover how tuples are used in some typical scenarios, and how it is possible to
extend the functionality of the library to best fit our purposes. We'll start with the
construction of tuples, and gradually move on to topics that include the details of
how tuples can be utilized.
Constructing Tuples
The construction of a tuple involves declaring the types and, optionally, providing
a list of initial values of compatible types.
[1]
[1]
The constructor arguments do not have to be of the exact type specified for the
elements when specializing the tuple so long as they are implicitly convertible to
those types.
boost::tuple<int,double,std::string>
triple(42,3.14,"My first tuple!");
The template parameters to the class template tuple specify the element types. The
preceding example shows the creation of a tuple with three types: an int, a double,
and a std::string. Providing three parameters to the constructor initializes the values
of all three elements. It's also possible to pass fewer arguments than there are
elements, which results in the remaining elements being default initialized.
boost::tuple<short,int,long> another;
In this example, another has elements of types short, int, and long, and they are all
initialized to 0.
[2]
Regardless of the set of types for your tuples, this is how they are
defined and constructed. So, if one of your tuple's element types is not default
constructible, you need to initialize it yourself. Compared to defining structs,
tuples are much simpler to declare, define, and use. There's also the convenience
function, make_tuple, which makes creating tuples easier still. It deduces the types,
relieving you from the monotony (and chance of error!) of specifying them
explicitly.
[2]
Within the context of a template, T() for a built-in type means initialization with
zero.
boost::tuples::tuple<int,double> get_values() {
return boost::make_tuple(6,12.0);
}
The function make_tuple is analogous to std::make_pair. By default, make_tuple
sets the types of the elements to non-const, non-referencethat is, the plain,
underlying types of the arguments. For example, consider the following variables:
int plain=42;
int& ref=plain;
const int& cref=ref;
These three variables are named after their cv-qualification (constness) and
whether they are references. The tuples created by the following invocations of
make_tuple all have one int element.
boost::make_tuple(plain);
boost::make_tuple(ref);
boost::make_tuple(cref);
This isn't always the right behavior, but on most occasions it is, which is the reason
why it's the default. To make an element of a tuple to be of reference type, use the
function boost::ref, which is part of another Boost library called Boost.Ref. The
following three lines use the variables that we declared earlier, but this time the
tuples have an int& element, except for the last, which has a const int& element
(we can't remove the constness of cref):
boost::make_tuple(boost::ref(plain));
boost::make_tuple(boost::ref(ref));
boost::make_tuple(boost::ref(cref));
If the elements should be const references, use boost::cref from Boost.Ref. Here,
the three tuples have one const int& element:
boost::make_tuple(boost::cref(plain));
boost::make_tuple(boost::cref(ref));
boost::make_tuple(boost::cref(cref));
It's probably obvious, but ref and cref have plenty of uses in other contexts too. In
fact, they were created as a part of the Boost.Tuple library, but were later moved to
a separate library because of their general utility.
Accessing tuple Elements
The elements of a tuple are accessed either through the tuple member function get
or the free function get. They both require a constant integral expression
designating the index of the element to retrieve.
#include <iostream>
#include <string>
#include "boost/tuple/tuple.hpp"
int main() {
boost::tuple<int,double,std::string>
triple(42,3.14,"The amazing tuple!");
int i=boost::tuples::get<0>(triple);
double d=triple.get<1>();
std::string s=boost::get<2>(triple);
}
In the example, a tuple with three elements with the innovative name triple was
created. triple contained an int, a double, and a string, which were retrieved
through the get functions.
int i=boost::tuples::get<0>(triple);
Here, you see the free function get at work. It takes a tuple as its one argument.
Note that supplying an invalid index causes an error at compilation time. The
precondition is that the index be a valid index for the tuple type.
double d=triple.get<1>();
This code shows using the member function get. The preceding line could also
have been written like this:
double& d=triple.get<1>();
The preceding binding to a reference works because get always returns a reference
to the element. If the tuple, or the type, is const, a const reference is returned. The
two functions are equivalent, but on some compilers only the free function works
correctly. The free function has the advantage of providing a consistent extraction
style for types other than tuple. One advantage of accessing the elements of tuples
by index rather than by name is that it enables generic solutions, because there are
no dependencies on a certain name, but only to an index. More on this later.
Tuple Assignment and Copy Construction
tuples can be assigned and copy constructed, providing that there are suitable
conversions between the types of the elements in the two tuples. To assign or copy
tuples, member-wise assignment or copying is performed, so the two tuples must
have the same number of elements. The elements in the source tuple must be
convertible to those of the destination tuple. The following example shows how
this works.
#include <iostream>
#include <string>
#include "boost/tuple/tuple.hpp"
class base {
public:
virtual ~base() {};
virtual void test() {
std::cout << "base::test()\n";
}
};
class derived : public base {
public:
virtual void test() {
std::cout << "derived::test()\n";
}
};
int main() {
boost::tuple<int,std::string,derived> tup1(-5,"Tuples");
boost::tuple<unsigned int,std::string,base> tup2;
tup2=tup1;
tup2.get<2>().test();
std::cout << "Interesting value: "
<< tup2.get<0>() << '\n';
const boost::tuple<double,std::string,base> tup3(tup2);
tup3.get<0>()=3.14;
}
The example begins by defining two classes, base and derived, which are used as
elements of two tuple types. The first tuple contains three elements of types, int,
std::string, and derived. The second tuple consists of three elements of the
compatible types unsigned int, std::string, and base. Consequently, the two tuples
meet the requirements for assignment, which is why tup2=tup1 is valid. In that
assignment, the third element of tup1, which is of type derived, is assigned to the
third element of tup2, which is of type base. The assignment succeeds, but the
derived object is sliced, so this defeats polymorphism.
tup2.get<2>().test();
That line extracts a base&, but the object in tup2 is of type base, so it winds up
calling base::test. We could have made the behavior truly polymorphic by
changing the tuples to contain references or pointers to base and derived,
respectively. Note that numeric conversion dangers (loss of precision, positive and
negative overflow) apply when converting between tuples as well. These
dangerous conversions can be made safe with the help of the Boost.Conversion
library, covered in "Library 2: Conversion."
The next line in the example copy-constructs a new tuple, tup3, with different, but
still compatible types, from tup2.
const boost::tuple<double,std::string,base> tup3(tup2);
Note that tup3 is declared const. This implies that there is an error in the example.
See if you can you spot it. I'll wait…. Did you see it? Here it is:
tup3.get<0>()=3.14;
Because tup3 is const, get returns a const double&. This means that the assignment
statement is ill-formed, and the example doesn't compile. Assignment and copy
construction between tuples are intuitive, because the semantics are exactly the
same for the tuples as for the individual elements. By way of example, let's see
how to give polymorphic behavior to the derived-to-base assignment between
tuples.
derived d;
boost::tuple<int,std::string,derived*>
tup4(-5,"Tuples",&d);