Generic Algorithms with Tuples
A tuple is a data structure that holds several values at once, and those values do not have to share a data type.
Predicting a Mixed-Type Tuple
A tuple is a data structure that holds several values at once, and those values do not have to share a data type. That single property is what makes tuples useful: one object can carry a name, a count, and a measurement together. Instead of starting with definitions, this article runs a short piece of C++ that uses tuples, asks you to predict what it should produce, and then checks that prediction against the program's behavior.
What do you think happens?
Suppose one tuple is declared with an explicit type, tuple<string,int,double>, and another is declared with auto and make_tuple from a string, an int, and a double. Will the second tuple have the same type as the first?
Reveal answer
Yes, the auto version has the same type
The source describes the make_tuple declaration as one where the type is deduced to tuple<string,int,double>, which is the same type the explicit declaration spells out. The two styles differ in who writes the type, not in the type that results.
Who Decides the Tuple's Type
There are two common ways to create a tuple in C++, and the difference between them is about who decides the tuple's type. In the first style, you write the type yourself. The angle brackets list each element's type in order, and the braces supply the values. In the second style, you use the auto keyword together with the make_tuple function. There you supply only the values, and the type is worked out from them.
sensor 3 21.5
sensor 3 21.5- The explicit declaration writes tuple<string, int, double> and the braces supply the values.
- The auto declaration supplies only the values to
make_tuple; the element types are deduced from them. - Both tuples are read with get and print the same output because their element types match.
Both declarations produce tuples that hold the same kinds of values in the same order, so they are the same type. The choice between them is about readability and intent. The explicit form tells a reader the exact types at a glance, which is helpful when the types are not obvious from the values. The make_tuple form avoids repeating a type that could have been inferred from the values you already supplied.
Reading Elements with get
Once a tuple exists, you read its elements with the get function. You give get a number in angle brackets, and it returns the element at that position. Positions start at zero, so the first element is at index 0. Each get call returns one element, and you store that result in a variable whose type matches the element at that position.
Read the index diagram from left to right. The index is the only thing that selects an element, and the index and the declared element type sit side by side in the declaration. So to predict what get<1> returns, look at the second element in the declaration, which is int. The result of get<1> is an int, and it should be stored in an int variable. The same reasoning gives you the type of get<2>, which is double.
Following the Sample Program Line by Line
Now follow a short program that builds a reading with make_tuple and then reads each element into a variable. As you go, compare each line against the type you predicted. The program is written so that one line reads the wrong position for the variable it feeds. Try to find it before reading the explanation.
#include <iostream>
#include <string>
#include <tuple>
using namespace std;
int main() {
auto reading = make_tuple(string("sensor"), 3, 21.5);
string name = get<0>(reading);
int count = get<1>(reading);
double value = get<1>(reading);
cout << name << " " << count << " " << value << endl;
return 0;
}- 1Line 1 builds the tuple. The deduced type is tuple<string,int,double>.
- 2Line 2 reads index 0, the string, into name. Index and type agree.
- 3Line 3 reads index 1, the int, into count. Index and type agree.
- 4Line 4 is the suspect. The variable value is a double, but get<1> points at the int at index 1, not the double at index 2.
- 5Line 5 prints the values. value holds 3 rather than 21.5, so the printed result differs from what the declaration suggests.
make_tuplesupplies a string, an int, and a double, so reading has three elements.- get<0> returns the string, which fits the string variable name.
- get<1> returns the int, which fits the int variable count.
- The line that defines value calls get<1> instead of get<2>; this is the divergence.
sensor 3 3Work backward from the wrong value, as the flowchart shows. The value printed as 3 came from a get call, so find that call first. It reads index 1. The declaration puts an int at index 1 and a double at index 2, so the variable value, which is a double, should have been fed by index 2. Most divergences are answered at this third question, because the index and the declared type sit side by side in the source. Changing get<1> to get<2> on the value line makes the output match the expected 21.5.
Assigning and Comparing Tuples
The source states that, like pairs, tuples can be assigned and compared if their elements can be. The rule has two parts. Assignment copies one tuple's contents into another. Comparison checks tuples against each other. Both depend on the elements, not only on the tuple as a container. A tuple whose element types support assignment and comparison can take part in those operations; if an element type does not support them, the tuple cannot either.
auto a = make_tuple(string("alpha"), 1, 2.0);
auto b = make_tuple(string("beta"), 5, 9.5);
bool same = (a == b);b holds (beta, 5, 9.5), so a and b differ in every position and the comparison is false.
auto a = make_tuple(string("alpha"), 1, 2.0);
auto b = make_tuple(string("beta"), 5, 9.5);
b = a;
bool same = (a == b);Assignment copies each position of a into b, so b holds (alpha, 1, 2.0) and the comparison is true.
Read the after state position by position. The assignment b = a handles the string, the int, and the double one at a time, because each element is copied into the matching position. The comparison works the same way: each position is checked against its counterpart. That is why every element type has to support the operation. A single element that cannot be assigned or compared stops the whole tuple from being assigned or compared.
Choosing Between Pairs and Tuples
A pair is common in interfaces, because a function often needs to return more than one value. The source gives an example: a result together with an indicator of how good that result is. A pair holds exactly two parts, which covers that case. The source notes that three or more parts are less common in results, and that tuples are therefore more often found inside the implementations of generic algorithms. The choice between them is driven by how many parts a result has and where that result is used.
| Feature | Pair | Tuple |
|---|---|---|
| Number of parts | Exactly two | Three or more |
| Typical home | Interfaces, such as a result with a quality indicator | Implementations of generic algorithms |
| Element types | May differ from each other | May differ from each other |
| Assignment and comparison | Allowed when the elements allow it | Allowed when the elements allow it |
Pairs and tuples share the same rules for mixed types, assignment, and comparison, and differ mainly in how many parts they hold.
Common Mistakes with Tuple Indexes and Types
Common mistake Counting positions from one instead of zero
What it looks likeUsing get<3> to read the third element of a three-element tuple
Why it failsPositions start at zero, so the first element is index 0 and the last of three is index 2.
Do this insteadUse get<2> for the third element.
Common mistake Storing a get result in a variable of the wrong type
What it looks likeWriting string label = get<1>(reading) when index 1 holds an int
Why it failsThe variable receiving the result must match the element's type at that index.
Do this insteadCheck the declared element type at the index, then choose a variable of that type.
Common mistake Assuming the auto form produces a different type than the explicit form
What it looks likeBelieving make_tuple(string("sensor"), 3, 21.5) is not tuple<string,int,double>Why it failsThe source describes the
make_tupledeclaration as deducing the same tuple<string,int,double> type.Do this insteadTreat the two styles as producing the same type, and choose between them on readability.
Common mistake Expecting a tuple to be assigned or compared when one of its element types cannot be
Practice: Predict Before You Run
A tuple is built with auto and make_tuple(string("log"), 7, 0.25). Predict the type of get<2> and the type of the result of get<1>. Then write one line of code that stores get<1> in a variable of the correct type, and explain which index you used and why.
Hints
- Count the values in the
make_tuplecall and assign each one an index starting at zero. - The source says the variable receiving the result must match that element's type.
- The
make_tupleform deduces the same types the explicit declaration would write.
Here is a program with one wrong line. Find it using the three debugging questions: which get call produced the value, what index it used, and what type the declaration gave that position. Then state the corrected line.
auto t = make_tuple(string("gate"), 12, 4.5);
string s = get<0>(t);
int n = get<2>(t);
cout << s << " " << n << endl;Hints
- Compare the variable type on each line with the element type at the index it reads.
- The element at index 2 is a double, and the variable n is an int.
- Changing the variable type or the index are both possible fixes; choose the one that matches the intended value.
Key Takeaways
- A tuple holds several values that can differ in type, and it can be declared with an explicit type or with auto and
make_tuple; in both cases the source describes the same resulting type. - The get function reads an element by its zero-based index, and the variable receiving the result must match the element's type at that index.
- Tuples can be assigned and compared when their elements can be, and every element type must support the operation.
- Pairs suit exactly two-part results common in interfaces, while tuples suit three or more parts, often inside generic algorithms.
- When a tuple result is wrong, trace the value back to its get call, read the index used, and check the declared element type at that position.