Double-Ended Queues using std::deque

A guide to double-ended queues - a structure that behaves like a vector, specialised for manipulating objects at the edges of the collection
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
Illustration representing computer hardware
Ryan McCombe
Ryan McCombe
Updated

In this lesson, we’ll introduce double-ended queues, commonly called deques. As we might expect, a double-ended queue is a queue where we can efficiently manipulate either end, typically called the "back" and the "front" of the queue.

Diagram showing a double-ended queue

The C++ standard library’s implementation of a deque is std::deque, and goes far beyond these basic capabilities. It has a full suite of features, effectively replicating the abilities of dynamic arrays, such as a std::vector.

For example, we can iterate through all the elements of a std::deque, and can directly access elements in any position using the [] operator.

Diagram showing how elements within a double-ended queue are accessed

We’ll explore all of these capabilities, and see how std::deque compares to other containers throughout this lesson.

Deque vs Vector

When reading through this lesson, it’s likely we’ll notice deques are very similar to dynamic arrays, such as a std::vector. Deques mostly provide the same capabilities and, in some ways, exceed the capabilities of dynamic arrays.

For example, deques offer a way to performantly add and remove items from the beginning of the collections, which is something arrays cannot offer.

So we may wonder why we would want to use arrays at all. We could just use deques.

As with most collection comparisons, there are trade-offs, and our choice of container depends on what factors are most important for our use case. The specific trade-offs between deques and arrays are:

  • Deques are faster at adding and removing objects at arbitrary locations. This is particularly true of locations at or near the start of the collection
  • Deques are faster at "resizing" - more on this later
  • Arrays are faster at accessing arbitrary elements, for example using the [] operator
  • Arrays consume less memory. This is especially true when the number of objects in our collection is small

In practice, the choice to use a std::deque is generally driven by a desire to have a container that behaves like a std::vector, but where we need to performantly add or remove objects near the start of the collection.

If we don't need that ability, we tend to just fall back to the simpler std::vector

Creating a std::deque

The standard library’s implementation of a deque is std::deque. available in the <deque> header.

We can create them in a similar way to other containers, by passing the type of data we’re storing as a template argument. In this case, we create a std::deque for storing int objects:

#include <deque>

int main(){
  std::deque<int> Deque;
}

We can provide an initial list of values for our std::deque:

#include <deque>

int main(){
  std::deque<int> Deque{1, 2, 3, 4, 5};
}

As we’re used to, class template argument deduction (CTAD) is supported. So, we can omit the template argument in scenarios where the compiler can infer it:

#include <deque>

int main(){
  std::deque Deque{1, 2, 3, 4, 5};
}

We can also provide a pair of iterators to specify a series of objects with which to initialize our deque.

We cover iterators in more detail later in the course, but the basic example below shows how we can initialize a deque with every value from another container:

#include <deque>
#include <vector>

int main(){
  std::vector Numbers{1, 2, 3, 4, 5};
  std::deque Deque{
    Numbers.begin(), Numbers.end()
  };
}

As of C++23, we can initialize a std::deque using a range. A range is a recent addition to the language, which we cover in more detail later in the course.

For now, we can note that most of the containers we’ve been working with so far are ranges, so we can initialize a std::deque using their values like this:

#include <deque>
#include <vector>

int main(){
  std::vector Numbers{1, 2, 3, 4, 5};
  std::deque Deque{std::from_range, Numbers};
}

std::from_range and Disambiguation Tags

The std::from_range argument in the previous example is a simple token that clarifies to the std::deque class how we’re trying to initialize the object.

Specifically, it’s a parameter that is added to a constructor to let the caller explicitly state that that is the constructor they intend to use. This technique is sometimes useful (or necessary) when we’re creating our own classes too.

An argument like this is sometimes called a disambiguation token or disambiguation tag

Iterating over a Deque

The simplest way of iterating over a deque is using a range-based for loop. We cover these in more detail later in the course, but a basic example looks like this:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};

  std::cout << "Values: ";
  for (auto i : Deque) {
    std::cout << i << ", ";
  }
}
Values: 1, 2, 3,

