Get Started Now

### Intro to C++ Programming

Starting from the fundamentals, become a C++ software engineer, step by step.

## Creating Custom Iterators using C++20 Concepts

A detailed guide to implementing a custom iterator type from scratch, using modern recommended techniques
###### Ryan McCombe
Published

Previously, we implemented iterators for our custom container simply by forwarding the iterators from the custom type to which we were deferringÂ storage.

Below, our custom type is using a std::vector to store its objects, so we can just forward any calls to begin() and end() to that underlyingÂ array:

class Party {
public:
std::array<Character, 3> Array;
auto begin() { return Array.begin(); }
auto end() { return Array.end(); }
};


In this lesson, we will instead see how we can add iterators to our custom container ourselves, building them from scratch. This has threeÂ advantages:

• Often weâ€™ll want to implement iterators in situations where we donâ€™t have some other underlying container that can bear the burden
• Even if there is an underlying container that supports iterators, sometimes weâ€™ll want our custom typeâ€™s iterators to behave differently from those of the underlying container

Let's getÂ started!

## Partition Algorithms

An introduction to partitions, and the C++ standard library algorithms that create them
###### Ryan McCombe
Published

A collection is partitioned if similar objects are grouped together, based on some rule that separates those objects into twoÂ groups.

For example, the following collection of numbers is partitioned into even and odd numbers. All of the even numbers are grouped at the start of the container, whilst the odd are grouped at theÂ end:

4, -6, 2, 7, 1, 5


Specifically, we can imagine this partition is generated based on the followingÂ rule:

[](int x){
return x % 2 == 0;
}


Such that in our collection, every element for which this function returns true (that is, every even number) occurs before every element for which the function returns false (that is, every oddÂ number)

### Partitioning vs Sorting

Partitioning a collection closely relates to the idea of sorting it. For example, the following collection is partitioned such that all negative numbers occur before all positiveÂ numbers:

-6, -1, -2, 7, 1, 5


Sorting the collection would also generate an ordering that obeys that same partitioningÂ rule:

-6, -2, -1, 1, 5, 7


The key difference is that sorting a collection can require significantly more operations than partitioning it. As such, if our use case only requires partitioning our collection, we can get large performance benefits by not unnecessarily sortingÂ everything.

This lesson introduces the 3 main standard library algorithms we have for creating partitionedÂ collections:

• partition(): Partitions a collection in-place
• stable_partition(): Partitions a collection in place, whilst maintaining the previous relative order of elements within each partition
• partition_copy(): Partitions a collection into two new collections

We also cover two other related algorithms, that can help us when working withÂ partitions:

• is_partitioned(): Returns a boolean representing whether a collection is partitioned
• partition_point(): Determines the position within the collection where one partition ends and the other starts

All of these algorithms are within the <algorithm> header and belong to the std::rangesÂ namespace.

## Comparison Algorithms

An introduction to the 8 main comparison algorithms in the C++ standard library
###### Ryan McCombe
Published

In this lesson, we cover all of the main standard library algorithms that are used to compare twoÂ collections:

• equal(): Determine if two collections contain the same objects, in the same order
• is_permutation(): Determine if two collections contain the same objects, in any order
• mismatch(): Find the first position where two collections deviate from each other
• lexicographical_compare(): Determine if one collection is â€śless thanâ€ť another collection.
• lexicographical_compare_three_way(): Determine if one collection is less than, greater than or equal to another collection
• starts_with(): Check if the objects in one collection match the objects at the start of another collection
• ends_with(): Check if the objects in one collection match the objects at the end of another collection
• includes(): Check if one collection contains another collection - that is, if one is a subset or superset of another

Let's getÂ started!

## Fully Dynamic Types using Void Pointers and std::any

An overview of how to create variables that can store any date type, using void pointers and std::any
###### Ryan McCombe
Published

In this lesson, weâ€™ll cover the two main techniques for implementing dynamic types in C++. A variable with a dynamic type can store different types of data throughout its life. For example, it might be initialized as an int, have a float assigned to it later, and then finally changed to store a Player or some other customÂ type.

However, we should remember that C++ is designed as a statically typed language. The use of dynamic types, particularly unconstrained dynamic types, should be quiteÂ rare.

• If we donâ€™t know the type of data we are working with, but it can be deduced at compile time, templates tend to be the tool we should prefer.
• If we need dynamic types at run time, weâ€™ll often be able to narrow it down to a range of possibilities. In this case, we should generally prefer specifying those possibilities, using the techniques we covered in the previous lesson - such as std::variant.

## Use Cases

There are some use cases where neither of these options apply, and we need to create an unconstrained container. Some examplesÂ include:

• Arbitrary user data: such as creating a file manager, where we have no way of predicting what type of data the users will be providing
• Developer tools: for performance reasons, the tools that support programming in general are often written in C++. Examples include our IDE, and interpreters that implement other languages such as Python and JavaScript. We have no way to know in advance what types will be created, and many of those programming languages are designed around a dynamic type system anyway
• Message passing: components that coordinate other systems are often written in C++. An example of this is a messaging component that facilitates communication between other systems. Our component may not need to operate on these messages in any way, so attempting to restrict or coerce them into a known type is unnecessary.

To deal with these scenarios, we have void pointers, or containers that are based on void pointers. An example of such a container is the standard libraryâ€™s std::any, introduced in C++17. Weâ€™ll cover both void pointers and std::any in thisÂ lesson.

## Fold Algorithms

