A tuple
is a C++11 construction and it is built heavily on variadic templates.
A tuple
is a variadic class template that stores an unlimited set of values of different types, defined when instantiating the tuple; for example:
tuple<int, int> x;
will store 2 integers.
tuple<string, int, bool> y;
will store one string, one integer and one boolean and so on.
Tuples are useful for several things:
- They are light replacements for structures.
- They can be useful to return several values from a function
- They let you perform comparisons through all the value set
You can instantiate tuples in these ways:
tuple<int, int, bool> x; //instantiating but not initializing
tuple<int, string> y { 2, "hello" }; //instantiating AND initializing
auto z = make_tuple(2, 3, 4, "bye"s); //instantiating AND initializing throught the make_tuple helper function.
Consider you declare something like this:
auto xx = make_tuple(3.14, "PI"s);
To get the tuple values, you must to use the get<int>()
function like in this code excerpt:
#include <tuple> #include <iostream> #include <string> using namespace std; int main() { auto xx = make_tuple(3.14, "PI"s); auto& first = get<0>(xx); auto& second = get<1>(xx); cout << "(" << first << "; " << second << ")" << endl; return 0; }The template parameter is the index of the element to be retrieved, so, if the first element was an integer, the index 0 will retrieve the integer stored as the first value in the tuple, the index 0 will retrieve the second one and so on.
Tuples are also very useful to create structs “on the fly” and to use them in data structures; for example:
int main() { vector<tuple<int, string>> vec; vec.push_back(make_tuple(10, "ten")); vec.emplace_back(20, "twenty"); vec.emplace_back(30, "thirty"); for (auto& i : vec) { cout << "(" << get<0>(i) << "; " << get<1>(i) << ")" << endl; } }In this case I did not need to create a new struct in order to store the number and the number name, for example.
Other nice thing on tuples is that you can compare tuples relying on the comparison operators of the data types inside the tuples.
Look at this code:
#include <iostream> #include <set> #include <string> #include <tuple> using namespace std; using car = tuple<string, string, int>; void print(const car& c) { cout << get<0>(c) << ", " << get<1>(c) << "; " << get<2>(c) << endl; } int main() { set<car> cars; cars.emplace("Toyota", "Rav4", 2012); cars.emplace("VW", "Jetta", 2015); cars.emplace("Chevrolet", "Sonic", 2013); cars.emplace("BMW", "X5", 2014); cars.emplace("VW", "Jetta", 2014); for (auto& i : cars) print(i); cout << "******" << endl; auto it = cars.find(car { "Toyota", "Rav4", 2012 }); if (it == cars.end()) cerr << "CAR NOT FOUND" << endl; else print(*it); return 0; }When it is executed, this thing is displayed:
BMW, X5; 2014
Chevrolet, Sonic; 2013
Toyota, Rav4; 2012
VW, Jetta; 2014
VW, Jetta; 2015
******
Toyota, Rav4; 2012The code and its execution contain several interesting things:
- I defined an alias to a tuple called “car”, having it, I was able to use the tuple as a declared type in a lot of cases… without declaring it!
- If I would have created a “car” struct, to use it inside a set, I would have to overload the operator<() for the struct, because the sets use this operator in order to insert the objects accordingly.
- When executing the program, all the objects where ordered by brand, by model and by year; using the tuple operator<(). If the first element in a tuple A is less than the first element in a tuple B, the operator returns TRUE. If the first element in tuple A is greater than the first element in tuple B, the operator returns FALSE; but if both elements are equal, the comparison is performed between the second elements and so on.
- Notice I created a car to pass it into the method find; I created it in exactly the same way I would have created a car struct instance.
- Notice the element is found in the set (because operator<() too).
Now I want to modify my set in order to store the elements ordered by year, by brand and by model. Look at the new implementation:
#include <iostream> #include <set> #include <string> #include <tuple> using namespace std; using car = tuple<string, string, int>; void print(const car& c) { cout << get<0>(c) << ", " << get<1>(c) << "; " << get<2>(c) << endl; } int main() { auto cc = [](auto& a, auto& b) { return tie(get<2>(a), get<0>(a), get<1>(a)) < tie(get<2>(b), get<0>(b), get<1>(b)); }; set<car, decltype(cc)> cars { cc }; cars.emplace("Toyota", "Rav4", 2012); cars.emplace("VW", "Jetta", 2015); cars.emplace("Chevrolet", "Sonic", 2013); cars.emplace("BMW", "X5", 2014); cars.emplace("VW", "Jetta", 2014); for (auto& i : cars) print(i); cout << "******" << endl; auto it = cars.find(car { "Toyota", "Rav4", 2012 }); if (it == cars.end()) cerr << "CAR NOT FOUND" << endl; else print(*it); return 0; }The output is:
Toyota, Rav4; 2012
Chevrolet, Sonic; 2013
BMW, X5; 2014
VW, Jetta; 2014
VW, Jetta; 2015
******
Toyota, Rav4; 2012The elements are ordered first by year.
Notice my “cc” lambda expression. It uses a function called “tie”.
“tie” is a function that takes a set of references and creates a tuple containing references (not values or copies) to the original values. So, tie is useful to create temporary light tuples. In my example, using “tie”, I was able to create other set of tuples with different logical order, so the tuple operator<() algorithm worked using this new logical order.
C++17 introduced “structured bindings“, a simpler way of accessing the elements on a tuple.