Accessing Front and Back Elements

As we might expect, the main use of a deque focuses on accessing and manipulating elements at either end of the collection.

front()

The front() method returns a reference to the first element in the deque:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};
  std::cout << "Front: " << Deque.front();
}
Front: 1

back()

The back() method returns a reference to the last element in the deque:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};
  std::cout << "Back: " << Deque.back();
}
Back: 3

Adding Objects to the Front and Back

Similar to other containers, std::deque gives us the ability to "emplace" and "push" objects, adding them to the collection. Given the container is double-ended, we can add elements either to the front or the back.

emplace_front()

The most efficient way of adding an object to a deque is by emplacing it. This constructs the object directly into the queue, removing the need to move or copy it into place. Any arguments we pass to an emplace function get forwarded to the constructor for the type our deque is storing.

The emplace_front() function constructs our object at the start of the collection:

#include <deque>
#include <iostream>

class Character {/*...*/}; int main(){ std::deque<Character> Deque; Deque.emplace_front("Roderick"); std::cout << "\nFront: " << Deque.front().Name; }
Constructing Roderick
Front: Roderick

push_front()

When we want to add an object that already exists to our deque, we can use push_back(). This lets us move or copy it into our collection:

#include <deque>
#include <iostream>

class Character {/*...*/}; int main(){ std::deque<Character> Deque; Character Player1{"Roderick"}; Deque.push_front(Player1); std::cout << "\nFront: " << Deque.front().Name; std::cout << "\n\n"; Character Player2{"Anna"}; Deque.push_front(std::move(Player2)); std::cout << "\nFront: " << Deque.front().Name; }
Constructing Roderick
Copying Roderick
Front: Roderick

Constructing Anna
Moving Anna
Front: Anna

We covered the mechanics of moving and copying earlier in our course, in our lessons on memory management:

emplace_back()

We can also construct objects into the back of our collection using emplace_back():

#include <deque>
#include <iostream>

class Character {/*...*/}; int main(){ std::deque<Character> Deque; Deque.emplace_back("Roderick"); std::cout << "\nBack: " << Deque.back().Name; }
Constructing Roderick
Back: Roderick

push_back()

And we can push existing objects to the back of our collection using push_back():

#include <deque>
#include <iostream>

class Character {/*...*/}; int main(){ std::deque<Character> Deque; Character Player1{"Roderick"}; Deque.push_back(Player1); std::cout << "\nBack: " << Deque.back().Name; std::cout << "\n\n"; Character Player2{"Anna"}; Deque.push_back(std::move(Player2)); std::cout << "\nBack: " << Deque.back().Name; }
Constructing Roderick
Copying Roderick
Back: Roderick

Constructing Anna
Moving Anna
Back: Anna

Prepending and Appending Ranges

As of C++23, we can now add entire ranges to the front or back of our deque. We cover ranges in more detail later, but most of the sequential containers we’ve seen so far are valid ranges. This includes deques.

The following examples show how we can push all of the content of such a container into our deque.

append_range()

The append_range() method pushes the contents of a range to the back of our deque:

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1, 2, 3};
  std::deque ExtraNumbers{4, 5, 6};

  Deque.append_range(ExtraNumbers);

  std::cout << "Values: ";
  for (auto i : Deque) {
    std::cout << i << ", ";
  }
}
Values: 1, 2, 3, 4, 5, 6,

prepend_range()

The prepend_range() method pushes the contents of a range to the front of our deque:

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1, 2, 3};
  std::deque ExtraNumbers{-2, -1, 0};

  Deque.prepend_range(ExtraNumbers);

  std::cout << "Values: ";
  for (auto i : Deque) {
    std::cout << i << ", ";
  }
}
Values: -2, -1, 0, 1, 2, 3,

Removing the First and Last Elements

Similar to regular queues, we can "pop" objects, removing them. Naturally, with double-ended queues, we can pop elements from either end.

pop_front()

The pop_front() method removes the element that is first in the queue:

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1, 2, 3};

  std::cout << "Front: " << Deque.front();
  Deque.pop_front();
  std::cout << "\nFront: " << Deque.front();
  Deque.pop_front();
  std::cout << "\nFront: " << Deque.front();
}
Front: 1
Front: 2
Front: 3

pop_back()

The pop_back() method removes the element that is last in the queue:

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1, 2, 3};

  std::cout << "Back: " << Deque.back();
  Deque.pop_back();
  std::cout << "\nBack: " << Deque.back();
  Deque.pop_back();
  std::cout << "\nBack: " << Deque.back();
}
Back: 3
Back: 2
Back: 1

Deque Size

There are two main ways we can check how many elements are in our deque

size()

The size() method returns a size_t, representing how many objects are in the collection:

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1, 2, 3};

  std::cout << "Size: " << Deque.size();
  Deque.pop_front();
  std::cout << "\nSize: " << Deque.size();
  Deque.emplace_front(1);
  std::cout << "\nSize: " << Deque.size();
}
Size: 3
Size: 2
Size: 3

empty()

If we want to know whether the collection is empty - that is, if its size() == 0 we can use the more descriptive empty() method:

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1};

  if (!Deque.empty()) {
    std::cout << "The deque is not empty\n";
  }

  Deque.pop_front();

  if (Deque.empty()) {
    std::cout << "But now it is";
  }
}
The deque is not empty
But now it is

Accessing Arbitrary Elements

Similar to arrays like std::array and std::vector, the std::deque container provides access to arbitrary elements in any position.

The [] operator

We can get a reference to an element in any position by passing the position index to the [] operator. As usual, the indexing is zero-based, that is, the element in the first position is at index 0:

Therefore, [0] is equivalent to accessing the front() element, and [1] accesses the second element. The last element, equivalent to the back() method is at index size() - 1:

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1, 2, 3};

  // Equivalent to Deque.front()
  std::cout << "First: " << Deque[0];

  std::cout << "\nSecond: " << Deque[1];

  // Equivalent to Deque.back()
  std::cout
    << "\nLast: "
    << Deque[Deque.size() - 1];
}
First: 1
Second: 2
Last: 3

The at() method

The at() method also gives us access to arbitrary elements, by passing its index as an argument. The at() method performs an additional check to ensure the index we pass is within a valid range, given the size of our deque.

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1, 2, 3};

  std::cout << "First: " << Deque.at(0);
  std::cout << "\nSecond: " << Deque.at(1);
  std::cout << "\nThird: " << Deque.at(2);
}
First: 1
Second: 2
Third: 3

The key difference between [] and at() is that If the index passed to at() is not within the bounds of our collection, an exception will be created.

We cover exceptions in more detail later in the course - for now, we can just note that they’re a way we can detect and handle error states in our programs:

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1, 2, 3};

  try { Deque.at(100); }
  catch (...) {
    std::cout << "Something went wrong...\n";
  }
  std::cout << "but we caught it and recovered";
}
Something went wrong...
but we caught it and recovered

Why at() is generally not worth using

In general, we should prefer the [] operator over the at() method. There are two reasons for this:

  • Bounds checking has a performance cost, so the at() method is slower than the [] operator
  • In most modern compilers, the [] operator performs bounds checking during development anyway

Specifically, the bounds checking of [] applies when we build our program in "debug" mode, which we typically do during development. So, when we’re testing our program, we will be notified if any of our [] operators is passed an out-of-range index, alerting us to a bug.

When we’re ready to release our program, we build it in release mode, which excludes the [] index checking in favor of performance.

So, in effect, [] gets us both benefits when they matter most. We get error checking during development, and optimal performance once our software is released.

Erasing Arbitrary Elements

We can erase one or more elements from our collection using the erase() method.

Erasing a Single Element

The erase() method requires an iterator that points to the element in the deque to erase. We cover iterators in detail later in the course, but we can effectively get an iterator to the index we want by using the begin() method, alongside the + operator to get to any index we want.

The following example creates iterators for index 0, 1, and 2:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};

  auto Iterator0 { Deque.begin() };
  auto Iterator1 { Deque.begin() + 1 };
  auto Iterator2 { Deque.begin() + 2 };
}

In this example, we apply this technique to erase the element at index 2 of our deque:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3, 4, 5};

  Deque.erase(Deque.begin() + 2);

  std::cout << "Values: ";
  for (auto i : Deque) {
    std::cout << i << ", ";
  }
}
Values: 1, 2, 4, 5,

Erasing Multiple Elements

An alternative form of the erase() method accepts a pair of iterators. Everything from the first iterator to the second iterator will be erased. The following example erases the middle three elements of our collection:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3, 4, 5};

  Deque.erase(Deque.begin() + 1,
              Deque.end() - 1);

  std::cout << "Values: ";
  for (auto i : Deque) {
    std::cout << i << ", ";
  }
}
Values: 1, 5,

Inserting Elements in any Position

We are not restricted to inserting elements just at the beginning or end of a deque. We have several options for inserting elements into any part of the deque.

emplace()

The emplace() method constructs objects in place. Where this is possible, it is the preferred method, as it minimizes the performance overhead of moving or copying objects.

The first argument to emplace() is an iterator denoting where we want the object to be constructed. Any remaining arguments are forwarded to the constructor of the type our deque is storing.

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{100, 300};

  Deque.emplace(Deque.begin() + 1, 200);

  std::cout << "Values: ";
  for (int x : Deque) {
    std::cout << x << ", ";
  }
}
Values: 100, 200, 300,

Below, we have a more complex example, where we’re constructing an object of a custom type:

#include <deque>
#include <iostream>

class Character {/*...*/}; int main(){ std::deque<Character> Deque; Deque.emplace_back("Player 1"); Deque.emplace_back("Player 3"); Deque.emplace(Deque.begin() + 1, "Player 2"); std::cout << "Values: "; for (const Character& C : Deque) { std::cout << C.Name << ", "; } }
Values: Player 1, Player 2, Player 3,

insert() a single object

When we want to add an object that already exists to our deque, we can use the insert() method. The first argument will be an iterator pointing to where we want the object to be inserted. The second argument will be the object we want to copy or move.

In the following example, we use the insert() method to copy one object into our deque, and to move another.

#include <deque>
#include <iostream>

class Character {/*...*/}; int main(){ std::deque<Character> Deque; Deque.emplace_back("Player 1"); Deque.emplace_back("Player 4"); // Copying a Character into the deque Character Player2{"Player 2"}; Deque.insert(Deque.begin() + 1, Player2); // Moving a Character into the deque Character Player3{"Player 3"}; Deque.insert(Deque.begin() + 2, std::move(Player3)); std::cout << "Values:\n"; for (const Character& C : Deque) { std::cout << C.Name << '\n'; } }
Values:
Player 1
Player 2
Player 3
Player 4

insert() multiple copies of an object

Another form of the insert() method allows us to insert multiple copies of an object. It uses three arguments:

  1. An iterator to the position we want to insert the object
  2. An integer representing the number of copies we want to insert
  3. The object we want to copy

In the following example, we insert 2 copies of the 10 object, starting from index 1 of our deque:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};

  Deque.insert(Deque.begin() + 1, 2, 10);

  std::cout << "Values: ";
  for (int x : Deque) {
    std::cout << x << ", ";
  }
}
Values: 1, 10, 10, 2, 3,

insert() multiple objects

The insert() method also allows us to pass two additional iterators as the second and third arguments. Objects from the second iterator to the third iterator will be inserted into our deque.