An introduction to the 6 new folding algorithms added in C++23, providing alternatives to std::reduce and std::accumulate
###### Ryan McCombe
Published

Similar to std::reduce() and std::accumulate() from the previous lesson, these algorithms are designed to work on collections of objects and to return a singleÂ result.

For example, were we to have a collection of objects in our shopping basket, and we wanted to total cost of all the objects, the fold algorithms areÂ ideal.

They give us some additional options over std::reduce() and std::accumulate(). Most notably, there are 8 different variants, giving us more control over how our collection isÂ combined.

Theyâ€™re also range-based algorithms, thereby giving us more control over how we define the collection of objects that we want to be theÂ input.

All the algorithms in this lesson are available within the <algorithm>Â header:

#include <algorithm>


## Reduce and Accumulate

A detailed guide to generating a single object from collections using the std::reduce and std::accumulate algorithms
###### Ryan McCombe
Published

In this lesson and the next, we introduce a range of algorithms that are designed to simplify large collections of objects into simplerÂ outputs.

For example, we might have a collection of objects representing bank transactions, and we want to generate a simple object that includes some aggregate data. That could perhaps include informationÂ like:

• What was the total amount of all transactions
• How many transactions were there
• What is the average transaction value

In this lesson, weâ€™ll introduce the reduce() and accumulate() algorithms, which remain the most popular way of implementing logic likeÂ this.

In the next lesson, weâ€™ll introduce fold algorithms, which were added to the language in C++23, and give us more ways to accomplish tasks likeÂ this.

## Ranges and Sentinels

A practical guide to defining ranges using sentinel values, and why we sometimes need to
###### Ryan McCombe
Published

In earlier lessons, we saw how any container with appropriate begin() and end() methods is a range.

Meanwhile, other pieces of code, such as algorithms, can be designed to work with ranges. This allows our code to be decoupled - an important design pattern to make complex projects moreÂ manageable.

For example, the creator of the container and the creator of the algorithm do not need to coordinate their work. If they both know what a range is, they can write their systems accordingly, and they will naturally be compatible with eachÂ other.

Below, we use a std::vector, which is a range, alongside the std::ranges::for_each() algorithm, which works with anyÂ range.

The algorithm passes every object in our range to a function, which we provide as the secondÂ argument:

#include <algorithm>
#include <iostream>
#include <vector>

void Log(int x){ std::cout << x << ", "; }

int main(){
std::vector R{1, 4, 3, 8, -2, 5};
std::ranges::for_each(R, Log);
}

1, 4, 3, 8, -2, 5,


There is a second way to define a range, which involves defining an iterator where our range starts, and a sentinel which signals when our rangeÂ ends.

Weâ€™ll cover this pattern in this lesson, and explain the reasons why we would need to useÂ it.

## List, Aggregate, and Designated Initialization

A quick guide to creating objects using lists, including std::initializer_list, aggregate and designated initialization
###### Ryan McCombe
Published

At this point, weâ€™re likely very familiar with initializing objects by specifying the type of object we want to construct, and a list of values to pass to a constructor defined as part of thatÂ type.

Typically, this looks something likeÂ this:

#include <iostream>

struct Vec3 {
// Constructor using a member initializer list
Vec3(int x, int y, int z)
: x{x}, y{y}, z{z}
{}

int x{0};
int y{0};
int z{0};

void Log(){
std::cout
<< "x=" << x
<< ", y=" << y
<< ", z=" << z << '\n';
}
};

int main(){
// Calling the constructor to create an object
Vec3 Vector{1, 2, 3};
Vector.Log();
}

x=1, y=2, z=3


This is referred to as list initialization, and the previous example is only one way to useÂ it.

In this lesson, weâ€™ll cover some of the other useful scenarios. Weâ€™ll also introduce other forms of list initialization, including aggregate initialization, and designated initialization.

## Dynamic Data Types using std::variant and Unions

A guide to storing one of several different data types within the same container, using unions and variants
###### Ryan McCombe
Published

Often, weâ€™ll come across scenarios where we need a variable to contain one of several possible data types. For example, we may need a variable that can store either an int or a float.

We could solve this problem with a class or struct, that simply stores our types as differentÂ members:

struct Number {
int i;
float f;
};


But this is a little wasteful of resources. Creating an object from this struct requires the compiler to allocate enough memory to store both an int and a float. But our use case doesnâ€™t require that - we only need to store either an int or a float.

Allocating memory for the combination of all the types is unnecessary, and it gets increasingly wasteful when our struct is declaring more, or larger,Â types.

Instead, we solve problems like these using unions.

## Working with String Views

An in-depth guide to std::string_view, including their methods, operators, and how to use them with standard library algorithms
###### Ryan McCombe
Published

In this lesson, we expand our knowledge of std::string_view by exploring its methods and operators. This includes working with the individual characters of a string view, analyzing its contents, shrinking its purview, andÂ more.

We also show some practical examples of how string views can interact with other parts of the standardÂ library.

This includes standard library views, algorithms, regular expressions, and anything that works withÂ iterators.

This builds upon our previous introduction to string views, so familiarity with what was covered there isÂ assumed:

Module 1

## Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access!

### This course includes:

• 66 Lessons
• Over 200 Quiz Questions
• Capstone Project
• Regularly Updated
• Help and FAQ
Module 2

## Making Games with SDL

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Under Construction
Module 3

## 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
Get Started Now

### Intro to C++ Programming

Starting from the fundamentals, become a C++ software engineer, step by step.