tuple
sWhen we define a tuple
, we name the type(s) of each of its members:
tuple<size_t, size_t, size_t> threeD; // all three members set to 0
tuple<string, vector<double>, int, list<int>>
someVal("constants", {3.14, 2.718}, 42, {0,1,2,3,4,5});
When we create a tuple
object, we can use the default tuple
constructor, which value initializes (§ 3.3.1, p. 98) each member, or we can supply an initializer for each member as we do in the initialization of someVal
. This tuple
constructor is explicit
(§ 7.5.4, p. 296), so we must use the direct initialization syntax:
tuple<size_t, size_t, size_t> threeD = {1,2,3}; // error
tuple<size_t, size_t, size_t> threeD{1,2,3}; // ok
Alternatively, similar to the make_pair
function (§ 11.2.3, p. 428), the library defines a make_tuple
function that generates a tuple
object:
// tuple that represents a bookstore transaction: ISBN, count, price per book
auto item = make_tuple("0-999-78345-X", 3, 20.00);
Like make_pair
, the make_tuple
function uses the types of the supplied initializers to infer the type of the tuple
. In this case, item
is a tuple
whose type is tuple<const char*, int
, double>
.
tuple
A pair
always has two members, which makes it possible for the library to give these members names (i.e., first
and second
). No such naming convention is possible for tuple
because there is no limit on the number of members a tuple
type can have. As a result, the members are unnamed. Instead, we access the members of a tuple
through a library function template named get
. To use get
we must specify an explicit template argument (§ 16.2.2, p. 682), which is the position of the member we want to access. We pass a tuple
object to get
, which returns a reference to the specified member:
auto book = get<0>(item); // returns the first member of item
auto cnt = get<1>(item); // returns the second member of item
auto price = get<2>(item)/cnt; // returns the last member of item
get<2>(item) *= 0.8; // apply 20% discount
The value inside the brackets must be an integral constant expression (§ 2.4.4, p. 65). As usual, we count from 0, meaning that get<0>
is the first member.
If we have a tuple
whose precise type details we don’t know, we can use two auxilliary class templates to find the number and types of the tuple
’s members:
typedef decltype(item) trans; // trans is the type of item
// returns the number of members in object's of type trans
size_t sz = tuple_size<trans>::value; // returns 3
// cnt has the same type as the second member in item
tuple_element<1, trans>::type cnt = get<1>(item); // cnt is an int
To use tuple_size
or tuple_element
, we need to know the type of a tuple
object. As usual, the easiest way to determine an object’s type is to use decltype
(§ 2.5.3, p. 70). Here, we use decltype
to define a type alias for the type of item
, which we use to instantiate both templates.
tuple_size
has a public static
data member named value
that is the number or members in the specified tuple
. The tuple_element
template takes an index as well as a tuple type. tuple_element
has a public
type member named type
that is the type of the specified member of the specified tuple
type. Like get, tuple_element
uses indices starting at 0.
The tuple
relational and equality operators behave similarly to the corresponding operations on containers (§ 9.2.7, p. 340). These operators execute pairwise on the members of the left-hand and right-hand tuple
s. We can compare two tuple
s only if they have the same number of members. Moreover, to use the equality or inequality operators, it must be legal to compare each pair of members using the ==
operator; to use the relational operators, it must be legal to use <
. For example:
tuple<string, string> duo("1", "2");
tuple<size_t, size_t> twoD(1, 2);
bool b = (duo == twoD); // error: can't compare a size_t and a string
tuple<size_t, size_t, size_t> threeD(1, 2, 3);
b = (twoD < threeD); // error: differing number of members
tuple<size_t, size_t> origin(0, 0);
b = (origin < twoD); // ok: b is true
Because tuple
defines the <
and ==
operators, we can pass sequences of tuple
s to the algorithms and can use a tuple
as key type in an ordered container.
Exercise 17.1: Define a tuple
that holds three int
values and initialize the members to 10, 20
, and 30
.
Exercise 17.2: Define a tuple
that holds a string
, a vector<string>
, and a pair<string, int>
.
Exercise 17.3: Rewrite the TextQuery
programs from § 12.3 (p. 484) to use a tuple
instead of the QueryResult
class. Explain which design you think is better and why.