Below, we select the middle three elements of our Extra collection, and insert them into our Deque collection, starting at index 1:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 5, 6};
  std::deque Extra{1, 2, 3, 4, 5};

  Deque.insert(Deque.begin() + 1,
               Extra.begin() + 1,
               Extra.end() - 1);

  std::cout << "Values: ";
  for (int x : Deque) {
    std::cout << x << ", ";
  }
}
Values: 1, 2, 3, 4, 5, 6,

insert_range()

As of C++23, we can insert a range into our deque, using the insert_range() method. The first argument is an iterator denoting where we want the range to be inserted, and the second argument is the range we want to insert.

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 5, 6};
  std::deque Extra{2, 3, 4};

  Deque.insert_range(Deque.begin() + 1, Extra);

  std::cout << "Values: ";
  for (int x : Deque) {
    std::cout << x << ", ";
  }
}
Values: 1, 2, 3, 4, 5, 6,

Replacing Contents

We have a few options that let us replace the entire contents of our deque with a new set of objects.

assign()

We can replace the contents in our std::deque by passing a list of new objects to the assign() method:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};

  Deque.assign({4, 5, 6});

  std::cout << "Values: ";
  for (int x : Deque) {
    std::cout << x << ", ";
  }
}
Values: 4, 5, 6,

An alternative form of the assign() method accepts two arguments - a quantity and an object. Our deque will then be updated to contain that quantity of copies of the object. In the following example, we set our deque to contain 5 copies of the 2 object:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};

  Deque.assign(5, 2);

  std::cout << "Values: ";
  for (int x : Deque) {
    std::cout << x << ", ";
  }
}
Values: 2, 2, 2, 2, 2,

The assign method can also receive a pair of iterators, replacing the deque contents with everything from the first iterator until the second iterator.

Below, we update our Deque to contain the middle 3 objects from the Replacements collection:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};
  std::deque Replacements{1, 2, 3, 4, 5};

  Deque.assign(Replacements.begin() + 1,
               Replacements.end() - 1);

  std::cout << "Values: ";
  for (int x : Deque) {
    std::cout << x << ", ";
  }
}
Values: 2, 3, 4,

assign_range()

As of C++23, we can replace the contents of a deque with the contents of any range, using the assign_range() method:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};
  std::deque Replacements{4, 5, 6};

  Deque.assign_range(Replacements);

  std::cout << "Values: ";
  for (int x : Deque) {
    std::cout << x << ", ";
  }
}
Values: 4, 5, 6,

clear()

Everything in our deque can be removed using the clear() method:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};

  Deque.clear();

  if (Deque.empty()) {
    std::cout << "The deque is empty";
  }
}
The deque is empty

swap()

The swap() method switches the content within two deque objects. This is highly performant, and one of the most efficient ways of moving a std::deque.

#include <deque>
#include <iostream>

int main(){
  std::deque A{1, 2, 3};
  std::deque B{4, 5, 6};

  A.swap(B);

  std::cout << "A Values: ";
  for (int x : A) { std::cout << x << ", "; }

  std::cout << "\nB Values: ";
  for (int x : B) { std::cout << x << ", "; }
}
A Values: 4, 5, 6,
B Values: 1, 2, 3,

Memory Management

Similar to std::vector, the std::deque container includes some additional methods to provide some high-level memory management.

resize()

The resize() method changes the number of objects our deque is holding. If we resize() the container to be smaller than its current size, we are effectively pruning elements from the end of the container:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};

  std::cout << "Size: " << Deque.size();
  Deque.resize(1); 
  std::cout << "\nNew Size: " << Deque.size();
  std::cout << "\nValue: " << Deque.front();
}
Size: 3
New Size: 1
Value: 1

If we’re resizing the deque to be bigger than its current size, the excess space will be filled with default-constructed objects of the type our deque is holding.

We can customize this by passing a second argument to the resize() function, passing a different value to be used instead.

Below, we resize our deque from 3 to 5, and use the value 10 to fill the additional two spaces:

#include <deque>
#include <iostream>

