Storing two values in a single variable using std::pair

An introduction to the std::pair data structure within the C++ standard library.
This lesson is part of the course:

Professional C++

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

83.jpg
Ryan McCombe
Ryan McCombe
Posted

A very common scenario we will come across is the need to store two values together. This might be to return two values from a function, to store two elements at every index of an array, or the simple desire to establish in our code that these two values are connected in some way.

Let's introduce a new Item struct that we will use for these lessons:

#include <string>
using std::string;

struct Item {
  string Name;
  int Weight;
  int Value;
};

Item HealingPotion { "Healing Potion", 2, 10 };

Imagine we were trying to create an inventory system, where each slot in our inventory can contain multiple quantities of a specific item.

So, we have two closely connected values: an Item, and how many of those items we have in our inventory. We already know we could create a struct for that:

struct InventoryItem {
  Item* Item;
  int Quantity;
}

InventoryItem Slot { &HealingPotion, 1000 };

Creating Pairs

But we can also use a more generic container. One of the most basic containers is a simple pair, available from the std namespace by including <utility>:

#include <utility>

std::pair Slot { &HealingPotion, 1000 };

In the above example, because we are initializing the pair at the same time we create it, we do not have to specify the type of each member in the pair.

We can provide the types we want (demonstrated below) but the compiler can just automatically deduce the types by the values we are providing for initialization.

In the above example, we create a pair that contains a pointer to an Item and an int. This form of automatic type inference is called "Class Template Argument Deduction", or CTAD.

If we do not provide the initial values, the data type we want to store cannot be inferred, so we would need to provide that information upfront.

For example, a pair that contains an Item pointer and int can be declared like this:

#include <utility>

std::pair<Item*, int> Slot;

CTAD is optional - we can provide both the types and initial values if we prefer:

#include <utility>

std::pair<Item*, int> Slot { &HealingPotion, 1000 };

The use of < and > in these examples indicates we are using a template. We will make use of templates provided by the standard library throughout this section, and we will learn how to create our own in the intermediate course.

As a final example of creating pairs, the following shows how we can create and return one from a function:

Item HealingPotion { "Healing Potion", 2, 10 };

std::pair<Item*, int> GetInventoryItem() {
  return { &HealingPotion, 10 }
}

auto InventoryItem { GetInventoryItem() };

Using Pairs

The first and second members of the pair are available through the member access operator . with the keys first and second respectively.

std::pair Slot { &HealingPotion, 1000 };

// Reading Values
Item& Item { Slot.first };
int Quantity { Slot.second };

int ItemValue { Slot.first->Value };
int TotalValue { Slot.first->Value * Quantity };
int TotalWeight { Slot.first->Weight * Quantity };

// Writing Values
Item ManaPotion { "Mana Potion", 1, 5 };
Slot.first = &ManaPotion;
Slot.second = 10;

Pair References and Pointers

Like with any other type, pairs can also be stored and passed around as values, references, or pointers. In the below example, we pass the pair by value, ie, we create a copy:

using std::pair, std::cout, std::endl;

void IncrementByValue(pair<Item*, int> Slot) {
  Slot.second++;
}

int main() {
  pair Slot { &HealingPotion, 100 };
  IncrementByValue(Slot);

  // Quantity is still 100 - we passed a copy of the Slot.
  cout << "Quantity: " << Slot.second << endl;
}
Quantity: 100

Like with any other type, we can create a reference to a pair using the & operator:

// A reference to a pair
// The pair stores a pointer to an Item and an integer
std::pair<Item*, int>& Slot;

Here, we pass a reference. Note the addition of the & in our parameter list:

using std::pair, std::cout, std::endl;

void IncrementByReference(pair<Item*, int>& Slot) {
  Slot.second++;
}

int main() {
  pair Slot { &HealingPotion, 100 };
  IncrementByReference(Slot);

  // Quantity is now 101 - we passed a reference to the Slot.
  cout << "Quantity: " << Slot.second << endl;
}
Quantity: 101

Finally, lets see an example using a pointer. Note the addition of the * on line 3 to indicate we're expecting a pointer, the switching of the . for the -> operator on line 4, and the use of the address-of operator & on line 9.

using std::pair, std::cout, std::endl;

void IncrementByPointer(pair<Item*, int>* Slot) {
  Slot->second++;
}

int main() {
  pair Slot { &HealingPotion, 100 };
  IncrementByPointer(&Slot);

  // Quantity is now 101 - we passed a pointer to the Slot.
  cout << "Quantity: " << Slot.second << endl;
}
Quantity: 101

The use of a pointer to a container (our pair) that itself contains a pointer (our Item) may be confusing but is not fundamentally different from what we’ve seen before.

We can get access to our Item* properties through our pair* by using the -> operator multiple times.

using std::pair;

int GetItemValue(pair<Item*, int>* Slot) {
  return Slot->first->Value;
}

As covered in our earlier lessons on pointers, we might want to ensure that a pointer is not a nullptr before trying to dereference it, to prevent exceptions:

using std::pair;

int GetItemValue(pair<Item*, int>* Slot) {
  // Ensure the std::pair* is not a nullptr
  if (!Slot) return 0;

  // Ensure the Item* is not a nullptr
  if (!Slot->first) return 0;

  return Slot->first->Value;
}

Pair Type Alias

The types of pairs can get a little verbose. As with other data structures, we can alias a pair type to a simpler, more descriptive name:

using InventorySlot = std::pair<Item*, int>*;

int GetItemValue(InventorySlot Slot) {
  if (!Slot) return 0;
  if (!Slot->first) return 0;

  return Slot->first->Value;
}

Structured Bindings with std::pair

As with classes and structs, we can use structured bindings to access the first and second elements of pairs. This allows us to use more descriptive names in our code, rather than first and second:

using std::pair;

// Using structured binding with a pair value
void BindingExample1(pair<Item*, int> Slot) {
  auto [item, quantity] = Slot;
}

// Using structured binding with a pair pointer
void BindingExample2(pair<Item*, int>* Slot) {
  auto [item, quantity] = *Slot;
}

Complete Example of std::pair

Here is a more complex example, using all the techniques we covered above:

#include <string>
#include <utility>
#include <iostream>

using std::string, std::pair, std::cout, std::endl;

struct Item {
  std::string Name;
  int Weight;
  int Value;
};

using InventorySlot = std::pair<Item*, int>*;

void LogSlotDetails(InventorySlot Slot) {
  auto [item, quantity] = *Slot;
  cout << "This slot has " << quantity
       << " " << item->Name << "s." << endl;
  cout << "Their weight is "
       << quantity * item->Weight
       << " grams." << endl;
  cout << "They are worth "
       << quantity * item->Value
       << " coins." << endl;
}

int main() {
  Item HealingPotion { "Healing Potion", 200, 5 };
  std::pair Slot { &HealingPotion, 10 };
  LogSlotDetails(&Slot);
}
This slot has 10 Healing Potions.
Their weight is 2000 grams.
They are worth 50 coins.

Was this lesson useful?

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

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.
dsg.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved