Tuples and std::tuple

A guide to tuples in C++, using the std::tuple container. Tuples allow us to store any number of objects, and those objects can have different types.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

dsg.jpg
Ryan McCombe
Ryan McCombe
Posted

We can think of a std::tuple as a simple extension of a std::pair. Whilst a pair can hold two objects, a tuple can hold any number of objects. And, like a pair, these objects can have different types.

Heterogenous Containers

In other documentation, the word heterogeneous is often used, meaning “different types”. For example, pairs and tuples are sometimes called heterogeneous containers as, unlike arrays, each object in the container can have a different type.

The std::tuple class is available from the <tuple> header. Once included, we can then create a tuple in the usual way. We pass the types of our objects as template parameters, and their values as constructor parameters:

#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 remove the need to specify types, but is less relevant now that CTAD provides the same convenience as a native feature of the language.

Tuple Member Access

Access to objects within a std::tuple is typically done through the std::get template function. We pass the index as a template parameter, and the tuple as an 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 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 parameter to std::get. This retrieves the element within the tuple that has that type:

#include <tuple>
#include <iostream>

int main(){
  std::tuple MyTuple{42, 3.14f, true};

  std::cout << std::get<float>(MyTuple);
}
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.

Structured Binding and std::tie

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

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

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

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

We have a dedicated lesson on type traits earlier in the course

Tuple Element Type

The std::tuple_element type trait allows us to determine the type of any object in the tuple. It receives two template parameters: the index of the element we’re querying within the tuple, and the tuple class.

Below, we get the type of the first element 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

Tuple Concatenation

Tuples are not designed to be resized at run time, but we can concatenate 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

Tuple Iteration

The standard library’s implementation of a tuple is a very basic implementation, 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 like a tuple. That is planned to be addressed in future versions of the language, with a feature tentatively called expansion statements.

Until then, if we need that capability, we need to do a little more work. 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 <boost/hana.hpp>
#include <iostream>

int main(){
  namespace hana = boost::hana;
  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 how to iterate 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,

Was this lesson useful?

Edit History

  • — First Published

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Using unordered sets to store unique objects

An introduction to a "hash sets" - the ideal container type for when we want to store collections of unique objects
05a.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved