An Introductory Tutorial for Modern C++

This site contains an original, self-contained guide to learning the fundamentals of Modern C++, intended to be the basis of a self-study course. Some Chapters depend upon material presented in earlier ones, so studying them linearly is recommended. There are also regular posts (accessible from the front page) which cover topics or areas of C++ not covered in the course.

If you have any queries or suggestions, please leave a comment. The course materials are in a completed state and are made freely available here:

  1. About this Tutorial
  2. String and Character Literals
  3. Variables, Scopes and Namespaces
  4. Conditions and Operators
  5. Functions
  6. Arrays, Pointers and Loops
  7. Enums and Structs
  8. Strings, Containers and Views
  9. Files and Formatting
  10. Classes, Friends and Polymorphism
  11. Templates, Exceptions, Lambdas, Smart Pointers

Download the code examples (as a zipfile) or browse on GitHub.

View details of the course topics.

Last update: 2023/05/04

Common mistakes to avoid in Modern C++ (1)

Learning a new programming language is hard, and is especially daunting when as a novice programmer the compiler rejects pretty much every program you write the first time you try to compile it. Of course, compiler errors and warnings are important as they encourage you to write correct code and ensure that your program actually runs at all, and works as expected.

C++ has traditionally been seen as one of the harder programming languages for beginners to pick up; ideally you should already have some coding experience under your belt in (an)other programming language(s) before trying to learn Modern C++. This mini-series intends to examine the parts of C++ that novice (and more experienced) programmers sometimes fail to understand fully, leading to buggy and/or sub-optimal code.

Continue reading “Common mistakes to avoid in Modern C++ (1)”

Generic classes in Modern C++

In this article we’re going to continue the theme of a class-designer’s toolkit, looking at adding generics (template functionality) to C++ classes. Templates have been around since C++98, and almost all C++ programmers will have used them: std::vector<int> has the type int as a parameter, itself being a specialization of a generic class. Any user-defined type can be used as a type parameter, having equal status to the built-in types.

Generic classes

Classes which are preceded by a the template keyword are automatically turned into generic classes. These classes can have any number of template parameters, we’re going to concentrate on classes having just one, this being a template type parameter. The class definition starts of as follows:

Continue reading “Generic classes in Modern C++”

A Modern C++ class designer’s toolkit

C++ has promoted class design throughout its history, and Modern C++ continues this tradition. Some new syntax has been added, while other syntax is used less often, or even deprecated. This article aims to summarize best practice using the latest versions of the language in one place, preferring to outline the modern variants (only) without reference to historical style.

Continue reading “A Modern C++ class designer’s toolkit”

Applications of C++23’s deducing ‘this’

All C++ programmers should understand the concept of “const-correctness” which is an additional form of type safety provided by the language. (C99 even incorporated this keyword and feature after C++ introduced it.) A common example of this feature is where classes provide accessors (getters/setters) to member data, with const member functions for the case where member data is logically immutable. A non-const-correct Matrix class allowing element access might look as follows:

Continue reading “Applications of C++23’s deducing ‘this’”

Visitor Pattern in Modern C++

The Visitor Pattern is a well-known technique of adding specific functionality to a number of possibly unrelated classes without actually modifying them significantly. If you do an online search for examples of this design pattern in C++, you may find examples with an abstract Visitor base class having a single, overloaded, pure virtual function, often called accept(). This article explores how to accomplish much the same task using compile-time polymorphism and more recent C++ syntax.

Continue reading “Visitor Pattern in Modern C++”

Know ‘constexpr’? Here’s ‘consteval’, ‘constinit’

As you may know, and as described in Chapter 4 of the Guide on this site, functions (and variables) may be declared constexpr in Modern C++. Functions declared with this keyword are guaranteed to be able to be called at compile-time, returning a result which can be used where a compile-time constant is needed. Importantly, these functions can also be called at with run-time-defined parameters, with the result of the function calculated at run-time with the same logic.

The new keyword consteval, added in C++20, can be used with function definitions to ensure that evaluation is performed at compile-time only. The restrictions are similar to those for constexpr functions (global state cannot be modified) with the added restriction that only constant parameter values are passed to the function when it is called. The following function definition is a valid consteval function:

Continue reading “Know ‘constexpr’? Here’s ‘consteval’, ‘constinit’”

C++ Iterators Primer

The C++ “Standard Containers” are meant to be accessed through iterators, which are described in the course on this site as pointer-like objects which yield an element when dereferenced. Of course, programmers coming from other language backgrounds sometimes to prefer to use indexing, at least at first, however not all container types offer a subscript operator due to implementation issues (std::list does not, for example).

The Standard Containers have always provided member functions to provide first and one-past-last elements, however since C++11 non-member functions are also available within the std namespace, and it is these which allow the functionality for range-for loops, also since C++11. The full list of functions is summarized below:

Continue reading “C++ Iterators Primer”

Uses of ‘auto’ in Modern C++

The auto keyword has been part of C++ since C++11, and its role has developed somewhat since then. This article intends to cover all the use cases of auto, including syntax that is new in the upcoming C++23 standard. The list of uses covered is:

  1. Type deduction for variable definitions
  2. References to other entities
  3. Deduction of a function’s return type
  4. Generic lambdas and functions
  5. Making a temporary copy of an entity
Continue reading “Uses of ‘auto’ in Modern C++”

Modern C++ flow diagnostic tools

Most programs produce output. (I suppose a corner-case could be a unit test framework that indicates all tests passing by not throwing an exception, whilst itself not producing any output.) A usual way of confirming a program is correct is by examining its output, this is familiar to programmers as part of the edit-compile-run-debug cycle of coding practice. (Note: I’m not including coding practices such as TDD, which impose a slightly different workflow.)

So, hands up, who’s ever done it? Of course, I refer to inserting debugging code such as std::cerr << "here1\n"; into C++ source files, when you want to ensure a certain code path has been visited. This article intends to show a way for you to never have to use such practices again, thereby avoiding the risk of evidence of debugging hacks remaining in your production code.

Continue reading “Modern C++ flow diagnostic tools”

C++ Coroutines Primer (3)

In the previous two articles of this mini-series we introduced co_await, co_yield and co_return, the last of these not taking a parameter. In this article we’ll look at the possibility of co_return actually returning a value, and the minimal boilerplate necessary to allow this functionality to work. Note that this boilerplate does become closer in complexity to Generator.hpp from the first article, so in your own projects you may prefer to start with that source file and adapt it as necessary.

Continue reading “C++ Coroutines Primer (3)”

C++ Coroutines Primer (2)

In the previous article we looked at co_yield producing a result from a coroutine and co_return (without parameter) causing an early “return” from a coroutine. In this article we’re going to look at the third C++ coroutine keyword, being co_await, and introduce the C++ boilerplate code necessary to allow it to function.

While co_await and co_yield are closely related, the semantics and meaning are slightly different: co_await says “suspend the current coroutine function until further notice, then resume at this point when directed by the caller“, while co_yield says “return this value to the caller right away, then resume at this point when another value is requested“. When control flow through a coroutine arrives at co_await a;, where a is an object we’ll call a “coroutine context”, a number of steps take place:

Continue reading “C++ Coroutines Primer (2)”

C++ Coroutines Primer (1)

Coroutines are a major addition to the C++20 language standard, and so should be interesting to anyone wishing to develop their use of Modern C++. Coroutines are different to threads (which have been standardized since C++11) and are considerably more lightweight in their Standard Library implementation, requiring just the <coroutine> header. An imperfect analogy could be that threads are similar to pre-emptive multitasking, while coroutines tend to mirror co-operative multitasking. Coroutines are not a replacement for threads, and the two can usefully co-exist; a coroutine can be paused on one thread and re-started on another, for example. An attractive feature of coroutines is that they are less susceptible to data races, deadlock, and some of the other pitfalls when using threads.

So the down side? While the change to the language itself is only the addition of three new keywords, co_await, co_yield and co_return, a large amount of boilerplate (even by C++ standards) is needed to allow them to do anything useful. There is a three-way interaction between the language keywords used in your coroutine code, the Standard Library implementation, and the boilerplate they require as the “glue” between them. This article intends to cover co_yield, with a focus on getting it to act like Python’s yield keyword when constructing generators.

Continue reading “C++ Coroutines Primer (1)”

C++ Lambdas Primer (4)

So far in this mini-series we’ve looked at capture by reference using [&], however you should know that there is another way of accessing variables from the enclosing scope called capture by copy, and this uses the syntax [=]. Can you guess the output of the program shown below?

#include <iostream>

auto f() {
    int a{ 1 };
    auto l = [=]{ std::cout << "l(): a = " << a << '\n'; };
    a = 2;
    l();
    a = 3;
    l();
    a = 4;
    return l;
}

int main() {
    f()();
}
Continue reading “C++ Lambdas Primer (4)”

C++ Lambdas Primer (3)

Often it is desirable for a lambda function to read or modify state which has to be preserved between calls. Just about the simplest example of a stateful lambda is one that adds up all the numbers it is called with, as shown in this example program:

#include <algorithm>
#include <iostream>

int main() {
    auto data = { 1.0, 2.2, 3.1, 2.1, 0.5, 1.6 };
    double sum{};
    auto sum_and_average = [&](const auto& elem){
        std::cout << elem << " ";
        sum += elem;
    };
    std::for_each(begin(data), end(data), sum_and_average);
    std::cout << "\nSum: " << sum << '\n';
}
Continue reading “C++ Lambdas Primer (3)”