About this book
The C++20 STL Cookbook provides recipes to help you get the most out of the C++ STL (Standard Template Library), including new features introduced with C++20.
C++ is a rich and powerful language. Built upon C, with syntactic extensions for type safety, generic programming, and object-oriented programming, C++ is essentially a low-level language. The STL provides a broad set of higher-level classes, functions, and algorithms to make your programming job easier, more effective, and less prone to error.
I've often said that C++ is five languages cobbled into one. The formal specification includes 1) the entire C language, 2) C's cryptic-yet-powerful macro preprocessor, 3) a feature-rich class/object model, 4) a generic programming model called templates, and finally, built upon C++ classes and templates, 5) the STL.
Prerequisite knowledge
This book presumes that you have a basic understanding of C++, including syntax, structure, data types, classes and objects, templates, and the STL.
The recipes and examples in this book presume that you understand the need to #include
certain headers to use library functions. The recipes don't usually list all the necessary headers, preferring to focus on the techniques at hand. You're encouraged to download the example code, which has all the necessary #include
directives and other front matter.
You may download the example code from GitHub: https://github.com/PacktPublishing/CPP-20-STL-Cookbook.
These assumptions mean that when you see a piece of code like this:
cout << "hello, world\n";
You should already know that you'll need to put this code in a main()
function, you'll need to #include
the <iostream>
header, and cout
is an object in the std::
namespace:
#include <iostream> int main() { std::cout << "hello, world\n"; }
The STL's power is derived from templates (a brief primer)
Templates are how C++ does generic programming, code that's independent of type while retaining type safety. C++ templates allow you to use tokens as placeholders for types and classes, like this:
template<typename T> T add_em_up(T& lhs, T& rhs) { return lhs + rhs; }
A template may be used for classes and/or functions. In this template function, the T
represents a generic type, which allows this code to be used in the context of any compatible class or type:
int a{ 72 }; // see braced initialization below int b{ 47 }; cout << add_em_up<int>(a, b) << "\n";
This invokes the template function with an int
type. This same code can be used with any type or class that supports the +
operator.
When the compiler sees a template invocation, like add_em_up<int>(a, b)
, it creates a specialization. This is what makes the code type safe. When you invoke add_em_up()
with an int
type, the specialization will look something like this:
int add_em_up(int& lhs, int& rhs) { return lhs + rhs; }
The specialization takes the template and replaces all instances of the T
placeholder with the type from the invocation, in this case, int
. The compiler creates a separate specialization of the template each time it's invoked with a different type.
STL containers, like vector
, stack
, or map
, along with their iterators and other supporting functions and algorithms, are built with templates so they can be used generically while maintaining type safety. This is what makes the STL so flexible. Templates are the T in the STL.
This book uses the C++20 standard
The C++ language is standardized by the International Organization for Standardization (ISO) on a roughly three-year cycle. The current standard is called C++20 (which was preceded by C++17, C++14, and C++11 before that). C++20 was approved in September 2020.
C++20 adds many important features to the language and the STL. New features like format, modules, ranges, and more will have significant impact on the way we use the STL.
There are also convenience changes. For example, if you want to remove every matching element of a vector
, you may have been using the erase-remove idiom like this:
auto it = std::remove(vec1.begin(), vec1.end(), value); vec1.erase(it, vec1.end());
Starting with C++20 you can use the new std::erase
function and do all of that in one simple, optimized function call:
std::erase(vec1, value);
C++20 has many improvements, both subtle and substantial. In this book, we will cover much of it, especially what's relevant to the STL.
Braced initialization
You may notice that the recipes in this book often use braced initialization in place of the more familiar copy initialization.
std::string name{ "Jimi Hendrix" }; // braced initialization std::string name = "Jimi Hendrix"; // copy initialization
The =
operator pulls double-duty as both an assignment and a copy operator. It's common, familiar, and it works, so we've all been using it forever.
The downside of the =
operator is that it's also a copy constructor, which often means implicit narrowing conversion. This is both inefficient and can lead to unintended type conversions, which can be difficult to debug.
Braced initialization uses the list initialization operator {}
(introduced in C++11) to avoid those side effects. It's a good habit to get into and you'll see it a lot in this book.
It's also worth noting that the special case of T{}
is guaranteed to be zero-initialized.
int x; // uninitialized bad :( int x = 0; // zero (copy constructed) good :) int x{}; // zero (zero-initialized) best :D
The empty brace zero initialization offers a useful shortcut for initializing new variables.
Hiding the std:: namespace
In most instances, the exercises in this book will hide the std::
namespace. This is mostly for page space and readability considerations. We all know that most STL identifiers are in the std::
namespace. I will normally use some form of the using
declaration to avoid cluttering the examples with repetitive prefixes. For example, when using cout
you can presume I've included a using
declaration like this:
using std::cout; // cout is now sans prefix cout << "Hello, Jimi!\n";
I usually will not show the using
declaration in the recipe listings. This allows us to focus on the purpose of the example.
It is poor practice to import the entire std::
namespace in your code. You should avoid a using namespace
declaration like this:
using namespace std; // bad. don't do that. cout << "Hello, Jimi!\n";
The std::
namespace includes thousands of identifiers and there's no good reason to clutter your namespace with them. The potential for collisions is not trivial, and can be hard to track down. When you want to use a name without the std::
prefix, the preferred method is to import a single name at a time, as above.
To further avoid namespace collisions, I often use a separate namespace for classes that will be re-used. I tend to use namespace bw
for my personal namespace. You may use something else that works for you.
Type aliases with using
This book uses the using
directive for type aliases instead of typedef
.
STL classes and types can be verbose at times. A templated iterator class, for example, may look like this:
std::vector<std::pair<int,std::string>>::iterator
Long type names are not just hard to type, they are prone to error.
One common technique is to abbreviate long type names with typedef
:
typedef std::vector<std::pair<int,std::string>>::iterator vecit_t
This declares an alias for our unwieldy iterator type. typedef
is inherited from C and its syntax reflects that.
Beginning with C+11, the using
keyword may be used to create a type alias:
using vecit_t = std::vector<std::pair<int,std::string>>::iterator;
In most circumstances, a using
alias is equivalent to typedef
. The most significant difference is that a using
alias may be templated:
template<typename T> using v = std::vector<T>; v<int> x{};
For these reasons, and for the sake of clarity, this book prefers the using
directive for type aliases.
Abbreviated function templates
Beginning with C++20, an abbreviated function template may be specified without the template header. For example:
void printc(const auto& c) { for (auto i : c) { std::cout << i << '\n'; } }
The auto
type in a parameter list works like an anonymous template typename
. It is equivalent to:
template<typename C> void printc(const C& c) { for (auto i : c) { std::cout << i << '\n'; } }
Though new in C++20, abbreviated function templates have been supported by the major compilers for some time already. This book will use abbreviated function templates in many of the examples.
The C++20 format() function
Until C++20 we've had a choice of using legacy printf()
or the STL cout
for formatting text. Both have serious flaws but we've used them because they work. Beginning with C++20, the format()
function provides text formatting inspired by Python 3's formatter.
This course uses the new STL format()
function liberally. Please see Chaper 1, New C++20 Features, for a more comprehensive description.
Use the STL to solve real-world problems
The recipes in this book use the STL to provide real-world solutions to real-world problems. They have been designed to rely exclusively on the STL and C++ standard libraries, with no external libraries. This should make it easy for you to experiment and learn without the distractions of installing and configuring third-party code.
Now, let's go have some fun with the STL. Happy learning!