int main(){
  std::deque Deque{1, 2, 3};

  std::cout << "Size: " << Deque.size();

  Deque.resize(5, 10); 

  std::cout << "\nNew Size: " << Deque.size();

  std::cout << "\nValues: ";
  for (int x : Deque) {
    std::cout << x << ", ";
  }
}
Size: 3
New Size: 5
Values: 1, 2, 3, 10, 10,

When should I Resize a std::deque?

In our lesson on std::vector, we discussed how, when we add an item to the collection, the vector may not have enough capacity. Behind the scenes, it will need to move everything to a new, larger block of memory to accommodate this new object. Preemptively resizing the vector could reduce the need for these expensive reallocations.

std::deque does not store its objects in contiguous memory. Because of this, manually resizing a std::deque is less common, because the same performance concerns don’t apply.

However, there are other reasons why we might need to resize a std::deque. For example, some algorithms will populate our containers with objects, and they require our containers to have enough capacity to accommodate those objects.

For example, the std::ranges::copy algorithm copies objects from a source location to a destination location. It assumes the destination has enough capacity, so we resize() our deque to ensure this prerequisite is met:

#include <algorithm>
#include <deque>

int main(){
  std::deque Source{1, 2, 3, 4, 5};
  std::deque<int> Dest;

  Dest.resize(Source.size());
  std::ranges::copy(Source, Dest.begin());
}

We cover standard library algorithms and how to use them in more detail later in the course.

max_size()

The max_size() method returns a size_t representing the maximum number of objects our std::deque can hold in the current environment.

In most scenarios, this is likely to be a very big number and not something we need to worry about. But, in some environments or use cases, it’s worth remembering that constraints like this do exist.

#include <iostream>
#include <deque>

int main(){
  std::deque Deque{1, 2, 3};
  std::cout << "Max Size: " << Deque.max_size();
}
Max Size: 4611686018427387903

shrink_to_fit()

Finally, we have the shrink_to_fit() method. This requests that the std::deque free any memory it is not using.

In general, this is almost never required. Behind the scenes, deques are much more proactive about managing their memory than other containers such as std::vector. As such, they rarely get into a state where they’re holding significantly more memory than they need.

But, in memory-constrained environments, we still have the option to manually intervene if needed:

#include <deque>

int main(){
  std::deque<int> Deque;
  for (int i{0}; i < 1'000; ++i) {
    Deque.push_back(i);
  }

  for (int i{0}; i < 900; ++i) {
    Deque.pop_back();
  }

  Deque.shrink_to_fit();
}

Summary

In this lesson, we've explored the std::deque container in C++, which provides a double-ended queue. We've seen how to create deques, manipulate elements at both ends, and access elements at arbitrary positions. Key takeaways:

  • std::deque is a container adapter that provides efficient insertion and deletion at both ends
  • Elements in a std::deque are not stored contiguously in memory
  • Elements can be inserted at the front using push_front() and emplace_front(), and at the back using push_back() and emplace_back()
  • Entire ranges can be inserted at the front using prepend_range() and at the back using append_range()
  • Elements can be removed from the front using pop_front() and from the back using pop_back()
  • The size of a std::deque can be checked using size() and empty()
  • Random access to elements is provided through the [] operator and the at() method
  • Elements can be erased at arbitrary positions using erase()
  • Elements can be inserted at arbitrary positions using insert() and emplace()
  • The contents of a std::deque can be replaced using assign(), assign_range(), or clear()
  • The size of a std::deque can be changed using resize(), and its capacity can be optimized using shrink_to_fit()
  • The maximum size of a std::deque is given by max_size(), which depends on the system's memory constraints

Was this lesson useful?

Next Lesson

First Class Functions

Learn about first-class functions in C++: a feature that lets you store functions in variables, pass them to other functions, and return them, opening up new design possibilities
3D Character Concept Art
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
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
Standard Library Data Structures
Next Lesson

First Class Functions

Learn about first-class functions in C++: a feature that lets you store functions in variables, pass them to other functions, and return them, opening up new design possibilities
3D Character Concept Art
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved