std::tuple
std::tuple
container, allowing us to store objects of different types.A std::tuple
is a generalization of std::pair
that allows storing a collection of objects of different types. While a pair is limited to two elements, a tuple can contain any number of elements. This makes tuples a flexible way to group related values.
Tuples are heterogeneous containers, meaning each element can have a different type. This is in contrast to homogeneous containers like arrays and lists where all elements have the same type.
Tuples are commonly used to return multiple values from a function, store related attributes of an object, or pass multiple arguments to a function. They provide a convenient way to work with small, fixed-size collections of diverse values.
The std::tuple
class template is available from the <tuple>
header. Once included, we can then create a tuple in the usual ways. We pass the types of the objects we’ll be storing as template arguments, and their values as constructor arguments:
#include <tuple>
std::tuple<int, float, bool> MyTuple{
42, 3.14f, true
};
When initializing the tuple with values, we can omit the template types, allowing the compiler to deduce them using class template argument deduction (CTAD)
#include <tuple>
std::tuple MyTuple{42, 3.14f, true};
The std::make_tuple()
function also allows tuples to be constructed with deduced types:
#include <tuple>
void HandleTuple(
std::tuple<int, float, bool> Object){
// ...
}
int main(){
HandleTuple(std::make_tuple(42, 3.14f, true));
}
This function was introduced to make construction require less syntax, but is less relevant now that CTAD provides the same convenience as a native feature of the language.
Access to objects within a std::tuple
is typically done through the std::get
template function. We pass the index as a template argument, and the tuple as a function argument:
#include <tuple>
#include <iostream>
int main(){
std::tuple MyTuple{42, 3.14f, true};
std::cout << std::get<0>(MyTuple) << ", ";
std::cout << std::get<1>(MyTuple) << ", ";
std::cout << std::get<2>(MyTuple);
}
42, 3.14, 1
The std::get
function returns the element by reference, allowing it to be updated:
#include <tuple>
#include <iostream>
int main(){
std::tuple MyTuple{42, 3.14f, true};
std::cout << "First element: "
<< std::get<0>(MyTuple);
std::get<0>(MyTuple) = 5;
std::cout << "\nNew first element: "
<< std::get<0>(MyTuple);
}
First element: 42
New first element: 5
We can also pass a type as the template argument to std::get
. This retrieves the element within the tuple that has that type:
#include <iostream>
#include <tuple>
int main() {
std::tuple MyTuple{42, 3.14f, true};
std::cout << "Float: "
<< std::get<float>(MyTuple);
}
Float: 3.14
This assumes the tuple has exactly one element of that type. If this is not the case, we will get a compilation error:
#include <iostream>
#include <tuple>
int main() {
// A std::tuple<float, int, float, bool>:
std::tuple MyTuple{9.8f, 42, 3.14f, true};
std::cout << "Float: "
<< std::get<float>(MyTuple);
}
error: static_assert failed: 'duplicate type T in get<T>(tuple)'
Similar to pairs, we can unpack tuple objects to dedicated variables using structured binding:
#include <tuple>
#include <iostream>
int main(){
std::tuple MyTuple{42, 3.14, "Hello"};
auto [a, b, c]{MyTuple};
std::cout << a << ", " << b << ", " << c;
}
42, 3.14, Hello
std::tie()
When the variables we want to use have already been declared, we can use std::tie()
to update them. Here, we reuse our a
, b
, and c
variables to store new values:
#include <tuple>
#include <iostream>
int main(){
using std::cout, std::tuple;
tuple MyTuple{42, 3.14, true};
auto [a, b, c]{MyTuple};
cout << a << ", " << b << ", " << c;
tuple AnotherTuple{100, 9.8, false};
std::tie(a, b, c) = AnotherTuple;
cout << '\n' << a << ", " << b << ", " << c;
}
42, 3.14, 1
100, 9.8, 0
std::ignore
When using std::tie()
, we can pass std::ignore
in any position to skip over that element in the tuple. Below, we skip over the middle element, only updating a
and c
:
#include <tuple>
#include <iostream>
int main(){
using std::cout, std::tuple, std::ignore;
tuple MyTuple{42, 3.14, true};
auto [a, b, c]{MyTuple};
cout << a << ", " << b << ", " << c;
tuple AnotherTuple{100, 9.8, false};
std::tie(a, ignore, c) = AnotherTuple;
cout << '\n' << a << ", " << b << ", " << c;
}
42, 3.14, 1
100, 3.14, 0
The <tuple>
header includes two type traits that can help us when working with std::tuple
containers at compile time. We covered type traits in detail earlier in the course:
std::tuple_size
Similar to std::array
, tuples have a fixed, static size, specified at compile time. We can get the size of a tuple by passing its type to the std::tuple_size
type trait. Usually, this is done using the shorthand std::tuple_size_v
syntax:
#include <tuple>
#include <iostream>
template <typename T>
void HandleTuple(T Tuple){
std::cout << "Tuple size: "
<< std::tuple_size_v<T>;
}
int main(){
HandleTuple(std::tuple{42, 3.14, true});
}
Tuple size: 3
std::tuple_element
The std::tuple_element
type trait allows us to determine the subtypes our tuple is storing. It receives two template parameters: the zero-based index of the position we’re querying within the tuple, and the tuple type.
Below, we get the type of the first element (index 0
) of our tuple using the shorthand syntax std::tuple_element_t
. We then compare it to the int
type using std::is_same_v
:
#include <tuple>
#include <iostream>
template <typename T>
void HandleTuple(T Tuple){
if constexpr (std::is_same_v<
std::tuple_element_t<0, T>, int>) {
std::cout <<
"The first element is an integer";
}
}
int main(){
HandleTuple(std::tuple{42, 3.14, true});
}
The first element is an integer
Tuples are not designed to be resized at run time, but we can create a new tuple by concatenating two or more tuples together using the std::tuple_cat()
function:
#include <tuple>
#include <iostream>
int main(){
std::tuple Original{42, 3.14, true};
std::tuple Additions{9.8f, "Hello"};
auto Combined{
std::tuple_cat(Original, Additions)};
std::cout << "Combined Tuple Size: "
<< std::tuple_size_v<decltype(Combined)>;
std::cout << "\nLast element: "
<< std::get<4>(Combined);
}
Combined Tuple Size: 5
Last element: Hello
The standard library’s implementation of a tuple is quite basic, with very limited capabilities. The type does not provide any assistance with iteration.
Additionally, the C++ language itself has limited support for iteration over heterogeneous collections in general. That will be addressed in future language versions, with a feature tentatively called expansion statements.
Until then, if we need that capability, we must do a little more work. If our project relies heavily on tuples, we could introduce a third-party library with a more robust implementation.
The following is an example using Boost.Hana in conjunction with a lambda expression. We cover lambda expressions in detail in our later chapter on functions:
#include <iostream>
#include <boost/hana.hpp>
namespace hana = boost::hana;
int main(){
hana::tuple Tuple{42, 3.14, true};
hana::for_each(Tuple, [](auto Object){
std::cout << Object << ", ";
});
}
42, 3.14, 1,
The following is an example of iterating over a tuple using only the standard library. It is also using more advanced functional concepts that we cover in the later chapter:
#include <tuple>
#include <iostream>
void Log(auto Object){
std::cout << Object << ", ";
}
int main(){
std::tuple MyTuple{42, 3.14f, true};
std::apply([](auto... Objects){
(Log(Objects), ...);
}, MyTuple);
}
42, 3.14, 1,
In this lesson, we covered tuples and std::tuple
, which provide a way to store and work with small collections of diverse objects. The key takeaways include:
std::tuple
is a container that stores objects of different typesstd::make_tuple()
std::get<index>()
or std::get<type>()
std::tie()
allow unpacking tuples into individual variablesstd::tuple_size
and std::tuple_element
are type traits for querying tuple propertiesstd::tuple_cat()
std::tuple
A guide to tuples and the std::tuple
container, allowing us to store objects of different types.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.