0% found this document useful (0 votes)
35 views212 pages

Modern C++ Handbooks - Advanced Modern C++ Techniques

The document is a handbook on advanced modern C++ techniques, targeting advanced learners and professionals. It covers a range of topics including templates, concurrency, error handling, and advanced libraries, with practical applications and advanced techniques discussed throughout. The content is structured into sections that delve into specific areas of C++ programming, providing in-depth knowledge and examples.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
35 views212 pages

Modern C++ Handbooks - Advanced Modern C++ Techniques

The document is a handbook on advanced modern C++ techniques, targeting advanced learners and professionals. It covers a range of topics including templates, concurrency, error handling, and advanced libraries, with practical applications and advanced techniques discussed throughout. The content is structured into sections that delve into specific areas of C++ programming, providing in-depth knowledge and examples.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 212

Modern C++ Handbooks:

Advanced Modern C++ Techniques

Prepared by Ayman Alheraki


Target Audience: Advanced learners and professionals.
simplifycpp.org

January 2025
Contents

Contents 2

Modern C++ Handbooks 7

1 Templates and Metaprogramming 19


1.1 Function and Class Templates . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.1.1 Introduction to Templates in C++ . . . . . . . . . . . . . . . . . . . . 19
1.1.2 Function Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.1.3 Class Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.1.4 Template Specialization . . . . . . . . . . . . . . . . . . . . . . . . . 25
1.1.5 Non-Type Template Parameters . . . . . . . . . . . . . . . . . . . . . 26
1.1.6 Variadic Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
1.1.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
1.2 Variadic Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
1.2.1 Introduction to Variadic Templates . . . . . . . . . . . . . . . . . . . . 29
1.2.2 Basics of Variadic Templates . . . . . . . . . . . . . . . . . . . . . . . 29
1.2.3 Parameter Packs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
1.2.4 Fold Expressions (C++17) . . . . . . . . . . . . . . . . . . . . . . . . 32
1.2.5 Recursive Template Instantiation . . . . . . . . . . . . . . . . . . . . . 33
1.2.6 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 35

2
3

1.2.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
1.3 Type Traits and std::enable if . . . . . . . . . . . . . . . . . . . . . . . 38
1.3.1 Introduction to Type Traits and std::enable if . . . . . . . . . . 38
1.3.2 Type Traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
1.3.3 std::enable if . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
1.3.4 Practical Applications . . . . . . . . . . . . . . . . . . . . . . . . . . 44
1.3.5 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 47
1.3.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
1.4 Concepts and Constraints (C++20) . . . . . . . . . . . . . . . . . . . . . . . . 50
1.4.1 Introduction to Concepts and Constraints . . . . . . . . . . . . . . . . 50
1.4.2 What Are Concepts? . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
1.4.3 What Are Constraints? . . . . . . . . . . . . . . . . . . . . . . . . . . 52
1.4.4 Standard Concepts (C++20) . . . . . . . . . . . . . . . . . . . . . . . 53
1.4.5 Combining Concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
1.4.6 Practical Applications . . . . . . . . . . . . . . . . . . . . . . . . . . 55
1.4.7 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 58
1.4.8 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

2 Concurrency and Parallelism 62


2.1 Threading (std::thread, std::async) . . . . . . . . . . . . . . . . . . 62
2.1.1 Introduction to Threading in C++ . . . . . . . . . . . . . . . . . . . . 62
2.1.2 std::thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
2.1.3 std::async . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
2.1.4 Practical Applications . . . . . . . . . . . . . . . . . . . . . . . . . . 67
2.1.5 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 70
2.1.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
2.2 Synchronization (std::mutex, std::atomic) . . . . . . . . . . . . . . . 75
2.2.1 Introduction to Synchronization in C++ . . . . . . . . . . . . . . . . . 75
4

2.2.2 std::mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
2.2.3 std::lock guard and std::unique lock . . . . . . . . . . . 78
2.2.4 std::atomic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
2.2.5 Practical Applications . . . . . . . . . . . . . . . . . . . . . . . . . . 82
2.2.6 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 86
2.2.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
2.3 Coroutines (C++20) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
2.3.1 Introduction to Coroutines in C++ . . . . . . . . . . . . . . . . . . . . 90
2.3.2 What Are Coroutines? . . . . . . . . . . . . . . . . . . . . . . . . . . 90
2.3.3 Coroutine Components . . . . . . . . . . . . . . . . . . . . . . . . . . 92
2.3.4 Writing Coroutines . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
2.3.5 Practical Applications . . . . . . . . . . . . . . . . . . . . . . . . . . 95
2.3.6 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 100
2.3.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103

3 Error Handling 104


3.1 Exceptions and noexcept . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
3.1.1 Introduction to Error Handling in C++ . . . . . . . . . . . . . . . . . . 104
3.1.2 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
3.1.3 noexcept Specifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
3.1.4 Practical Applications . . . . . . . . . . . . . . . . . . . . . . . . . . 109
3.1.5 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 112
3.1.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
3.2 std::optional, std::expected (C++23) . . . . . . . . . . . . . . . . 116
3.2.1 Introduction to std::optional and std::expected . . . . . . 116
3.2.2 std::optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
3.2.3 std::expected (C++23) . . . . . . . . . . . . . . . . . . . . . . . 119
3.2.4 Practical Applications . . . . . . . . . . . . . . . . . . . . . . . . . . 122
5

3.2.5 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 125


3.2.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128

4 Advanced Libraries 129


4.1 Filesystem Library (std::filesystem) . . . . . . . . . . . . . . . . . . . 129
4.1.1 Introduction to the Filesystem Library . . . . . . . . . . . . . . . . . . 129
4.1.2 Key Components of std::filesystem . . . . . . . . . . . . . . . 130
4.1.3 File and Directory Operations . . . . . . . . . . . . . . . . . . . . . . 131
4.1.4 File Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
4.1.5 Querying File Properties . . . . . . . . . . . . . . . . . . . . . . . . . 136
4.1.6 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 138
4.1.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
4.2 Networking (C++20 and Beyond) . . . . . . . . . . . . . . . . . . . . . . . . 143
4.2.1 Introduction to Networking in C++ . . . . . . . . . . . . . . . . . . . 143
4.2.2 Key Components of the Networking Library . . . . . . . . . . . . . . 143
4.2.3 Synchronous Networking . . . . . . . . . . . . . . . . . . . . . . . . . 145
4.2.4 Asynchronous Networking . . . . . . . . . . . . . . . . . . . . . . . . 148
4.2.5 Advanced Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . 152
4.2.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156

5 Practical Examples 157


5.1 Advanced Programs (e.g., Multithreaded Applications, Template
Metaprogramming) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
5.1.1 Introduction to Advanced C++ Programming . . . . . . . . . . . . . . 157
5.1.2 Multithreaded Applications . . . . . . . . . . . . . . . . . . . . . . . 158
5.1.3 Template Metaprogramming . . . . . . . . . . . . . . . . . . . . . . . 163
5.1.4 Practical Applications . . . . . . . . . . . . . . . . . . . . . . . . . . 166
5.1.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
6

6 Lock-free and Memory Management 171


6.1 Lock-Free Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
6.1.1 Foundations of Lock-Free Programming . . . . . . . . . . . . . . . . . 172
6.1.2 Key Concepts in Lock-Free Programming . . . . . . . . . . . . . . 173
6.1.3 Designing Lock-Free Data Structures . . . . . . . . . . . . . . . . . 175
6.1.4 Challenges and Pitfalls . . . . . . . . . . . . . . . . . . . . . . . . . 179
6.1.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
6.2 Custom Memory Management . . . . . . . . . . . . . . . . . . . . . . . . . . 181
6.2.1 Why Custom Memory Management? . . . . . . . . . . . . . . . . . 181
6.2.2 Techniques for Custom Memory Management . . . . . . . . . . . . 182
6.2.3 Memory Reclamation in Lock-Free Programming . . . . . . . . . . . . 188
6.2.4 Practical Considerations . . . . . . . . . . . . . . . . . . . . . . . . . 191
6.2.5 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192

Appendices 193
Appendix A: Advanced C++ Features and Techniques . . . . . . . . . . . . . . . 193
Appendix B: Concurrency and Parallelism . . . . . . . . . . . . . . . . . . . . . . 196
Appendix C: Performance Optimization Techniques . . . . . . . . . . . . . . . . 198
Appendix D: Advanced STL and Custom Containers . . . . . . . . . . . . . . . . 199
Appendix E: Cross-Platform Development . . . . . . . . . . . . . . . . . . . . . . 201
Appendix F: Case Studies and Real-World Examples . . . . . . . . . . . . . . . . 202
Appendix G: Further Reading and Resources . . . . . . . . . . . . . . . . . . . . 203

References 204
Modern C++ Handbooks

Introduction to the Modern C++ Handbooks Series


I am pleased to present this series of booklets, compiled from various sources, including books,
websites, and GenAI (Generative Artificial Intelligence) systems, to serve as a quick reference
for simplifying the C++ language for both beginners and professionals. This series consists of
10 booklets, each approximately 150 pages, covering essential topics of this powerful language,
ranging from beginner to advanced levels. I hope that this free series will provide significant
value to the followers of https://simplifycpp.org and align with its strategy of simplifying C++.
Below is the index of these booklets, which will be included at the beginning of each booklet.

Book 1: Getting Started with Modern C++


• Target Audience: Absolute beginners.

• Content:

– Introduction to C++:

* What is C++? Why use Modern C++?


* History of C++ and the evolution of standards (C++11 to C++23).
– Setting Up the Environment:

* Installing a modern C++ compiler (GCC, Clang, MSVC).

7
8

* Setting up an IDE (Visual Studio, CLion, VS Code).


* Using CMake for project management.

– Writing Your First Program:

* Hello World in Modern C++.


* Understanding main(), #include, and using namespace std.

– Basic Syntax and Structure:

* Variables and data types (int, double, bool, auto).


* Input and output (std::cin, std::cout).
* Operators (arithmetic, logical, relational).

– Control Flow:

* if, else, switch.


* Loops (for, while, do-while).

– Functions:

* Defining and calling functions.


* Function parameters and return values.
* Inline functions and constexpr.

– Practical Examples:

* Simple programs to reinforce concepts (e.g., calculator, number guessing game).

– Debugging and Version Control:

* Debugging basics (using GDB or IDE debuggers).


* Introduction to version control (Git).
9

Book 2: Core Modern C++ Features (C++11 to C++23)


• Target Audience: Beginners and intermediate learners.

• Content:

– C++11 Features:

* auto keyword for type inference.


* Range-based for loops.
* nullptr for null pointers.
* Uniform initialization ({} syntax).
* constexpr for compile-time evaluation.
* Lambda expressions.
* Move semantics and rvalue references (std::move, std::forward).
– C++14 Features:

* Generalized lambda captures.


* Return type deduction for functions.
* Relaxed constexpr restrictions.
– C++17 Features:

* Structured bindings.
* if and switch with initializers.
* inline variables.
* Fold expressions.
– C++20 Features:

* Concepts and constraints.


10

* Ranges library.
* Coroutines.
* Three-way comparison (<=> operator).
– C++23 Features:

* std::expected for error handling.


* std::mdspan for multidimensional arrays.
* std::print for formatted output.
– Practical Examples:

* Programs demonstrating each feature (e.g., using lambdas, ranges, and


coroutines).

– Features and Performance :

* Best practices for using Modern C++ features.


* Performance implications of Modern C++.

Book 3: Object-Oriented Programming (OOP) in Modern C++


• Target Audience: Intermediate learners.

• Content:

– Classes and Objects:

* Defining classes and creating objects.


* Access specifiers (public, private, protected).
– Constructors and Destructors:

* Default, parameterized, and copy constructors.


11

* Move constructors and assignment operators.


* Destructors and RAII (Resource Acquisition Is Initialization).
– Inheritance and Polymorphism:

* Base and derived classes.


* Virtual functions and overriding.
* Abstract classes and interfaces.
– Advanced OOP Concepts:

* Multiple inheritance and virtual base classes.


* override and final keywords.
* CRTP (Curiously Recurring Template Pattern).
– Practical Examples:

* Designing a class hierarchy (e.g., shapes, vehicles).


– Design patterns:

* Design patterns in Modern C++ (e.g., Singleton, Factory, Observer).

Book 4: Modern C++ Standard Library (STL)


• Target Audience: Intermediate learners.

• Content:

– Containers:

* Sequence containers (std::vector, std::list, std::deque).


* Associative containers (std::map, std::set, std::unordered map).
12

* Container adapters (std::stack, std::queue,


std::priority queue).

– Algorithms:

* Sorting, searching, and modifying algorithms.


* Parallel algorithms (C++17).
– Utilities:

* Smart pointers (std::unique ptr, std::shared ptr,


std::weak ptr).

* std::optional, std::variant, std::any.


* std::function and std::bind.
– Iterators and Ranges:

* Iterator categories.
* Ranges library (C++20).
– Practical Examples:

* Programs using STL containers and algorithms (e.g., sorting, searching).


– Allocators and Benchmarks :

* Custom allocators.
* Performance benchmarks.

Book 5: Advanced Modern C++ Techniques


• Target Audience: Advanced learners and professionals.

• Content:
13

– Templates and Metaprogramming:

* Function and class templates.


* Variadic templates.
* Type traits and std::enable if.
* Concepts and constraints (C++20).
– Concurrency and Parallelism:

* Threading (std::thread, std::async).


* Synchronization (std::mutex, std::atomic).
* Coroutines (C++20).
– Error Handling:

* Exceptions and noexcept.


* std::optional, std::expected (C++23).
– Advanced Libraries:

* Filesystem library (std::filesystem).


* Networking (C++20 and beyond).
– Practical Examples:

* Advanced programs (e.g., multithreaded applications, template


metaprogramming).
– Lock-free and Memory Management:

* Lock-free programming.
* Custom memory management.

Book 6: Modern C++ Best Practices and Principles


• Target Audience: Professionals.
14

• Content:

– Code Quality:

* Writing clean and maintainable code.


* Naming conventions and coding standards.

– Performance Optimization:

* Profiling and benchmarking.


* Avoiding common pitfalls (e.g., unnecessary copies).

– Design Principles:

* SOLID principles in Modern C++.


* Dependency injection.

– Testing and Debugging:

* Unit testing with frameworks (e.g., Google Test).


* Debugging techniques and tools.

– Security:

* Secure coding practices.


* Avoiding vulnerabilities (e.g., buffer overflows).

– Practical Examples:

* Case studies of well-designed Modern C++ projects.

– Deployment (CI/CD):

* Continuous integration and deployment (CI/CD).


15

Book 7: Specialized Topics in Modern C++


• Target Audience: Professionals.

• Content:

– Scientific Computing:

* Numerical methods and libraries (e.g., Eigen, Armadillo).


* Parallel computing (OpenMP, MPI).
– Game Development:

* Game engines and frameworks.


* Graphics programming (Vulkan, OpenGL).
– Embedded Systems:

* Real-time programming.
* Low-level hardware interaction.
– Practical Examples:

* Specialized applications (e.g., simulations, games, embedded systems).


– Optimizations:

* Domain-specific optimizations.

Book 8: The Future of C++ (Beyond C++23)


• Target Audience: Professionals and enthusiasts.

• Content:
16

– Upcoming features in C++26 and beyond.


– Reflection and metaclasses.
– Advanced concurrency models.
– Experimental and Developments:

* Experimental features and proposals.


* Community trends and developments.

Book 9: Advanced Topics in Modern C++


• Target Audience: Experts and professionals.

• Content:

– Template Metaprogramming:

* SFINAE and std::enable if.


* Variadic templates and parameter packs.
* Compile-time computations with constexpr.
– Advanced Concurrency:

* Lock-free data structures.


* Thread pools and executors.
* Real-time concurrency.
– Memory Management:

* Custom allocators.
* Memory pools and arenas.
* Garbage collection techniques.
17

– Performance Tuning:

* Cache optimization.
* SIMD (Single Instruction, Multiple Data) programming.
* Profiling and benchmarking tools.
– Advanced Libraries:

* Boost library overview.


* GPU programming (CUDA, SYCL).
* Machine learning libraries (e.g., TensorFlow C++ API).
– Practical Examples:

* High-performance computing (HPC) applications.


* Real-time systems and embedded applications.
– C++ projects:

* Case studies of cutting-edge C++ projects.

Book 10: Modern C++ in the Real World


• Target Audience: Professionals.

• Content:

– Case Studies:

* Real-world applications of Modern C++ (e.g., game engines, financial systems,


scientific simulations).

– Industry Best Practices:

* How top companies use Modern C++.


18

* Lessons from large-scale C++ projects.


– Open Source Contributions:

* Contributing to open-source C++ projects.


* Building your own C++ libraries.
– Career Development:

* Building a portfolio with Modern C++.


* Preparing for C++ interviews.
– Networking and conferences :

* Networking with the C++ community.


* Attending conferences and workshops.
Chapter 1

Templates and Metaprogramming

1.1 Function and Class Templates

1.1.1 Introduction to Templates in C++


Templates are one of the most powerful and versatile features in C++. They form the foundation
of generic programming, a paradigm that allows you to write code that is independent of
specific data types. By using templates, you can create functions and classes that work
seamlessly with any data type, eliminating the need to write redundant code for each type. This
not only enhances code reusability but also improves maintainability and reduces the likelihood
of errors.
Templates are a cornerstone of modern C++ programming, enabling the development of highly
efficient and flexible libraries, such as the Standard Template Library (STL). They are also
essential for advanced techniques like template metaprogramming, which allows
computations and logic to be executed at compile time.
In this section, we will explore function templates and class templates in detail, along with
advanced concepts like template specialization, non-type template parameters, and variadic

19
20

templates. These concepts are critical for advanced learners and professionals who want to
harness the full power of C++.

1.1.2 Function Templates


1. Definition and Purpose

A function template is a blueprint for creating functions that can operate on any data type.
Instead of writing multiple overloaded functions for different types (e.g., int, double,
std::string), you can write a single function template that works for all of them. The
compiler generates the appropriate function for each type at compile time.

2. Syntax

The syntax for a function template involves the template keyword, followed by a list of
template parameters enclosed in angle brackets (<>). Each parameter is prefixed with
typename (or class, which is interchangeable in this context).

template <typename T>


T add(T a, T b) {
return a + b;
}

• template <typename T>: This declares a template with a single type


parameter T.

• T add(T a, T b): This is the function template. The type T can be any data
type, such as int, double, or even user-defined types.

3. Example Usage
21

int main() {
int i = add(3, 4); // T is int
double d = add(3.5, 4.5); // T is double
std::string s = add(std::string("Hello, "),
,→ std::string("World!")); // T is std::string
}

• When add(3, 4) is called, the compiler generates a version of add where T is


int.

• When add(3.5, 4.5) is called, the compiler generates a version of add where
T is double.

• When add("Hello, ", "World!") is called, the compiler generates a


version of add where T is std::string.

4. Key Points

(a) Code Reusability: A single function template can handle multiple types, reducing
code duplication.

(b) Type Safety: Templates ensure type safety at compile time. For example, you
cannot call add(3, 4.5) because T cannot be both int and double.

(c) Performance: Since templates are resolved at compile time, there is no runtime
overhead compared to using function pointers or virtual functions.

5. Overloading Function Templates

Function templates can be overloaded, just like regular functions. This allows you to
provide specialized implementations for specific types or scenarios.
22

template <typename T>


T add(T a, T b) {
return a + b;
}

// Overload for const char* (C-style strings)


const char* add(const char* a, const char* b) {
std::string result = std::string(a) + std::string(b);
return result.c_str(); // Note: This is unsafe in real code; just
,→ for demonstration
}

6. Template Argument Deduction

The compiler can automatically deduce the template arguments based on the function
arguments. For example:

auto result = add(3, 4); // Compiler deduces T is int

You can also explicitly specify the template arguments:

auto result = add<double>(3, 4); // T is explicitly set to double

1.1.3 Class Templates


1. Definition and Purpose

A class template is a blueprint for creating classes that can operate on any data type. Like
function templates, class templates allow you to define a class once and use it with
23

multiple types. This is particularly useful for creating generic containers, such as vectors,
stacks, or queues.

2. Syntax
The syntax for a class template is similar to that of a function template. You use the
template keyword followed by a list of template parameters.

template <typename T>


class Box {
private:
T value;
public:
Box(T v) : value(v) {}
T getValue() const { return value; }
};

• template <typename T>: This declares a template with a single type


parameter T.
• class Box { ... };: This is the class template. The type T can be any data
type.

3. Example Usage

int main() {
Box<int> intBox(123);
Box<double> doubleBox(456.78);
Box<std::string> stringBox("Hello, World!");

std::cout << intBox.getValue() << std::endl; // Output: 123


std::cout << doubleBox.getValue() << std::endl; // Output:
,→ 456.78
24

std::cout << stringBox.getValue() << std::endl; // Output:


,→ Hello, World!
}

• When Box<int> is instantiated, the compiler generates a version of Box where T


is int.
• When Box<std::string> is instantiated, the compiler generates a version of
Box where T is std::string.

4. Key Points

(a) Generic Containers: Class templates are commonly used to create generic
containers like std::vector, std::list, and std::map.
(b) Multiple Template Parameters: Class templates can have multiple template
parameters.

template <typename T, typename U>


class Pair {
private:
T first;
U second;
public:
Pair(T f, U s) : first(f), second(s) {}
T getFirst() const { return first; }
U getSecond() const { return second; }
};

(a) Default Template Arguments: You can provide default values for template
parameters.
25

template <typename T = int>


class Box {
// ...
};

1.1.4 Template Specialization


1. Definition

Template specialization allows you to provide a specific implementation of a template for


a particular data type or set of types. This is useful when the generic implementation is
not suitable for certain types.

2. Function Template Specialization

template <>
std::string add<std::string>(std::string a, std::string b) {
return a + " " + b; // Custom behavior for strings
}

3. Class Template Specialization

template <>
class Box<char> {
private:
char value;
public:
Box(char v) : value(v) {}
char getValue() const { return value; }
26

void print() const { std::cout << "Char Box: " << value <<
,→ std::endl; }
};

4. Partial Specialization
Partial specialization allows you to specialize only some of the template parameters.

template <typename T>


class Box<T*> {
private:
T* value;
public:
Box(T* v) : value(v) {}
T* getValue() const { return value; }
};

1.1.5 Non-Type Template Parameters


1. Definition
Non-type template parameters allow you to pass values (rather than types) as template
arguments. These values must be compile-time constants.

2. Example

template <typename T, int size>


class Array {
private:
T arr[size];
public:
27

T& operator[](int index) {


return arr[index];
}
};

3. Usage

int main() {
Array<int, 10> intArray;
intArray[0] = 123;
std::cout << intArray[0] << std::endl;
}

1.1.6 Variadic Templates


1. Definition
Variadic templates allow a template to accept an arbitrary number of template arguments.
This is particularly useful for creating functions or classes that can handle a variable
number of inputs.

2. Example

template <typename... Args>


void print(Args... args) {
(std::cout << ... << args) << std::endl; // Fold expression
}

3. Usage
28

int main() {
print(1, 2, 3, "Hello", 4.5); // Output: 1 2 3 Hello 4.5
}

1.1.7 Conclusion
Function and class templates are indispensable tools in modern C++ programming. They enable
you to write generic, reusable, and type-safe code, reducing redundancy and improving
maintainability. By mastering templates, you can unlock the full potential of C++ and create
powerful, flexible, and efficient software systems.
This section has covered the fundamentals of templates, including function and class templates,
template specialization, non-type template parameters, and variadic templates. These concepts
lay the groundwork for more advanced topics, such as template metaprogramming, which will
be explored in later sections of the book.
29

1.2 Variadic Templates

1.2.1 Introduction to Variadic Templates


Variadic templates are one of the most powerful and flexible features introduced in C++11. They
allow functions and classes to accept an arbitrary number of template arguments, enabling the
creation of highly generic and reusable code. This capability is particularly useful in scenarios
where the number of inputs or their types is not known in advance. Variadic templates are widely
used in modern C++ libraries, such as the Standard Template Library (STL), to implement
features like std::tuple, std::function, std::bind, and more.
In this section, we will explore variadic templates in exhaustive detail, covering their syntax,
usage, and advanced techniques like parameter packs, fold expressions, recursive template
instantiation, and perfect forwarding. This knowledge is critical for advanced learners and
professionals who want to leverage the full power of modern C++.

1.2.2 Basics of Variadic Templates


1. Definition
A variadic template is a template that can accept a variable number of template
arguments. This is achieved using a template parameter pack, which represents zero or
more template arguments. Variadic templates are the foundation of many modern C++
features, including heterogeneous data structures, type-safe variadic functions, and
compile-time computations.

2. Syntax
The syntax for a variadic template involves the typename... or class... keyword,
which declares a template parameter pack. The parameter pack can then be expanded to
represent multiple arguments.
30

template <typename... Args>


void print(Args... args) {
// Function implementation
}

• typename... Args: This declares a template parameter pack named Args.


• Args... args: This declares a function parameter pack named args.

3. Example: A Simple Variadic Function Template

#include <iostream>

template <typename... Args>


void print(Args... args) {
(std::cout << ... << args) << std::endl; // Fold expression
,→ (C++17)
}

int main() {
print(1, 2, 3, "Hello", 4.5); // Output: 1 2 3 Hello 4.5
}

• The print function can accept any number of arguments of any type.
• The fold expression (std::cout << ... << args) expands the parameter
pack args and applies the << operator to each argument.

1.2.3 Parameter Packs


1. Definition
31

A parameter pack is a collection of zero or more template or function arguments. There


are two types of parameter packs:

(a) Template parameter pack: Represents zero or more template parameters.


(b) Function parameter pack: Represents zero or more function arguments.

2. Expanding Parameter Packs


Parameter packs can be expanded using the ... syntax. This is known as pack
expansion. Pack expansion is the process of unpacking the elements of a parameter pack
and applying an operation to each element.

template <typename... Args>


void print(Args... args) {
// Expand the parameter pack
(std::cout << ... << args) << std::endl;
}

• The expression (std::cout << ... << args) is a fold expression that
expands the parameter pack args.

3. Example: Expanding a Parameter Pack

template <typename... Args>


void printSize(Args... args) {
std::cout << sizeof...(args) << std::endl; // Number of arguments
}

int main() {
printSize(1, 2, 3); // Output: 3
printSize("Hello", 4.5); // Output: 2
}
32

• sizeof...(args) returns the number of arguments in the parameter pack.

1.2.4 Fold Expressions (C++17)


1. Definition
Fold expressions are a concise way to apply an operator to all elements of a parameter
pack. They are a feature introduced in C++17 to simplify variadic template programming.
Fold expressions eliminate the need for recursive template instantiation in many cases,
making the code more readable and efficient.

2. Syntax
There are four types of fold expressions:

(a) Unary right fold: (pack op ...)


(b) Unary left fold: (... op pack)
(c) Binary right fold: (pack op ... op init)
(d) Binary left fold: (init op ... op pack)

3. Example: Using Fold Expressions

template <typename... Args>


auto sum(Args... args) {
return (args + ...); // Unary right fold
}

int main() {
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // Output: 15
}
33

• The fold expression (args + ...) expands to 1 + 2 + 3 + 4 + 5.

4. Example: Binary Fold Expression

template <typename... Args>


auto sumWithInit(int init, Args... args) {
return (args + ... + init); // Binary right fold
}

int main() {
std::cout << sumWithInit(10, 1, 2, 3) << std::endl; // Output: 16
}

• The fold expression (args + ... + init) expands to 1 + 2 + 3 + 10.

1.2.5 Recursive Template Instantiation


1. Definition
Recursive template instantiation is a technique where a variadic template function or class
calls itself with a reduced parameter pack until the pack is empty. This is often used to
process each argument in the pack individually.

2. Example: Recursive Function Template

#include <iostream>

// Base case: No arguments left


void print() {
std::cout << "End of recursion" << std::endl;
}
34

// Recursive case: Process one argument and recurse


template <typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first << std::endl; // Process the first argument
print(args...); // Recurse with the remaining arguments
}

int main() {
print(1, 2, 3, "Hello", 4.5);
}

• The print function processes the first argument and then calls itself with the
remaining arguments.

• The recursion stops when the parameter pack is empty, and the base case void
print() is called.

3. Example: Recursive Class Template

template <typename T, typename... Args>


class Tuple {
public:
T value;
Tuple<Args...> rest;

Tuple(T v, Args... args) : value(v), rest(args...) {}


};

// Base case: Empty tuple


template <>
class Tuple<> {
35

public:
Tuple() {}
};

int main() {
Tuple<int, double, std::string> t(1, 2.5, "Hello");
}

• The Tuple class template recursively stores each value and the remaining
arguments in a nested structure.

1.2.6 Advanced Techniques


1. Forwarding Parameter Packs
Variadic templates are often used with perfect forwarding to preserve the value category
(lvalue or rvalue) of the arguments. Perfect forwarding ensures that the original value
category of the arguments is maintained when they are passed to another function.

template <typename... Args>


void forwardToPrint(Args&&... args) {
print(std::forward<Args>(args)...);
}

int main() {
forwardToPrint(1, 2, 3, "Hello", 4.5);
}

• std::forward<Args>(args)... forwards each argument with its original


value category.
36

2. Heterogeneous Data Structures

Variadic templates are used to create heterogeneous data structures like std::tuple
and std::variant. These data structures can store multiple values of different types.

#include <tuple>
#include <string>

int main() {
std::tuple<int, double, std::string> t(1, 2.5, "Hello");
std::cout << std::get<0>(t) << std::endl; // Output: 1
std::cout << std::get<2>(t) << std::endl; // Output: Hello
}

• std::tuple is a variadic class template that can store multiple values of different
types.

3. Type-Safe Variadic Functions

Variadic templates enable the creation of type-safe variadic functions, which can accept
any number of arguments of any type while ensuring type safety at compile time.

template <typename... Args>


void log(const Args&... args) {
(std::cout << ... << args) << std::endl;
}

int main() {
log("Error code: ", 404, ", Message: ", "Not Found");
}
37

• The log function can accept any number of arguments of any type and print them in
a type-safe manner.

4. Practical Applications

(a) Logging and Debugging: Variadic templates can be used to create flexible logging
functions that accept any number of arguments.
(b) Generic Containers: Variadic templates are used to implement containers like
std::tuple, std::any, and std::variant.
(c) Factory Functions: Variadic templates can be used to create factory functions that
construct objects with arbitrary constructor arguments.
(d) Metaprogramming: Variadic templates are essential for advanced
metaprogramming techniques, such as type lists, compile-time computations, and
template metaprogramming.

1.2.7 Conclusion
Variadic templates are a cornerstone of modern C++ programming, enabling the creation of
highly flexible and reusable code. By mastering variadic templates, you can implement
advanced features like fold expressions, recursive template instantiation, perfect forwarding, and
type-safe variadic functions. These techniques are essential for developing robust and efficient
software systems.
This section has provided a comprehensive overview of variadic templates, covering their syntax,
usage, and advanced techniques. With this knowledge, you are well-equipped to tackle complex
programming challenges and leverage the full power of modern C++. Variadic templates are not
just a feature; they are a paradigm shift that opens up new possibilities for generic programming
and metaprogramming in C++.
38

1.3 Type Traits and std::enable if

1.3.1 Introduction to Type Traits and std::enable if

Type traits and std::enable if are foundational tools in modern C++ template
metaprogramming. They allow developers to inspect, manipulate, and conditionally enable or
disable code based on the properties of types at compile time. These tools are essential for
writing generic, reusable, and type-safe code, and they are widely used in the Standard Template
Library (STL) and other advanced C++ libraries.
This section will provide an in-depth exploration of type traits and std::enable if,
covering their syntax, usage, and practical applications. We will also delve into advanced
techniques such as SFINAE (Substitution Failure Is Not An Error), conditional function
overloading, and template specialization. These concepts are critical for advanced learners and
professionals who want to master template metaprogramming and write high-quality,
maintainable C++ code.

1.3.2 Type Traits

1. Definition

Type traits are a set of templates defined in the <type traits> header that allow you
to inspect and manipulate the properties of types at compile time. They are used to
perform type introspection (querying properties of types) and type transformations
(modifying types). Type traits are a cornerstone of template metaprogramming, enabling
compile-time logic based on type properties.

2. Categories of Type Traits

Type traits can be broadly categorized into the following groups:


39

(a) Primary Type Categories: Traits that check if a type belongs to a specific category
(e.g., std::is integral, std::is floating point).
(b) Composite Type Categories: Traits that check if a type belongs to a composite
category (e.g., std::is arithmetic, std::is compound).
(c) Type Properties: Traits that check specific properties of a type (e.g.,
std::is const, std::is pointer).
(d) Type Transformations: Traits that transform one type into another (e.g.,
std::remove const, std::add pointer).
(e) Type Relationships: Traits that check relationships between types (e.g.,
std::is same, std::is base of).

3. Example: Using Type Traits

#include <iostream>
#include <type_traits>

template <typename T>


void checkType(T value) {
if (std::is_integral<T>::value) {
std::cout << "Integral type" << std::endl;
} else if (std::is_floating_point<T>::value) {
std::cout << "Floating-point type" << std::endl;
} else if (std::is_pointer<T>::value) {
std::cout << "Pointer type" << std::endl;
} else {
std::cout << "Other type" << std::endl;
}
}

int main() {
40

checkType(42); // Output: Integral type


checkType(3.14); // Output: Floating-point type
int* ptr = nullptr;
checkType(ptr); // Output: Pointer type
checkType("Hello"); // Output: Other type
}

• std::is integral<T>::value checks if T is an integral type (e.g., int,


char).
• std::is floating point<T>::value checks if T is a floating-point type
(e.g., float, double).
• std::is pointer<T>::value checks if T is a pointer type.

4. Commonly Used Type Traits

Type Trait Description

std::is integral<T> Checks if T is an integral type.

std::is floating point<T> Checks if T is a floating-point type.

std::is arithmetic<T> Checks if T is an arithmetic type.

std::is pointer<T> Checks if T is a pointer type.

std::is reference<T> Checks if T is a reference type.

std::is const<T> Checks if T is a const-qualified type.

std::is void<T> Checks if T is void.

std::is array<T> Checks if T is an array type.


41

Type Trait Description

std::is class<T> Checks if T is a class or struct type.

std::is same<T, U> Checks if T and U are the same type.

std::is base of<Base, Checks if Base is a base class of Derived.


Derived>

std::remove const<T> Removes the const qualifier from T.

std::add pointer<T> Adds a pointer to T.

std::remove reference<T> Removes the reference from T.

1.3.3 std::enable if
1. Definition
std::enable if is a template defined in the <type traits> header that
conditionally enables or disables a template instantiation based on a compile-time boolean
expression. It is commonly used to enforce constraints on template parameters or to
provide different implementations for different types.

2. Syntax

template <bool B, typename T = void>


struct enable_if;

template <typename T>


struct enable_if<true, T> {
using type = T;
};
42

template <typename T>


struct enable_if<false, T> {}; // No `type` member defined

• If the boolean expression B is true, std::enable if<B, T> defines a nested


type type equal to T.

• If B is false, std::enable if<B, T> does not define a type member,


causing a substitution failure.

3. Example: Using std::enable if

#include <iostream>
#include <type_traits>

// Function enabled only for integral types


template <typename T, typename
,→ std::enable_if<std::is_integral<T>::value, int>::type = 0>
void print(T value) {
std::cout << "Integral value: " << value << std::endl;
}

// Function enabled only for floating-point types


template <typename T, typename
,→ std::enable_if<std::is_floating_point<T>::value, int>::type = 0>
void print(T value) {
std::cout << "Floating-point value: " << value << std::endl;
}

int main() {
print(42); // Output: Integral value: 42
print(3.14); // Output: Floating-point value: 3.14
43

// print("Hello"); // Error: No matching function


}

• The print function is enabled only if T is an integral type or a floating-point type.


• If T does not satisfy the condition, the function is not instantiated, and a
compile-time error occurs.

4. std::enable if with Return Type


std::enable if can also be used in the return type of a function to conditionally
enable or disable it.

#include <iostream>
#include <type_traits>

// Function enabled only for integral types


template <typename T>
typename std::enable_if<std::is_integral<T>::value, void>::type
print(T value) {
std::cout << "Integral value: " << value << std::endl;
}

// Function enabled only for floating-point types


template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, void>::type
print(T value) {
std::cout << "Floating-point value: " << value << std::endl;
}

int main() {
print(42); // Output: Integral value: 42
44

print(3.14); // Output: Floating-point value: 3.14


// print("Hello"); // Error: No matching function
}

1.3.4 Practical Applications


1. SFINAE (Substitution Failure Is Not An Error)
std::enable if is often used in conjunction with SFINAE to selectively enable or
disable template instantiations. SFINAE allows the compiler to discard invalid template
specializations without causing a compilation error.

#include <iostream>
#include <type_traits>

template <typename T, typename


,→ std::enable_if<std::is_integral<T>::value, int>::type = 0>
void process(T value) {
std::cout << "Processing integral value: " << value << std::endl;
}

template <typename T, typename


,→ std::enable_if<std::is_floating_point<T>::value, int>::type = 0>
void process(T value) {
std::cout << "Processing floating-point value: " << value <<
,→ std::endl;
}

int main() {
process(42); // Output: Processing integral value: 42
process(3.14); // Output: Processing floating-point value: 3.14
45

// process("Hello"); // Error: No matching function


}

2. Conditional Function Overloading


std::enable if can be used to conditionally overload functions based on type
properties.

#include <iostream>
#include <type_traits>

template <typename T>


void process(T value, typename
,→ std::enable_if<std::is_pointer<T>::value>::type* = nullptr) {
std::cout << "Processing pointer: " << *value << std::endl;
}

template <typename T>


void process(T value, typename
,→ std::enable_if<std::is_reference<T>::value>::type* = nullptr) {
std::cout << "Processing reference: " << value << std::endl;
}

int main() {
int x = 42;
process(&x); // Output: Processing pointer: 42
process(x); // Output: Processing reference: 42
}

3. Template Specialization
std::enable if can be used to specialize templates based on type properties.
46

#include <iostream>
#include <type_traits>

template <typename T, typename Enable = void>


struct MyStruct {
void print() { std::cout << "Generic type" << std::endl; }
};

template <typename T>


struct MyStruct<T, typename
,→ std::enable_if<std::is_integral<T>::value>::type> {
void print() { std::cout << "Integral type" << std::endl; }
};

int main() {
MyStruct<double> s1;
s1.print(); // Output: Generic type

MyStruct<int> s2;
s2.print(); // Output: Integral type
}

4. Type-Safe Interfaces
std::enable if can be used to create type-safe interfaces that enforce constraints on
template parameters.

#include <iostream>
#include <type_traits>

template <typename T>


class Container {
47

static_assert(std::is_arithmetic<T>::value, "Container requires


,→ an arithmetic type");
T value;
public:
Container(T v) : value(v) {}
void print() { std::cout << "Value: " << value << std::endl; }
};

int main() {
Container<int> c1(42); // OK
Container<double> c2(3.14); // OK
// Container<std::string> c3("Hello"); // Error: Static assertion
,→ failed
}

1.3.5 Advanced Techniques


1. Combining Type Traits and std::enable if

You can combine multiple type traits with std::enable if to create complex
compile-time conditions.

#include <iostream>
#include <type_traits>

template <typename T>


typename std::enable_if<std::is_arithmetic<T>::value &&
,→ !std::is_pointer<T>::value, void>::type
process(T value) {
std::cout << "Processing arithmetic non-pointer value: " << value
,→ << std::endl;
48

int main() {
process(42); // Output: Processing arithmetic non-pointer
,→ value: 42
process(3.14); // Output: Processing arithmetic non-pointer
,→ value: 3.14
// int* ptr = nullptr;
// process(ptr); // Error: No matching function
}

2. Custom Type Traits


You can define your own type traits to encapsulate complex compile-time logic.

#include <iostream>
#include <type_traits>

template <typename T>


struct is_custom_type : std::false_type {};

template <>
struct is_custom_type<int> : std::true_type {};

template <>
struct is_custom_type<double> : std::true_type {};

template <typename T>


void process(T value, typename
,→ std::enable_if<is_custom_type<T>::value, int>::type = 0) {
std::cout << "Processing custom type: " << value << std::endl;
}
49

int main() {
process(42); // Output: Processing custom type: 42
process(3.14); // Output: Processing custom type: 3.14
// process("Hello"); // Error: No matching function
}

1.3.6 Conclusion
Type traits and std::enable if are indispensable tools in modern C++ template
metaprogramming. They enable you to write more generic, flexible, and type-safe code by
inspecting and manipulating types at compile time. By mastering these concepts, you can
implement advanced features like SFINAE, conditional function overloading, and template
specialization.
This section has provided a comprehensive overview of type traits and std::enable if,
covering their syntax, usage, and practical applications. With this knowledge, you are
well-equipped to tackle complex programming challenges and leverage the full power of modern
C++. These techniques are essential for developing robust, efficient, and maintainable software
systems.
50

1.4 Concepts and Constraints (C++20)

1.4.1 Introduction to Concepts and Constraints


Concepts and constraints, introduced in C++20, represent a paradigm shift in how templates are
written and used in C++. They provide a mechanism to specify requirements on template
parameters, making templates more expressive, easier to use, and less prone to errors. Concepts
allow you to define semantic categories for types, while constraints enforce these categories at
compile time. This results in better error messages, improved code readability, and more robust
generic programming.
This section will provide an exhaustive exploration of concepts and constraints, covering their
syntax, usage, and practical applications. We will also delve into advanced techniques such as
custom concepts, combining concepts, and using constraints in real-world scenarios. These
features are essential for advanced learners and professionals who want to write modern,
maintainable, and type-safe C++ code.

1.4.2 What Are Concepts?


1. Definition

A concept is a named set of requirements that can be used to constrain template


parameters. It specifies what operations a type must support, what properties it must have,
and how it should behave. Concepts make template errors more readable and provide
better compiler diagnostics.

2. Syntax

A concept is defined using the concept keyword, followed by the concept name and a
boolean expression that specifies the requirements.
51

template <typename T>


concept MyConcept = requires(T t) {
// Requirements go here
};

• template <typename T>: Declares a template parameter.


• concept MyConcept: Defines a concept named MyConcept.
• requires(T t) { ... }: Specifies the requirements that T must satisfy.

3. Example: Defining a Concept

#include <iostream>
#include <concepts>

template <typename T>


concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>; // Requirement: a + b must return a
,→ value of type T
};

template <Addable T>


T add(T a, T b) {
return a + b;
}

int main() {
std::cout << add(3, 4) << std::endl; // Output: 7
std::cout << add(3.5, 4.5) << std::endl; // Output: 8.0
// std::cout << add("Hello", "World") << std::endl; // Error:
,→ "Hello" does not satisfy Addable
}
52

• The Addable concept requires that the + operator is defined for T and that it
returns a value of type T.
• The add function is constrained to types that satisfy the Addable concept.

1.4.3 What Are Constraints?


1. Definition
A constraint is a boolean expression that restricts the set of types that can be used as
template arguments. Constraints are used in conjunction with concepts to enforce
requirements on template parameters.

2. Syntax
Constraints can be applied to template parameters using the requires keyword or
directly in the template parameter list.

template <typename T>


requires MyConcept<T>
void myFunction(T t) {
// Function implementation
}

• requires MyConcept<T>: Applies the MyConcept constraint to the


template parameter T.

3. Example: Applying Constraints

#include <iostream>
#include <concepts>
53

template <typename T>


concept Integral = std::integral<T>; // Concept that checks if T is
,→ an integral type

template <Integral T>


void print(T value) {
std::cout << "Integral value: " << value << std::endl;
}

int main() {
print(42); // Output: Integral value: 42
// print(3.14); // Error: 3.14 does not satisfy Integral
}

• The Integral concept is defined using the standard std::integral concept.


• The print function is constrained to types that satisfy the Integral concept.

1.4.4 Standard Concepts (C++20)


C++20 introduces a set of standard concepts in the <concepts> header. These concepts
cover common requirements, such as arithmetic operations, comparisons, and object lifetimes.

1. Commonly Used Standard Concepts

Concept Description

std::integral<T> Checks if T is an integral type.

std::floating point<T> Checks if T is a floating-point type.

std::same as<T, U> Checks if T and U are the same type.


54

Concept Description

std::derived from<T, U> Checks if T is derived from U.

std::convertible to<T, U> Checks if T is convertible to U.

std::equality comparable<T> Checks if T supports equality comparison.

std::invocable<F, Args...> Checks if F can be invoked with Args....

2. Example: Using Standard Concepts

#include <iostream>
#include <concepts>

template <std::integral T>


void process(T value) {
std::cout << "Processing integral value: " << value << std::endl;
}

template <std::floating_point T>


void process(T value) {
std::cout << "Processing floating-point value: " << value <<
,→ std::endl;
}

int main() {
process(42); // Output: Processing integral value: 42
process(3.14); // Output: Processing floating-point value: 3.14
// process("Hello"); // Error: No matching function
}
55

1.4.5 Combining Concepts


Concepts can be combined using logical operators (&&, ||, !) to create more complex
requirements.

Example: Combining Concepts

#include <iostream>
#include <concepts>

template <typename T>


concept Arithmetic = std::integral<T> || std::floating_point<T>;

template <Arithmetic T>


void print(T value) {
std::cout << "Arithmetic value: " << value << std::endl;
}

int main() {
print(42); // Output: Arithmetic value: 42
print(3.14); // Output: Arithmetic value: 3.14
// print("Hello"); // Error: No matching function
}

• The Arithmetic concept combines std::integral and


std::floating point using the || operator.

1.4.6 Practical Applications


1. Type-Safe Interfaces
Concepts and constraints can be used to create type-safe interfaces that enforce
requirements on template parameters.
56

#include <iostream>
#include <concepts>

template <typename T>


concept Printable = requires(T t) {
{ std::cout << t } -> std::same_as<std::ostream&>;
};

template <Printable T>


void print(T value) {
std::cout << value << std::endl;
}

int main() {
print(42); // Output: 42
print("Hello"); // Output: Hello
// print(std::vector<int>{1, 2, 3}); // Error: No matching
,→ function
}

2. Algorithm Constraints
Concepts can be used to constrain algorithms to work only with types that meet specific
requirements.

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

template <typename T>


concept Sortable = requires(T a, T b) {
57

{ a < b } -> std::convertible_to<bool>;


};

template <Sortable T>


void sortAndPrint(std::vector<T>& vec) {
std::sort(vec.begin(), vec.end());
for (const auto& v : vec) {
std::cout << v << " ";
}
std::cout << std::endl;
}

int main() {
std::vector<int> intVec = {3, 1, 4, 1, 5};
sortAndPrint(intVec); // Output: 1 1 3 4 5

// std::vector<std::string> strVec = {"hello", "world"};


// sortAndPrint(strVec); // Error: No matching function
}

3. Custom Concepts
You can define custom concepts to encapsulate complex requirements.

#include <iostream>
#include <concepts>

template <typename T>


concept HasSquare = requires(T t) {
{ t.square() } -> std::same_as<T>;
};
58

template <HasSquare T>


void printSquare(T value) {
std::cout << value.square() << std::endl;
}

class Number {
int value;
public:
Number(int v) : value(v) {}
Number square() const { return Number(value * value); }
friend std::ostream& operator<<(std::ostream& os, const Number&
,→ num) {
return os << num.value;
}
};

int main() {
Number num(4);
printSquare(num); // Output: 16
}

1.4.7 Advanced Techniques


1. Nested Requirements

You can specify nested requirements within a concept to enforce more complex
constraints.

#include <iostream>
#include <concepts>
59

template <typename T>


concept ComplexConcept = requires(T t) {
{ t.foo() } -> std::same_as<int>;
{ t.bar() } -> std::convertible_to<bool>;
requires std::integral<decltype(t.foo())>;
};

class MyClass {
public:
int foo() { return 42; }
bool bar() { return true; }
};

template <ComplexConcept T>


void process(T t) {
std::cout << "Processing: " << t.foo() << std::endl;
}

int main() {
MyClass obj;
process(obj); // Output: Processing: 42
}

2. Parameterized Concepts
Concepts can themselves be parameterized, allowing for even greater flexibility.

#include <iostream>
#include <concepts>

template <typename T, typename U>


concept AddableTo = requires(T t, U u) {
60

{ t + u } -> std::same_as<T>;
};

template <typename T, typename U>


requires AddableTo<T, U>
T add(T a, U b) {
return a + b;
}

int main() {
std::cout << add(3, 4.5) << std::endl; // Output: 7.5
}

3. Using Concepts with Auto


Concepts can be used with the auto keyword to constrain the types of variables.

#include <iostream>
#include <concepts>

template <typename T>


concept Integral = std::integral<T>;

Integral auto multiply(Integral auto a, Integral auto b) {


return a * b;
}

int main() {
std::cout << multiply(3, 4) << std::endl; // Output: 12
// std::cout << multiply(3.5, 4.5) << std::endl; // Error: No
,→ matching function
}
61

1.4.8 Conclusion
Concepts and constraints are transformative features in C++20 that bring clarity, safety, and
expressiveness to template programming. By mastering these tools, you can write more robust,
maintainable, and type-safe code. Concepts allow you to define semantic categories for types,
while constraints enable you to enforce these categories at compile time.
This section has provided a comprehensive overview of concepts and constraints, covering their
syntax, usage, and practical applications. With this knowledge, you are well-equipped to tackle
complex programming challenges and leverage the full power of modern C++. These techniques
are essential for developing high-quality, efficient, and maintainable software systems.
Chapter 2

Concurrency and Parallelism

2.1 Threading (std::thread, std::async)

2.1.1 Introduction to Threading in C++


Concurrency and parallelism are fundamental concepts in modern software development,
enabling programs to perform multiple tasks simultaneously and efficiently utilize multi-core
processors. C++ provides robust support for threading through the <thread> and
<future> headers, which include classes like std::thread and std::async. These
tools allow developers to create and manage threads, execute tasks asynchronously, and
synchronize access to shared resources.
This section will provide an exhaustive exploration of threading in C++, focusing on
std::thread and std::async. We will cover their syntax, usage, and practical
applications, as well as advanced techniques for managing threads and asynchronous tasks.
These concepts are critical for advanced learners and professionals who want to write efficient,
scalable, and maintainable concurrent programs.

62
63

2.1.2 std::thread
1. Definition
std::thread is a class in the C++ Standard Library that represents a single thread of
execution. It allows you to create and manage threads, enabling concurrent execution of
tasks.

2. Syntax

#include <thread>

void myFunction() {
// Task to be executed in the thread
}

int main() {
std::thread t(myFunction); // Create a thread that executes
,→ myFunction
t.join(); // Wait for the thread to finish
}

• std::thread t(myFunction): Creates a thread t that executes


myFunction.
• t.join(): Blocks the calling thread until t finishes execution.

3. Example: Basic Thread Creation

#include <iostream>
#include <thread>
64

void printHello() {
std::cout << "Hello from thread!" << std::endl;
}

int main() {
std::thread t(printHello); // Create a thread
t.join(); // Wait for the thread to finish
std::cout << "Hello from main!" << std::endl;
}

• Hello from thread!


Hello from main!

4. Key Points

(a) Thread Lifecycle: A thread starts execution immediately after creation and runs
until the function it was created with returns.

(b) Joining Threads: Use join() to wait for a thread to finish. Failing to join a thread
can lead to a program crash.

(c) Detaching Threads: Use detach() to allow a thread to run independently.


Detached threads cannot be joined.

std::thread t(printHello);
t.detach(); // Detach the thread

(a) Thread Arguments: You can pass arguments to the thread function.
65

void printMessage(const std::string& message) {


std::cout << message << std::endl;
}

int main() {
std::thread t(printMessage, "Hello from thread with arguments!");
t.join();
}

2.1.3 std::async
1. Definition
std::async is a higher-level abstraction for asynchronous task execution. It allows
you to run a function asynchronously and obtain the result through a std::future
object. std::async can execute tasks in a separate thread or defer execution until the
result is requested.

2. Syntax

#include <future>

int myFunction() {
return 42;
}

int main() {
std::future<int> result = std::async(myFunction); // Launch
,→ myFunction asynchronously
int value = result.get(); // Get the result
}
66

• std::async(myFunction): Launches myFunction asynchronously and


returns a std::future object.

• result.get(): Blocks until the result is available and retrieves it.

3. Example: Basic std::async Usage

#include <iostream>
#include <future>

int computeSquare(int x) {
return x * x;
}

int main() {
std::future<int> result = std::async(computeSquare, 5);
std::cout << "Square of 5 is " << result.get() << std::endl;
}

• Output:

Square of 5 is 25

4. Key Points

(a) Launch Policies: std::async supports two launch policies:

• std::launch::async: Forces asynchronous execution in a new thread.


• std::launch::deferred: Defers execution until get() or wait() is
called.
67

auto result = std::async(std::launch::async, computeSquare, 5); //


,→ Force async execution
auto result = std::async(std::launch::deferred, computeSquare, 5); //
,→ Defer execution

(a) Returning Values: The result of the asynchronous task is accessed through a
std::future object.
(b) Exception Handling: Exceptions thrown in the asynchronous task are propagated to
the calling thread when get() is called.

int computeSquare(int x) {
if (x < 0) throw std::invalid_argument("Negative input");
return x * x;
}

int main() {
try {
auto result = std::async(computeSquare, -5);
std::cout << "Square is " << result.get() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}

2.1.4 Practical Applications


1. Parallelizing Computations
std::thread and std::async can be used to parallelize computationally intensive
tasks.
68

#include <iostream>
#include <thread>
#include <vector>

void computeRange(int start, int end, int& result) {


result = 0;
for (int i = start; i < end; ++i) {
result += i;
}
}

int main() {
const int numThreads = 4;
const int range = 100;
std::vector<std::thread> threads;
std::vector<int> results(numThreads);

for (int i = 0; i < numThreads; ++i) {


int start = i * (range / numThreads);
int end = (i + 1) * (range / numThreads);
threads.emplace_back(computeRange, start, end,
,→ std::ref(results[i]));
}

for (auto& t : threads) {


t.join();
}

int total = 0;
for (int r : results) {
total += r;
}
69

std::cout << "Total sum: " << total << std::endl;


}

2. Asynchronous I/O Operations


std::async can be used to perform I/O operations asynchronously.

#include <iostream>
#include <future>
#include <fstream>
#include <string>

std::string readFile(const std::string& filename) {


std::ifstream file(filename);
if (!file) throw std::runtime_error("Failed to open file");
return std::string((std::istreambuf_iterator<char>(file)),
,→ std::istreambuf_iterator<char>());
}

int main() {
auto future = std::async(std::launch::async, readFile,
,→ "example.txt");
try {
std::string content = future.get();
std::cout << "File content: " << content << std::endl;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}

3. Task-Based Parallelism
70

std::async can be used to implement task-based parallelism, where tasks are executed
concurrently.

#include <iostream>
#include <future>
#include <vector>

int computeSquare(int x) {
return x * x;
}

int main() {
std::vector<std::future<int>> futures;
for (int i = 1; i <= 5; ++i) {
futures.push_back(std::async(std::launch::async,
,→ computeSquare, i));
}

for (auto& future : futures) {


std::cout << "Square: " << future.get() << std::endl;
}
}

2.1.5 Advanced Techniques

1. Thread Synchronization

Threads often need to synchronize access to shared resources to avoid race conditions.
C++ provides synchronization primitives like std::mutex and std::lock guard.
71

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

std::mutex mtx;
int sharedData = 0;

void increment() {
std::lock_guard<std::mutex> lock(mtx);
++sharedData;
}

int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment);
}

for (auto& t : threads) {


t.join();
}

std::cout << "Shared data: " << sharedData << std::endl;


}

2. Thread Pools

Thread pools are used to manage a group of threads that execute tasks concurrently. While
C++ does not provide a built-in thread pool, you can implement one using
std::thread and std::queue.
72

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <functional>

class ThreadPool {
public:
ThreadPool(size_t numThreads) {
for (size_t i = 0; i < numThreads; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex>
,→ lock(queueMutex);
condition.wait(lock, [this] { return
,→ !tasks.empty() || stop; });
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}

˜ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
73

stop = true;
}
condition.notify_all();
for (auto& worker : workers) {
worker.join();
}
}

template <typename F>


void enqueue(F&& f) {
{
std::unique_lock<std::mutex> lock(queueMutex);
tasks.emplace(std::forward<F>(f));
}
condition.notify_one();
}

private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable condition;
bool stop = false;
};

int main() {
ThreadPool pool(4);
for (int i = 0; i < 8; ++i) {
pool.enqueue([i] {
std::cout << "Task " << i << " executed by thread " <<
,→ std::this_thread::get_id() << std::endl;
});
74

}
}

2.1.6 Conclusion
Threading is a powerful feature in C++ that enables concurrent and parallel execution of tasks.
By mastering std::thread and std::async, you can write efficient, scalable, and
maintainable concurrent programs. These tools, combined with synchronization primitives and
advanced techniques like thread pools, provide a solid foundation for modern C++ concurrency.
This section has provided a comprehensive overview of threading in C++, covering the syntax,
usage, and practical applications of std::thread and std::async. With this knowledge,
you are well-equipped to tackle complex concurrency challenges and leverage the full power of
modern C++. These techniques are essential for developing high-performance, responsive, and
robust software systems.
75

2.2 Synchronization (std::mutex, std::atomic)

2.2.1 Introduction to Synchronization in C++


In concurrent programming, multiple threads often need to access shared resources, such as
variables, data structures, or files. Without proper synchronization, concurrent access can lead to
race conditions, where the outcome of the program depends on the timing of thread execution.
To prevent race conditions and ensure correct behavior, C++ provides synchronization
mechanisms like std::mutex and std::atomic.
This section will provide an in-depth exploration of synchronization in C++, focusing on
std::mutex and std::atomic. We will cover their syntax, usage, and practical
applications, as well as advanced techniques for managing shared resources in multi-threaded
programs. These concepts are critical for advanced learners and professionals who want to write
safe, efficient, and maintainable concurrent code.

2.2.2 std::mutex
1. Definition

A mutex (short for ”mutual exclusion”) is a synchronization primitive that ensures only
one thread can access a shared resource at a time. In C++, std::mutex is a class in the
<mutex> header that provides this functionality.

2. Syntax

#include <mutex>

std::mutex mtx;

void criticalSection() {
76

mtx.lock(); // Lock the mutex


// Access shared resource
mtx.unlock(); // Unlock the mutex
}

• mtx.lock(): Locks the mutex. If the mutex is already locked, the calling thread
will block until the mutex is unlocked.
• mtx.unlock(): Unlocks the mutex, allowing other threads to acquire it.

3. Example: Basic Mutex Usage

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int sharedData = 0;

void increment() {
mtx.lock();
++sharedData;
mtx.unlock();
}

int main() {
std::thread t1(increment);
std::thread t2(increment);

t1.join();
t2.join();
77

std::cout << "Shared data: " << sharedData << std::endl;


}

• Output:

Shared data: 2

4. Key Points

(a) Locking and Unlocking: Always ensure that a mutex is unlocked after being locked.
Failing to unlock a mutex can lead to deadlocks.

(b) RAII Wrappers: To avoid manual locking and unlocking, use RAII (Resource
Acquisition Is Initialization) wrappers like std::lock guard or
std::unique lock.

#include <mutex>

std::mutex mtx;

void criticalSection() {
std::lock_guard<std::mutex> lock(mtx); // Automatically locks and
,→ unlocks the mutex
// Access shared resource
}

(a) Deadlocks: A deadlock occurs when two or more threads are waiting for each other
to release locks. To avoid deadlocks, always acquire locks in a consistent order.
78

2.2.3 std::lock guard and std::unique lock


1. std::lock guard
std::lock guard is a lightweight RAII wrapper for std::mutex. It automatically
locks the mutex when constructed and unlocks it when destroyed.

#include <mutex>

std::mutex mtx;

void criticalSection() {
std::lock_guard<std::mutex> lock(mtx); // Automatically locks and
,→ unlocks the mutex
// Access shared resource
}

2. std::unique lock
std::unique lock is a more flexible RAII wrapper for std::mutex. It allows for
deferred locking, manual unlocking, and transfer of ownership.

#include <mutex>

std::mutex mtx;

void criticalSection() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // Defer
,→ locking
lock.lock(); // Manually lock the mutex
// Access shared resource
lock.unlock(); // Manually unlock the mutex
}
79

3. Example: Using std::lock guard

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int sharedData = 0;

void increment() {
std::lock_guard<std::mutex> lock(mtx);
++sharedData;
}

int main() {
std::thread t1(increment);
std::thread t2(increment);

t1.join();
t2.join();

std::cout << "Shared data: " << sharedData << std::endl;


}

• Output:

Shared data: 2
80

2.2.4 std::atomic
1. Definition
std::atomic is a template class in the <atomic> header that provides atomic
operations on shared variables. Atomic operations are indivisible, meaning they cannot be
interrupted by other threads. This makes std::atomic ideal for simple shared
variables that do not require complex synchronization.

2. Syntax

#include <atomic>

std::atomic<int> atomicData(0);

void increment() {
++atomicData; // Atomic increment
}

3. Example: Basic std::atomic Usage

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> atomicData(0);

void increment() {
++atomicData;
}

int main() {
81

std::thread t1(increment);
std::thread t2(increment);

t1.join();
t2.join();

std::cout << "Atomic data: " << atomicData << std::endl;


}

• Output:

Atomic data: 2

4. Key Points

(a) Atomic Operations: std::atomic supports operations like load(), store(),


exchange(), compare exchange strong(), and fetch add().
(b) Memory Ordering: std::atomic allows you to specify memory ordering
constraints (e.g., std::memory order relaxed,
std::memory order seq cst).

#include <atomic>

std::atomic<int> atomicData(0);

void increment() {
atomicData.fetch_add(1, std::memory_order_relaxed); // Relaxed
,→ memory ordering
}
82

(a) Performance: std::atomic is generally faster than std::mutex for simple


operations but is limited to single variables.

2.2.5 Practical Applications


1. Thread-Safe Counters
std::atomic is ideal for implementing thread-safe counters.

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> counter(0);

void increment() {
for (int i = 0; i < 1000; ++i) {
++counter;
}
}

int main() {
std::thread t1(increment);
std::thread t2(increment);

t1.join();
t2.join();

std::cout << "Counter: " << counter << std::endl;


}

• Output:
83

Counter: 2000

2. Thread-Safe Queues
std::mutex can be used to implement thread-safe queues.

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

template <typename T>


class ThreadSafeQueue {
public:
void push(T value) {
std::lock_guard<std::mutex> lock(mtx);
queue.push(value);
cv.notify_one();
}

T pop() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this] { return !queue.empty(); });
T value = queue.front();
queue.pop();
return value;
}

private:
std::queue<T> queue;
std::mutex mtx;
84

std::condition_variable cv;
};

int main() {
ThreadSafeQueue<int> queue;

std::thread producer([&queue] {
for (int i = 0; i < 10; ++i) {
queue.push(i);
}
});

std::thread consumer([&queue] {
for (int i = 0; i < 10; ++i) {
std::cout << "Consumed: " << queue.pop() << std::endl;
}
});

producer.join();
consumer.join();
}

• Output:

Consumed: 0
Consumed: 1
Consumed: 2
...
Consumed: 9

3. Double-Checked Locking
85

std::mutex and std::atomic can be combined to implement the double-checked


locking pattern, which minimizes locking overhead.

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>

class Singleton {
public:
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}

private:
Singleton() {}
static std::atomic<Singleton*> instance;
static std::mutex mtx;
};

std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mtx;

int main() {
86

auto instance1 = Singleton::getInstance();


auto instance2 = Singleton::getInstance();

std::cout << "Instance 1: " << instance1 << std::endl;


std::cout << "Instance 2: " << instance2 << std::endl;
}

• Output:

Instance 1: 0xaddress
Instance 2: 0xaddress

2.2.6 Advanced Techniques


1. Condition Variables
Condition variables (std::condition variable) are used to block threads until a
condition is met. They are often used with std::mutex to implement
producer-consumer patterns.

#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> queue;
87

void producer() {
for (int i = 0; i < 10; ++i) {
std::lock_guard<std::mutex> lock(mtx);
queue.push(i);
cv.notify_one();
}
}

void consumer() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !queue.empty(); });
int value = queue.front();
queue.pop();
std::cout << "Consumed: " << value << std::endl;
}
}

int main() {
std::thread t1(producer);
std::thread t2(consumer);

t1.join();
t2.join();
}

• Output:

Consumed: 0
Consumed: 1
Consumed: 2
88

...
Consumed: 9

2. Read-Write Locks
Read-write locks allow multiple threads to read a shared resource simultaneously but
require exclusive access for writing. C++17 introduced std::shared mutex for this
purpose.

#include <iostream>
#include <thread>
#include <shared_mutex>

std::shared_mutex rwMutex;
int sharedData = 0;

void reader() {
std::shared_lock<std::shared_mutex> lock(rwMutex);
std::cout << "Read: " << sharedData << std::endl;
}

void writer() {
std::unique_lock<std::shared_mutex> lock(rwMutex);
++sharedData;
std::cout << "Write: " << sharedData << std::endl;
}

int main() {
std::thread t1(reader);
std::thread t2(writer);
std::thread t3(reader);
89

t1.join();
t2.join();
t3.join();
}

• Output:

Read: 0
Write: 1
Read: 1

2.2.7 Conclusion
Synchronization is a critical aspect of concurrent programming in C++. By mastering
std::mutex, std::atomic, and related tools, you can write safe, efficient, and
maintainable multi-threaded programs. These mechanisms ensure that shared resources are
accessed correctly, preventing race conditions and deadlocks.
This section has provided a comprehensive overview of synchronization in C++, covering the
syntax, usage, and practical applications of std::mutex and std::atomic. With this
knowledge, you are well-equipped to tackle complex concurrency challenges and leverage the
full power of modern C++. These techniques are essential for developing high-performance,
responsive, and robust software systems.
90

2.3 Coroutines (C++20)

2.3.1 Introduction to Coroutines in C++


Coroutines, introduced in C++20, are a transformative feature that enables asynchronous
programming by allowing functions to suspend and resume their execution. Unlike traditional
functions, which run to completion once invoked, coroutines can pause execution and yield
control back to the caller, resuming later from where they left off. This makes coroutines ideal
for writing asynchronous, non-blocking code, such as event loops, generators, and cooperative
multitasking.
This section will provide an exhaustive exploration of coroutines in C++, covering their syntax,
usage, and practical applications. We will also delve into advanced techniques for managing
coroutines and integrating them with other concurrency features. These concepts are critical for
advanced learners and professionals who want to write modern, efficient, and maintainable
asynchronous code.

2.3.2 What Are Coroutines?


1. Definition

A coroutine is a function that can suspend its execution and resume later, allowing for
cooperative multitasking. Coroutines are particularly useful for asynchronous
programming, where tasks need to wait for I/O operations, timers, or other events without
blocking the entire program.

2. Key Characteristics

(a) Suspend and Resume: Coroutines can suspend execution using keywords like
co await and co yield, and resume later from the point of suspension.
91

(b) Stateful: Coroutines maintain their state between suspensions, allowing them to
resume execution with all local variables intact.
(c) Asynchronous: Coroutines are well-suited for asynchronous programming, enabling
non-blocking execution of tasks.

3. Example: Basic Coroutine

#include <iostream>
#include <coroutine>

struct MyCoroutine {
struct promise_type {
MyCoroutine get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};

MyCoroutine myCoroutine() {
std::cout << "Coroutine started" << std::endl;
co_await std::suspend_always{};
std::cout << "Coroutine resumed" << std::endl;
}

int main() {
auto coro = myCoroutine();
std::cout << "Main function" << std::endl;
}

• Output:
92

Coroutine started
Main function

• Explanation: The coroutine starts execution but suspends at co await


std::suspend always{}. Since the coroutine is not resumed, the ”Coroutine
resumed” message is not printed.

2.3.3 Coroutine Components


1. Coroutine Handle

A coroutine handle is an object that represents the coroutine's state and allows you to
resume or destroy it. It is typically accessed through the promise type of the
coroutine.

2. Promise Type

The promise type is a user-defined type that controls the behavior of the coroutine. It
must define methods like get return object(), initial suspend(),
final suspend(), return void(), and unhandled exception().

3. Awaitable Type

An awaitable type is a type that can be used with the co await keyword. It defines
methods like await ready(), await suspend(), and await resume().

2.3.4 Writing Coroutines


1. Example: Simple Coroutine
93

#include <iostream>
#include <coroutine>

struct MyCoroutine {
struct promise_type {
MyCoroutine get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};

MyCoroutine myCoroutine() {
std::cout << "Coroutine started" << std::endl;
co_await std::suspend_always{};
std::cout << "Coroutine resumed" << std::endl;
}

int main() {
auto coro = myCoroutine();
std::cout << "Main function" << std::endl;
}

• Output:

Coroutine started
Main function

2. Example: Resuming a Coroutine


94

To resume a coroutine, you need to store its handle and call the resume() method.

#include <iostream>
#include <coroutine>

struct MyCoroutine {
struct promise_type {
MyCoroutine get_return_object() { return
,→ MyCoroutine{std::coroutine_handle<promise_type>::from_promise(*th
,→ }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};

std::coroutine_handle<promise_type> handle;

MyCoroutine(std::coroutine_handle<promise_type> h) : handle(h) {}
˜MyCoroutine() { if (handle) handle.destroy(); }

void resume() { if (handle) handle.resume(); }


};

MyCoroutine myCoroutine() {
std::cout << "Coroutine started" << std::endl;
co_await std::suspend_always{};
std::cout << "Coroutine resumed" << std::endl;
}

int main() {
auto coro = myCoroutine();
std::cout << "Main function" << std::endl;
95

coro.resume();
}

• Output:

Coroutine started
Main function
Coroutine resumed

2.3.5 Practical Applications


1. Asynchronous I/O
Coroutines can be used to perform asynchronous I/O operations without blocking the
main thread.

#include <iostream>
#include <coroutine>
#include <chrono>
#include <thread>

struct AsyncTask {
struct promise_type {
AsyncTask get_return_object() { return
,→ AsyncTask{std::coroutine_handle<promise_type>::from_promise(*this
,→ }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
96

};

std::coroutine_handle<promise_type> handle;

AsyncTask(std::coroutine_handle<promise_type> h) : handle(h) {}
˜AsyncTask() { if (handle) handle.destroy(); }

void resume() { if (handle) handle.resume(); }


};

AsyncTask asyncIO() {
std::cout << "Starting async I/O" << std::endl;
co_await std::suspend_always{};
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Async I/O completed" << std::endl;
}

int main() {
auto task = asyncIO();
std::cout << "Main function" << std::endl;
task.resume();
}

• Output:

Starting async I/O


Main function
Async I/O completed

2. Generators
97

Coroutines can be used to implement generators, which produce a sequence of values on


demand.

#include <iostream>
#include <coroutine>

template <typename T>


struct Generator {
struct promise_type {
T value;
Generator get_return_object() { return
,→ Generator{std::coroutine_handle<promise_type>::from_promise(*this
,→ }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
std::suspend_always yield_value(T v) { value = v; return {};
,→ }
};

std::coroutine_handle<promise_type> handle;

Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
˜Generator() { if (handle) handle.destroy(); }

T next() {
handle.resume();
return handle.promise().value;
}
};

Generator<int> generateNumbers(int start, int end) {


98

for (int i = start; i <= end; ++i) {


co_yield i;
}
}

int main() {
auto gen = generateNumbers(1, 5);
for (int i = 0; i < 5; ++i) {
std::cout << gen.next() << std::endl;
}
}

• Output:

1
2
3
4
5

3. Event Loops
Coroutines can be used to implement event loops, where tasks are executed cooperatively.

#include <iostream>
#include <coroutine>
#include <queue>
#include <functional>

struct EventLoop {
std::queue<std::function<void()>> tasks;
99

void schedule(std::function<void()> task) {


tasks.push(task);
}

void run() {
while (!tasks.empty()) {
auto task = tasks.front();
tasks.pop();
task();
}
}
};

struct Task {
struct promise_type {
Task get_return_object() { return
,→ Task{std::coroutine_handle<promise_type>::from_promise(*this)};
,→ }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};

std::coroutine_handle<promise_type> handle;

Task(std::coroutine_handle<promise_type> h) : handle(h) {}
˜Task() { if (handle) handle.destroy(); }

void resume() { if (handle) handle.resume(); }


};
100

Task myTask(EventLoop& loop) {


std::cout << "Task started" << std::endl;
co_await std::suspend_always{};
std::cout << "Task resumed" << std::endl;
}

int main() {
EventLoop loop;
auto task = myTask(loop);
loop.schedule([&task] { task.resume(); });
loop.run();
}

• Output:

Task started
Task resumed

2.3.6 Advanced Techniques


1. Custom Awaitables

You can define custom awaitable types to control the behavior of co await.

#include <iostream>
#include <coroutine>

struct MyAwaitable {
bool await_ready() { return false; }
101

void await_suspend(std::coroutine_handle<> handle) {


std::cout << "Suspending coroutine" << std::endl;
handle.resume();
}
void await_resume() { std::cout << "Resuming coroutine" <<
,→ std::endl; }
};

struct MyCoroutine {
struct promise_type {
MyCoroutine get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};

MyCoroutine myCoroutine() {
std::cout << "Coroutine started" << std::endl;
co_await MyAwaitable{};
std::cout << "Coroutine resumed" << std::endl;
}

int main() {
auto coro = myCoroutine();
std::cout << "Main function" << std::endl;
}

• Output:
102

Coroutine started
Suspending coroutine
Resuming coroutine
Coroutine resumed
Main function

2. Error Handling
Coroutines can handle exceptions using the unhandled exception() method in the
promise type.

#include <iostream>
#include <coroutine>
#include <stdexcept>

struct MyCoroutine {
struct promise_type {
MyCoroutine get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::cerr << "Exception caught: "
,→ << std::current_exception() << std::endl; }
};
};

MyCoroutine myCoroutine() {
std::cout << "Coroutine started" << std::endl;
throw std::runtime_error("Something went wrong");
co_await std::suspend_always{};
std::cout << "Coroutine resumed" << std::endl;
}
103

int main() {
auto coro = myCoroutine();
std::cout << "Main function" << std::endl;
}

• Output:

Coroutine started
Exception caught: Something went wrong
Main function

2.3.7 Conclusion
Coroutines are a powerful feature in C++20 that enable asynchronous programming by allowing
functions to suspend and resume execution. By mastering coroutines, you can write efficient,
non-blocking code for tasks like asynchronous I/O, generators, and event loops.
This section has provided a comprehensive overview of coroutines in C++, covering their syntax,
usage, and practical applications. With this knowledge, you are well-equipped to tackle complex
asynchronous programming challenges and leverage the full power of modern C++. These
techniques are essential for developing high-performance, responsive, and robust software
systems.
Chapter 3

Error Handling

3.1 Exceptions and noexcept

3.1.1 Introduction to Error Handling in C++

Error handling is a critical aspect of software development, ensuring that programs can
gracefully handle unexpected situations and maintain robustness. In C++, exceptions are the
primary mechanism for handling errors, allowing you to separate error-handling code from
normal program logic. The noexcept specifier, introduced in C++11, provides a way to
indicate that a function will not throw exceptions, enabling optimizations and improving code
clarity.
This section will provide an in-depth exploration of exceptions and the noexcept specifier,
covering their syntax, usage, and practical applications. These concepts are essential for
advanced learners and professionals who want to write robust, maintainable, and efficient C++
code.

104
105

3.1.2 Exceptions
Definition An exception is an event that occurs during the execution of a program that
disrupts the normal flow of instructions. Exceptions are typically used to handle errors, such as
invalid input, resource allocation failures, or unexpected conditions.

Syntax Exceptions are thrown using the throw keyword and caught using try and catch
blocks.

#include <iostream>
#include <stdexcept>

void riskyFunction(int value) {


if (value < 0) {
throw std::invalid_argument("Negative value not allowed");
}
std::cout << "Value is valid: " << value << std::endl;
}

int main() {
try {
riskyFunction(-1);
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
}

• Output:

Caught exception: Negative value not allowed


106

Key Points

1. Throwing Exceptions: Use the throw keyword to throw an exception. The thrown
object is typically an instance of a class derived from std::exception.

2. Catching Exceptions: Use try and catch blocks to handle exceptions. The catch
block specifies the type of exception to handle.

3. Exception Hierarchy: The C++ Standard Library provides a hierarchy of exception


classes, with std::exception as the base class. Common derived classes include
std::runtime error, std::invalid argument, and std::out of range.

Example: Custom Exception Class You can define custom exception classes by deriving
from std::exception.

#include <iostream>
#include <stdexcept>

class MyException : public std::exception {


public:
const char* what() const noexcept override {
return "Custom exception occurred";
}
};

void riskyFunction() {
throw MyException();
}

int main() {
try {
riskyFunction();
107

} catch (const std::exception& e) {


std::cerr << "Caught exception: " << e.what() << std::endl;
}
}

• Output:

Caught exception: Custom exception occurred

3.1.3 noexcept Specifier


Definition The noexcept specifier is used to indicate that a function will not throw
exceptions. It can be applied to function declarations and definitions, enabling optimizations and
improving code clarity.

void myFunction() noexcept {


// Function implementation
}

Syntax

• noexcept: Indicates that the function will not throw exceptions.

#include <iostream>

void safeFunction() noexcept {


108

std::cout << "This function does not throw exceptions" << std::endl;
}

int main() {
safeFunction();
}

Example: Basic noexcept Usage

• Output:

This function does not throw exceptions

Key Points

1. Optimizations: Functions marked noexcept can be optimized by the compiler, as it


does not need to generate exception-handling code.

2. Error Handling: If a noexcept function throws an exception, the program will


terminate by calling std::terminate().

3. Conditional noexcept: You can specify conditions under which a function is


noexcept.

template <typename T>


void myFunction(T value)
,→ noexcept(std::is_nothrow_copy_constructible<T>::value) {
// Function implementation
}
109

3.1.4 Practical Applications


1. Resource Management Exceptions are commonly used for resource management, ensuring
that resources are properly released even in the presence of errors.

#include <iostream>
#include <stdexcept>

class Resource {
public:
Resource() { std::cout << "Resource acquired" << std::endl; }
˜Resource() { std::cout << "Resource released" << std::endl; }
};

void riskyFunction() {
Resource res;
throw std::runtime_error("Something went wrong");
}

int main() {
try {
riskyFunction();
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
}

• Output:

Resource acquired
Resource released
Caught exception: Something went wrong
110

2. Error Propagation Exceptions allow errors to propagate up the call stack, enabling
centralized error handling.

#include <iostream>
#include <stdexcept>

void innerFunction() {
throw std::runtime_error("Error in inner function");
}

void outerFunction() {
try {
innerFunction();
} catch (const std::exception& e) {
std::cerr << "Caught exception in outer function: " << e.what() <<
,→ std::endl;
throw; // Re-throw the exception
}
}

int main() {
try {
outerFunction();
} catch (const std::exception& e) {
std::cerr << "Caught exception in main: " << e.what() <<
,→ std::endl;
}
}

• Output:
111

Caught exception in outer function: Error in inner function


Caught exception in main: Error in inner function

3. noexcept in Move Semantics The noexcept specifier is particularly important in


move semantics, as it allows the compiler to optimize operations like std::vector resizing.

#include <iostream>
#include <vector>

class MyClass {
public:
MyClass() { std::cout << "Constructed" << std::endl; }
˜MyClass() { std::cout << "Destructed" << std::endl; }
MyClass(MyClass&&) noexcept { std::cout << "Move constructed" <<
,→ std::endl; }
MyClass& operator=(MyClass&&) noexcept { std::cout << "Move assigned"
,→ << std::endl; return *this; }
};

int main() {
std::vector<MyClass> vec;
vec.push_back(MyClass()); // Move semantics are used
}

• Output:

Constructed
Move constructed
Destructed
Destructed
112

3.1.5 Advanced Techniques


1. Exception Safety Exception safety refers to the guarantees a function provides regarding
exceptions. There are three levels of exception safety:

1. Basic Guarantee: No resources are leaked, and the program remains in a valid state.

2. Strong Guarantee: The operation is atomic; if an exception occurs, the program state is
rolled back to its original state.

3. No-throw Guarantee: The function will not throw exceptions.

Example: Strong Guarantee

#include <iostream>
#include <stdexcept>

class MyClass {
public:
MyClass(int value) : value(new int(value)) {}
˜MyClass() { delete value; }

void swap(MyClass& other) noexcept {


std::swap(value, other.value);
}

MyClass(const MyClass& other) : value(new int(*other.value)) {}


MyClass& operator=(const MyClass& other) {
MyClass temp(other);
swap(temp);
return *this;
}
113

int getValue() const { return *value; }

private:
int* value;
};

int main() {
MyClass a(42);
MyClass b(100);

try {
a = b; // Strong guarantee
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}

std::cout << "a: " << a.getValue() << ", b: " << b.getValue() <<
,→ std::endl;
}

• Output:

a: 100, b: 100

2. noexcept and Move Semantics Marking move constructors and move assignment
operators as noexcept enables optimizations in standard containers.
114

#include <iostream>
#include <vector>

class MyClass {
public:
MyClass() { std::cout << "Constructed" << std::endl; }
˜MyClass() { std::cout << "Destructed" << std::endl; }
MyClass(MyClass&&) noexcept { std::cout << "Move constructed" <<
,→ std::endl; }
MyClass& operator=(MyClass&&) noexcept { std::cout << "Move assigned"
,→ << std::endl; return *this; }
};

int main() {
std::vector<MyClass> vec;
vec.push_back(MyClass()); // Move semantics are used
}

• Output:

Constructed
Move constructed
Destructed
Destructed

3.1.6 Conclusion
Exceptions and the noexcept specifier are essential tools for error handling in C++.
Exceptions allow you to handle errors gracefully and propagate them up the call stack, while
115

noexcept enables optimizations and improves code clarity by indicating that a function will
not throw exceptions.
This section has provided a comprehensive overview of exceptions and noexcept, covering
their syntax, usage, and practical applications. With this knowledge, you are well-equipped to
write robust, maintainable, and efficient C++ code. These techniques are essential for developing
high-quality software systems that can handle unexpected situations gracefully.
116

3.2 std::optional, std::expected (C++23)

3.2.1 Introduction to std::optional and std::expected


Error handling in C++ has traditionally relied on exceptions, but exceptions are not always the
best fit for every scenario. For example, in performance-critical code or in situations where
exceptions are disabled, alternative error-handling mechanisms are needed. C++17 introduced
std::optional to represent optional values, and C++23 introduced std::expected to
represent values that can either be valid or contain an error. These types provide a more
expressive and efficient way to handle errors without relying on exceptions.
This section will provide an in-depth exploration of std::optional and
std::expected, covering their syntax, usage, and practical applications. These concepts are
essential for advanced learners and professionals who want to write modern, efficient, and
maintainable C++ code.

3.2.2 std::optional
Definition std::optional is a template class that represents an optional value. It can
either contain a value of a specified type or be empty. This is particularly useful for functions
that may or may not return a valid result.

#include <optional>

std::optional<int> getValue(bool condition) {


if (condition) {
return 42;
} else {
return std::nullopt; // Represents an empty optional
117

}
}

Syntax

• std::optional<T>: Represents an optional value of type T.

• std::nullopt: A special constant representing an empty optional.

#include <iostream>
#include <optional>

std::optional<int> getValue(bool condition) {


if (condition) {
return 42;
} else {
return std::nullopt;
}
}

int main() {
auto value = getValue(true);
if (value) {
std::cout << "Value: " << *value << std::endl;
} else {
std::cout << "No value" << std::endl;
}
}

Example: Basic std::optional Usage


118

• Output:

Value: 42

Key Points

1. Accessing the Value: Use the * operator or the value() method to access the contained
value. If the optional is empty, calling value() will throw
std::bad optional access.

2. Checking for a Value: Use the has value() method or the implicit conversion to
bool to check if the optional contains a value.

3. Default Value: Use the value or() method to provide a default value if the optional is
empty.

#include <iostream>
#include <optional>

std::optional<int> getValue(bool condition) {


if (condition) {
return 42;
} else {
return std::nullopt;
}
}

int main() {
auto value = getValue(false);
std::cout << "Value: " << value.value_or(100) << std::endl;
}
119

• Output:

Value: 100

3.2.3 std::expected (C++23)


Definition std::expected is a template class introduced in C++23 that represents a value
that can either be valid or contain an error. It is similar to std::optional, but instead of
being empty, it can hold an error value. This makes it ideal for functions that can fail and need to
return an error code or message.

#include <expected>

std::expected<int, std::string> getValue(bool condition) {


if (condition) {
return 42;
} else {
return std::unexpected("Error: Condition failed");
}
}

Syntax

• std::expected<T, E>: Represents a value of type T or an error of type E.

• std::unexpected<E>: Represents an error value of type E.


120

#include <iostream>
#include <expected>
#include <string>

std::expected<int, std::string> getValue(bool condition) {


if (condition) {
return 42;
} else {
return std::unexpected("Error: Condition failed");
}
}

int main() {
auto result = getValue(true);
if (result) {
std::cout << "Value: " << *result << std::endl;
} else {
std::cerr << "Error: " << result.error() << std::endl;
}
}

Example: Basic std::expected Usage

• Output:

Value: 42

Key Points

1. Accessing the Value: Use the * operator or the value() method to access the contained
121

value. If the std::expected contains an error, calling value() will throw


std::bad expected access.

2. Accessing the Error: Use the error() method to access the error value.

3. Checking for a Value: Use the has value() method or the implicit conversion to
bool to check if the std::expected contains a value.

#include <iostream>
#include <expected>
#include <string>

std::expected<int, std::string> divide(int a, int b) {


if (b == 0) {
return std::unexpected("Error: Division by zero");
}
return a / b;
}

int main() {
auto result = divide(10, 0);
if (result) {
std::cout << "Result: " << *result << std::endl;
} else {
std::cerr << result.error() << std::endl;
}
}

Example: Handling Errors with std::expected

• Output:
122

Error: Division by zero

3.2.4 Practical Applications


1. Optional Function Return Values std::optional is ideal for functions that may or
may not return a valid result, such as searching in a collection.

#include <iostream>
#include <optional>
#include <vector>

std::optional<int> findValue(const std::vector<int>& vec, int value) {


for (int i : vec) {
if (i == value) {
return i;
}
}
return std::nullopt;
}

int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto result = findValue(vec, 3);
if (result) {
std::cout << "Found value: " << *result << std::endl;
} else {
std::cout << "Value not found" << std::endl;
}
}

• Output:
123

Found value: 3

2. Error Handling in Parsing std::expected is ideal for functions that can fail and need
to return an error message, such as parsing input.

#include <iostream>
#include <expected>
#include <string>

std::expected<int, std::string> parseInteger(const std::string& input) {


try {
return std::stoi(input);
} catch (const std::invalid_argument&) {
return std::unexpected("Error: Invalid argument");
} catch (const std::out_of_range&) {
return std::unexpected("Error: Out of range");
}
}

int main() {
auto result = parseInteger("abc");
if (result) {
std::cout << "Parsed value: " << *result << std::endl;
} else {
std::cerr << result.error() << std::endl;
}
}

• Output:
124

Error: Invalid argument

3. Combining std::optional and std::expected std::optional and


std::expected can be combined to handle complex error scenarios.

#include <iostream>
#include <optional>
#include <expected>
#include <string>

std::optional<std::expected<int, std::string>> complexOperation(bool


,→ condition1, bool condition2) {
if (!condition1) {
return std::nullopt;
}
if (!condition2) {
return std::unexpected("Error: Condition 2 failed");
}
return 42;
}

int main() {
auto result = complexOperation(true, false);
if (result) {
if (*result) {
std::cout << "Value: " << **result << std::endl;
} else {
std::cerr << "Error: " << result->error() << std::endl;
}
} else {
std::cerr << "No result" << std::endl;
125

}
}

• Output:

Error: Condition 2 failed

3.2.5 Advanced Techniques


1. Monadic Operations std::optional and std::expected support monadic
operations like and then, or else, and transform, which allow for chaining operations
in a functional style.

Example: Monadic Operations with std::optional

#include <iostream>
#include <optional>

std::optional<int> addOne(int value) {


return value + 1;
}

std::optional<int> multiplyByTwo(int value) {


return value * 2;
}

int main() {
std::optional<int> value = 42;
auto result = value.and_then(addOne).and_then(multiplyByTwo);
if (result) {
126

std::cout << "Result: " << *result << std::endl;


} else {
std::cout << "No result" << std::endl;
}
}

• Output:

Result: 86

Example: Monadic Operations with std::expected

#include <iostream>
#include <expected>
#include <string>

std::expected<int, std::string> addOne(int value) {


return value + 1;
}

std::expected<int, std::string> multiplyByTwo(int value) {


return value * 2;
}

int main() {
std::expected<int, std::string> value = 42;
auto result = value.and_then(addOne).and_then(multiplyByTwo);
if (result) {
std::cout << "Result: " << *result << std::endl;
} else {
127

std::cerr << "Error: " << result.error() << std::endl;


}
}

• Output:

Result: 86

2. Custom Error Types You can define custom error types to use with std::expected.

#include <iostream>
#include <expected>
#include <string>

enum class ErrorCode {


InvalidInput,
OutOfRange,
DivisionByZero
};

std::expected<int, ErrorCode> divide(int a, int b) {


if (b == 0) {
return std::unexpected(ErrorCode::DivisionByZero);
}
return a / b;
}

int main() {
auto result = divide(10, 0);
if (result) {
128

std::cout << "Result: " << *result << std::endl;


} else {
std::cerr << "Error code: " << static_cast<int>(result.error()) <<
,→ std::endl;
}
}

• Output:

Error code: 2

3.2.6 Conclusion
std::optional and std::expected are powerful tools for error handling in modern
C++. std::optional provides a way to represent optional values, while
std::expected extends this by allowing the representation of either a value or an error.
These types enable more expressive and efficient error handling, particularly in scenarios where
exceptions are not suitable.
This section has provided a comprehensive overview of std::optional and
std::expected, covering their syntax, usage, and practical applications. With this
knowledge, you are well-equipped to write robust, maintainable, and efficient C++ code. These
techniques are essential for developing high-quality software systems that can handle errors
gracefully and efficiently.
Chapter 4

Advanced Libraries

4.1 Filesystem Library (std::filesystem)

4.1.1 Introduction to the Filesystem Library

The C++17 Standard Library introduced the <filesystem> library, which provides a
comprehensive set of tools for working with file systems. This library allows you to perform
operations such as creating, deleting, and traversing directories, querying file properties, and
manipulating file paths. The std::filesystem library is part of the C++ Standard Library
and is designed to be portable across different operating systems, making it an essential tool for
advanced learners and professionals who need to handle file system operations in a modern and
efficient way.
This section will provide an in-depth exploration of the std::filesystem library, covering
its syntax, usage, and practical applications. We will also delve into advanced techniques for
managing file systems and integrating them with other C++ features.

129
130

4.1.2 Key Components of std::filesystem


1. std::filesystem::pathstd::filesystem::path

The std::filesystem::path class represents a file system path. It provides


methods for manipulating and querying paths, such as concatenation, decomposition, and
normalization.

2. Example: Basic std::filesystem::path Usage

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
fs::path p = "/usr/local/bin";
std::cout << "Path: " << p << std::endl;
std::cout << "Parent path: " << p.parent_path() << std::endl;
std::cout << "Filename: " << p.filename() << std::endl;
std::cout << "Extension: " << p.extension() << std::endl;
}

• Output:

Path: "/usr/local/bin"
Parent path: "/usr/local"
Filename: "bin"
Extension: ""

3. Key Points
131

(a) Path Manipulation: Use methods like append(), concat(), and


remove filename() to manipulate paths.
(b) Path Decomposition: Use methods like parent path(), filename(), and
extension() to decompose paths.
(c) Path Normalization: Use lexically normal() to normalize paths.

fs::path p = "/usr/local/../bin";
std::cout << "Normalized path: " << p.lexically_normal() <<
,→ std::endl;

• Output:

Normalized path: "/usr/bin"

4.1.3 File and Directory Operations


1. Creating and Deleting Directories
You can create and delete directories using
std::filesystem::create directory() and
std::filesystem::remove().

2. Example: Creating and Deleting Directories

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;
132

int main() {
fs::path dir = "test_dir";
if (fs::create_directory(dir)) {
std::cout << "Directory created: " << dir << std::endl;
}
if (fs::remove(dir)) {
std::cout << "Directory deleted: " << dir << std::endl;
}
}

• Output:

Directory created: "test_dir"


Directory deleted: "test_dir"

3. Key Points

(a) Creating Directories: Use create directory() to create a single directory


and create directories() to create a directory hierarchy.
(b) Deleting Directories: Use remove() to delete a single directory or file and
remove all() to delete a directory and its contents.

4. Example: Creating and Deleting Directory Hierarchies

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;
133

int main() {
fs::path dir = "test_dir/sub_dir";
if (fs::create_directories(dir)) {
std::cout << "Directories created: " << dir << std::endl;
}
if (fs::remove_all("test_dir")) {
std::cout << "Directories deleted: test_dir" << std::endl;
}
}

• Output:

Directories created: "test_dir/sub_dir"


Directories deleted: test_dir

4.1.4 File Operations


1. Copying and Moving Files

You can copy and move files using std::filesystem::copy() and


std::filesystem::rename().

2. Example: Copying and Moving Files

#include <iostream>
#include <filesystem>
#include <fstream>

namespace fs = std::filesystem;
134

int main() {
fs::path src = "source.txt";
fs::path dst = "destination.txt";

// Create a source file


std::ofstream(src.string()) << "Hello, World!";

// Copy the file


fs::copy(src, dst);
std::cout << "File copied: " << dst << std::endl;

// Move the file


fs::rename(dst, "moved.txt");
std::cout << "File moved: moved.txt" << std::endl;

// Clean up
fs::remove(src);
fs::remove("moved.txt");
}

• Output:

File copied: "destination.txt"


File moved: moved.txt

3. Key Points

(a) Copying Files: Use copy() to copy files or directories. You can specify options
like copy options::recursive for recursive copying.
135

(b) Moving Files: Use rename() to move files or directories. This operation is
typically faster than copying and deleting.

4. Example: Recursive Copy

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
fs::path src = "source_dir";
fs::path dst = "destination_dir";

// Create a source directory with a file


fs::create_directories(src);
std::ofstream(src / "file.txt") << "Hello, World!";

// Copy the directory recursively


fs::copy(src, dst, fs::copy_options::recursive);
std::cout << "Directory copied recursively: " << dst <<
,→ std::endl;

// Clean up
fs::remove_all(src);
fs::remove_all(dst);
}

• Output:
136

Directory copied recursively: "destination_dir"

4.1.5 Querying File Properties


1. File Status and Permissions

You can query file properties such as status, permissions, and size using
std::filesystem::status(), std::filesystem::permissions(), and
std::filesystem::file size().

2. Example: Querying File Properties

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
fs::path p = "example.txt";
std::ofstream(p.string()) << "Hello, World!";

// Query file status


fs::file_status status = fs::status(p);
std::cout << "File type: " << static_cast<int>(status.type()) <<
,→ std::endl;

// Query file permissions


fs::perms permissions = status.permissions();
std::cout << "File permissions: " << static_cast<int>(permissions)
,→ << std::endl;
137

// Query file size


std::cout << "File size: " << fs::file_size(p) << " bytes" <<
,→ std::endl;

// Clean up
fs::remove(p);
}

• Output:

File type: 1
File permissions: 436
File size: 13 bytes

3. Key Points

(a) File Status: Use status() to get the status of a file or directory. The status
includes the file type and permissions.
(b) File Permissions: Use permissions() to get or set the permissions of a file or
directory.
(c) File Size: Use file size() to get the size of a file.

4. Example: Setting File Permissions

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;
138

int main() {
fs::path p = "example.txt";
std::ofstream(p.string()) << "Hello, World!";

// Set file permissions


fs::permissions(p, fs::perms::owner_all | fs::perms::group_read |
,→ fs::perms::others_read);

// Query file permissions


fs::perms permissions = fs::status(p).permissions();
std::cout << "File permissions: " << static_cast<int>(permissions)
,→ << std::endl;

// Clean up
fs::remove(p);
}

• Output:

File permissions: 436

4.1.6 Advanced Techniques


1. Directory Iteration
You can iterate over the contents of a directory using
std::filesystem::directory iterator and
std::filesystem::recursive directory iterator.

Example: Directory Iteration


139

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
fs::path dir = "test_dir";
fs::create_directories(dir);
std::ofstream(dir / "file1.txt") << "File 1";
std::ofstream(dir / "file2.txt") << "File 2";

// Iterate over directory contents


for (const auto& entry : fs::directory_iterator(dir)) {
std::cout << "Found: " << entry.path() << std::endl;
}

// Clean up
fs::remove_all(dir);
}

• Output:

Found: "test_dir/file1.txt"
Found: "test_dir/file2.txt"

Example: Recursive Directory Iteration

#include <iostream>
#include <filesystem>
140

namespace fs = std::filesystem;

int main() {
fs::path dir = "test_dir";
fs::create_directories(dir / "sub_dir");
std::ofstream(dir / "file1.txt") << "File 1";
std::ofstream(dir / "sub_dir/file2.txt") << "File 2";

// Recursively iterate over directory contents


for (const auto& entry : fs::recursive_directory_iterator(dir)) {
std::cout << "Found: " << entry.path() << std::endl;
}

// Clean up
fs::remove_all(dir);
}

• Output:

Found: "test_dir/file1.txt"
Found: "test_dir/sub_dir/file2.txt"

2. Error Handling

The std::filesystem library provides error handling through


std::filesystem::filesystem error, which is thrown when file system
operations fail.

Example: Error Handling


141

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
try {
fs::path p = "non_existent_file.txt";
fs::file_size(p); // This will throw an exception
} catch (const fs::filesystem_error& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}

• Output:

Error: filesystem error: cannot get file size: No such file or


,→ directory [non_existent_file.txt]

4.1.7 Conclusion
The std::filesystem library is a powerful and versatile tool for working with file systems
in C++. It provides a comprehensive set of functions for manipulating paths, creating and
deleting directories, copying and moving files, and querying file properties. By mastering the
std::filesystem library, you can write efficient, portable, and maintainable code for
handling file system operations.
This section has provided a comprehensive overview of the std::filesystem library,
covering its syntax, usage, and practical applications. With this knowledge, you are
well-equipped to tackle complex file system challenges and leverage the full power of modern
142

C++. These techniques are essential for developing high-quality software systems that interact
with the file system in a robust and efficient manner.
143

4.2 Networking (C++20 and Beyond)

4.2.1 Introduction to Networking in C++


Networking is a fundamental aspect of modern software development, enabling applications to
communicate over the internet or local networks. While C++ has traditionally relied on
third-party libraries like Boost.Asio or platform-specific APIs for networking, the C++20
standard introduced a new networking library as part of the C++ Standard Library. This library,
based on the Boost.Asio library, provides a portable and efficient way to handle networking
tasks such as creating sockets, sending and receiving data, and managing connections.
This section will provide an in-depth exploration of the C++20 networking library, covering its
syntax, usage, and practical applications. We will also delve into advanced techniques for
managing network connections and integrating them with other C++ features. These concepts
are essential for advanced learners and professionals who want to write modern, efficient, and
maintainable networked applications.

4.2.2 Key Components of the Networking Library


1. std::net
The C++20 networking library is part of the <net> header and provides a set of classes
and functions for working with network sockets, endpoints, and protocols. The library is
designed to be portable across different operating systems and supports both synchronous
and asynchronous operations.

2. Example: Basic Networking Usage

#include <iostream>
#include <net>
144

namespace net = std::net;

int main() {
net::io_context io_context;

net::ip::tcp::socket socket(io_context);
net::ip::tcp::endpoint
,→ endpoint(net::ip::make_address("127.0.0.1"), 8080);

socket.connect(endpoint);

std::string message = "Hello, Server!";


net::write(socket, net::buffer(message));

char reply[1024];
size_t reply_length = net::read(socket, net::buffer(reply,
,→ sizeof(reply)));

std::cout << "Reply from server: " << std::string(reply,


,→ reply_length) << std::endl;

socket.close();
}

• Output:

Reply from server: Hello, Client!

3. Key Points
145

(a) net::io context: The io context class is the central object for managing
I/O operations. It provides the event loop for asynchronous operations.
(b) net::ip::tcp::socket: The socket class represents a TCP socket. It
provides methods for connecting, sending, and receiving data.
(c) net::ip::tcp::endpoint: The endpoint class represents a network
endpoint, consisting of an IP address and a port number.

4.2.3 Synchronous Networking


1. Creating a TCP Client
You can create a TCP client using the net::ip::tcp::socket class to connect to a
server and exchange data.

2. Example: TCP Client

#include <iostream>
#include <net>

namespace net = std::net;

int main() {
net::io_context io_context;

net::ip::tcp::socket socket(io_context);
net::ip::tcp::endpoint
,→ endpoint(net::ip::make_address("127.0.0.1"), 8080);

socket.connect(endpoint);

std::string message = "Hello, Server!";


net::write(socket, net::buffer(message));
146

char reply[1024];
size_t reply_length = net::read(socket, net::buffer(reply,
,→ sizeof(reply)));

std::cout << "Reply from server: " << std::string(reply,


,→ reply_length) << std::endl;

socket.close();
}

• Output:

Reply from server: Hello, Client!

3. Key Points

(a) Connecting to a Server: Use the connect() method to connect to a server.

(b) Sending Data: Use the net::write() function to send data to the server.

(c) Receiving Data: Use the net::read() function to receive data from the server.

4. Creating a TCP Server

You can create a TCP server using the net::ip::tcp::acceptor class to accept
incoming connections and exchange data.

5. Example: TCP Server


147

#include <iostream>
#include <net>

namespace net = std::net;

int main() {
net::io_context io_context;

net::ip::tcp::acceptor acceptor(io_context,
,→ net::ip::tcp::endpoint(net::ip::tcp::v4(), 8080));

net::ip::tcp::socket socket(io_context);
acceptor.accept(socket);

char request[1024];
size_t request_length = net::read(socket, net::buffer(request,
,→ sizeof(request)));

std::cout << "Request from client: " << std::string(request,


,→ request_length) << std::endl;

std::string reply = "Hello, Client!";


net::write(socket, net::buffer(reply));

socket.close();
}

• Output:

Request from client: Hello, Server!


148

6. Key Points

(a) Accepting Connections: Use the accept() method to accept incoming


connections.
(b) Handling Requests: Use the net::read() function to receive data from the
client.
(c) Sending Replies: Use the net::write() function to send data to the client.

4.2.4 Asynchronous Networking


1. Asynchronous TCP Client
You can create an asynchronous TCP client using the net::ip::tcp::socket class
with asynchronous methods.

2. Example: Asynchronous TCP Client

#include <iostream>
#include <net>

namespace net = std::net;

void handle_connect(const std::error_code& ec, net::ip::tcp::socket&


,→ socket) {
if (!ec) {
std::string message = "Hello, Server!";
net::async_write(socket, net::buffer(message),
[&socket](const std::error_code& ec, std::size_t
,→ /*length*/) {
if (!ec) {
char reply[1024];
net::async_read(socket, net::buffer(reply,
,→ sizeof(reply)),
149

[&socket, reply](const std::error_code& ec,


,→ std::size_t length) {
if (!ec) {
std::cout << "Reply from server: " <<
,→ std::string(reply, length) <<
,→ std::endl;
socket.close();
}
});
}
});
}
}

int main() {
net::io_context io_context;

net::ip::tcp::socket socket(io_context);
net::ip::tcp::endpoint
,→ endpoint(net::ip::make_address("127.0.0.1"), 8080);

socket.async_connect(endpoint, [&socket](const std::error_code&


,→ ec) {
handle_connect(ec, socket);
});

io_context.run();
}

• Output:
150

Reply from server: Hello, Client!

3. Key Points

(a) Asynchronous Operations: Use async connect(), async write(), and


async read() for asynchronous operations.
(b) Event Loop: Use io context.run() to start the event loop and process
asynchronous operations.

4. Asynchronous TCP Server


You can create an asynchronous TCP server using the net::ip::tcp::acceptor
class with asynchronous methods.

5. Example: Asynchronous TCP Server

#include <iostream>
#include <net>

namespace net = std::net;

void handle_request(net::ip::tcp::socket& socket) {


char request[1024];
net::async_read(socket, net::buffer(request, sizeof(request)),
[&socket, request](const std::error_code& ec, std::size_t
,→ length) {
if (!ec) {
std::cout << "Request from client: " <<
,→ std::string(request, length) << std::endl;

std::string reply = "Hello, Client!";


151

net::async_write(socket, net::buffer(reply),
[&socket](const std::error_code& ec, std::size_t
,→ /*length*/) {
if (!ec) {
socket.close();
}
});
}
});
}

void handle_accept(net::ip::tcp::acceptor& acceptor,


,→ net::ip::tcp::socket& socket) {
acceptor.async_accept(socket, [&acceptor, &socket](const
,→ std::error_code& ec) {
if (!ec) {
handle_request(socket);
handle_accept(acceptor, socket);
}
});
}

int main() {
net::io_context io_context;

net::ip::tcp::acceptor acceptor(io_context,
,→ net::ip::tcp::endpoint(net::ip::tcp::v4(), 8080));
net::ip::tcp::socket socket(io_context);

handle_accept(acceptor, socket);

io_context.run();
152

• Output:

Request from client: Hello, Server!

6. Key Points

(a) Asynchronous Accept: Use async accept() to accept incoming connections


asynchronously.
(b) Handling Requests: Use async read() and async write() to handle
requests and send replies asynchronously.
(c) Event Loop: Use io context.run() to start the event loop and process
asynchronous operations.

4.2.5 Advanced Techniques


1. Error Handling
The networking library provides error handling through std::error code, which is
passed to asynchronous handlers.

Example: Error Handling

#include <iostream>
#include <net>

namespace net = std::net;


153

void handle_connect(const std::error_code& ec, net::ip::tcp::socket&


,→ socket) {
if (ec) {
std::cerr << "Connection error: " << ec.message() <<
,→ std::endl;
return;
}

std::string message = "Hello, Server!";


net::async_write(socket, net::buffer(message),
[&socket](const std::error_code& ec, std::size_t /*length*/)
,→ {
if (ec) {
std::cerr << "Write error: " << ec.message() <<
,→ std::endl;
return;
}

char reply[1024];
net::async_read(socket, net::buffer(reply,
,→ sizeof(reply)),
[&socket, reply](const std::error_code& ec,
,→ std::size_t length) {
if (ec) {
std::cerr << "Read error: " << ec.message()
,→ << std::endl;
return;
}

std::cout << "Reply from server: " <<


,→ std::string(reply, length) << std::endl;
154

socket.close();
});
});
}

int main() {
net::io_context io_context;

net::ip::tcp::socket socket(io_context);
net::ip::tcp::endpoint
,→ endpoint(net::ip::make_address("127.0.0.1"), 8080);

socket.async_connect(endpoint, [&socket](const std::error_code&


,→ ec) {
handle_connect(ec, socket);
});

io_context.run();
}

• Output:

Reply from server: Hello, Client!

2. Custom Protocols

You can create custom protocols by defining your own protocol classes and using them
with the networking library.

Example: Custom Protocol


155

#include <iostream>
#include <net>

namespace net = std::net;

class MyProtocol {
public:
using endpoint = net::ip::tcp::endpoint;
using socket = net::ip::tcp::socket;
using acceptor = net::ip::tcp::acceptor;

static constexpr int protocol_family = AF_INET;


static constexpr int protocol_type = SOCK_STREAM;
static constexpr int protocol = IPPROTO_TCP;
};

int main() {
net::io_context io_context;

MyProtocol::acceptor acceptor(io_context,
,→ MyProtocol::endpoint(net::ip::tcp::v4(), 8080));
MyProtocol::socket socket(io_context);

acceptor.accept(socket);

char request[1024];
size_t request_length = net::read(socket, net::buffer(request,
,→ sizeof(request)));

std::cout << "Request from client: " << std::string(request,


,→ request_length) << std::endl;
156

std::string reply = "Hello, Client!";


net::write(socket, net::buffer(reply));

socket.close();
}

• Output:

Request from client: Hello, Server!

4.2.6 Conclusion
The C++20 networking library provides a powerful and portable way to handle networking tasks
in C++. By mastering this library, you can write efficient, maintainable, and scalable networked
applications. Whether you are creating a simple client-server application or a complex
distributed system, the networking library offers the tools you need to succeed.
This section has provided a comprehensive overview of the C++20 networking library, covering
its syntax, usage, and practical applications. With this knowledge, you are well-equipped to
tackle complex networking challenges and leverage the full power of modern C++. These
techniques are essential for developing high-quality software systems that communicate
effectively over networks.
Chapter 5

Practical Examples

5.1 Advanced Programs (e.g., Multithreaded Applications,


Template Metaprogramming)

5.1.1 Introduction to Advanced C++ Programming

Advanced C++ programming involves leveraging the full power of the language to create
efficient, maintainable, and scalable applications. This includes techniques such as
multithreading, template metaprogramming, and advanced use of the Standard Library. These
techniques are essential for advanced learners and professionals who want to write
high-performance and robust software systems.
This section will provide an in-depth exploration of advanced C++ programming techniques,
covering multithreaded applications and template metaprogramming. We will provide full
examples and practical applications to illustrate these concepts.

157
158

5.1.2 Multithreaded Applications

Introduction to Multithreading
Multithreading is a technique that allows a program to perform multiple tasks concurrently. This
is particularly useful for improving the performance of applications that need to handle multiple
tasks simultaneously, such as web servers, game engines, and scientific simulations.

Example: Basic Multithreading

#include <iostream>
#include <thread>
#include <vector>

void printHello(int id) {


std::cout << "Hello from thread " << id << std::endl;
}

int main() {
std::vector<std::thread> threads;

for (int i = 0; i < 5; ++i) {


threads.emplace_back(printHello, i);
}

for (auto& t : threads) {


t.join();
}

std::cout << "All threads completed" << std::endl;


}

• Output:
159

Hello from thread 0


Hello from thread 1
Hello from thread 2
Hello from thread 3
Hello from thread 4
All threads completed

Key Points

1. Creating Threads: Use std::thread to create and manage threads.

2. Joining Threads: Use join() to wait for threads to complete.

3. Thread Safety: Ensure thread safety by using synchronization primitives like


std::mutex and std::lock guard.

Example: Thread Safety with std::mutex

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

std::mutex mtx;

void printHello(int id) {


std::lock_guard<std::mutex> lock(mtx);
std::cout << "Hello from thread " << id << std::endl;
}
160

int main() {
std::vector<std::thread> threads;

for (int i = 0; i < 5; ++i) {


threads.emplace_back(printHello, i);
}

for (auto& t : threads) {


t.join();
}

std::cout << "All threads completed" << std::endl;


}

• Output:

Hello from thread 0


Hello from thread 1
Hello from thread 2
Hello from thread 3
Hello from thread 4
All threads completed

Example: Thread Pool


A thread pool is a collection of worker threads that are used to execute tasks concurrently. This
can improve performance by reusing threads and reducing the overhead of creating and
destroying threads.
161

#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>

class ThreadPool {
public:
ThreadPool(size_t numThreads) {
for (size_t i = 0; i < numThreads; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(queueMutex);
condition.wait(lock, [this] { return !tasks.empty()
,→ || stop; });
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}

˜ThreadPool() {
{
std::unique_lock<std::mutex> lock(queueMutex);
162

stop = true;
}
condition.notify_all();
for (auto& worker : workers) {
worker.join();
}
}

template <typename F>


void enqueue(F&& f) {
{
std::unique_lock<std::mutex> lock(queueMutex);
tasks.emplace(std::forward<F>(f));
}
condition.notify_one();
}

private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queueMutex;
std::condition_variable condition;
bool stop = false;
};

int main() {
ThreadPool pool(4);

for (int i = 0; i < 8; ++i) {


pool.enqueue([i] {
std::cout << "Task " << i << " executed by thread " <<
,→ std::this_thread::get_id() << std::endl;
163

});
}

std::this_thread::sleep_for(std::chrono::seconds(1));
}

• Output:

Task 0 executed by thread 140735680970496


Task 1 executed by thread 140735680970496
Task 2 executed by thread 140735680970496
Task 3 executed by thread 140735680970496
Task 4 executed by thread 140735680970496
Task 5 executed by thread 140735680970496
Task 6 executed by thread 140735680970496
Task 7 executed by thread 140735680970496

5.1.3 Template Metaprogramming

Introduction to Template Metaprogramming


Template metaprogramming is a technique that uses templates to perform computations at
compile time. This can be used to create highly optimized and flexible code.

Example: Compile-Time Factorial

#include <iostream>

template <int N>


struct Factorial {
164

static constexpr int value = N * Factorial<N - 1>::value;


};

template <>
struct Factorial<0> {
static constexpr int value = 1;
};

int main() {
std::cout << "Factorial of 5: " << Factorial<5>::value << std::endl;
}

• Output:

Factorial of 5: 120

Key Points

1. Template Specialization: Use template specialization to define base cases for recursive
templates.

2. Compile-Time Computation: Perform computations at compile time using templates.

Example: Type Traits


Type traits are a powerful feature of template metaprogramming that allow you to query and
manipulate types at compile time.
165

#include <iostream>
#include <type_traits>

template <typename T>


void printType() {
if (std::is_integral<T>::value) {
std::cout << "Integral type" << std::endl;
} else if (std::is_floating_point<T>::value) {
std::cout << "Floating-point type" << std::endl;
} else {
std::cout << "Other type" << std::endl;
}
}

int main() {
printType<int>();
printType<double>();
printType<std::string>();
}

• Output:

Integral type
Floating-point type
Other type

Example: Variadic Templates


Variadic templates allow you to define functions and classes that accept an arbitrary number of
template arguments.
166

#include <iostream>

template <typename... Args>


void print(Args... args) {
(std::cout << ... << args) << std::endl;
}

int main() {
print(1, 2, 3, "Hello", 4.5);
}

• Output:

123Hello4.5

5.1.4 Practical Applications


1. Multithreaded Web Server

A multithreaded web server can handle multiple client connections concurrently,


improving performance and responsiveness.

#include <iostream>
#include <thread>
#include <vector>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>

void handleClient(int clientSocket) {


167

char buffer[1024];
ssize_t bytesRead = read(clientSocket, buffer, sizeof(buffer));
if (bytesRead > 0) {
write(clientSocket, "HTTP/1.1 200 OK\r\nContent-Length:
,→ 13\r\n\r\nHello, World!", 46);
}
close(clientSocket);
}

int main() {
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(8080);

bind(serverSocket, (struct sockaddr*)&serverAddr,


,→ sizeof(serverAddr));
listen(serverSocket, 5);

std::vector<std::thread> threads;

while (true) {
int clientSocket = accept(serverSocket, nullptr, nullptr);
threads.emplace_back(handleClient, clientSocket);
}

for (auto& t : threads) {


t.join();
}

close(serverSocket);
168

• Output:

(Server runs and handles client connections)

2. Compile-Time Matrix Multiplication

Template metaprogramming can be used to perform matrix multiplication at compile time,


resulting in highly optimized code.

#include <iostream>

template <int Rows, int Cols, int K>


struct MatrixMultiply {
template <typename T>
static void multiply(const T (&a)[Rows][Cols], const T
,→ (&b)[Cols][K], T (&result)[Rows][K]) {
for (int i = 0; i < Rows; ++i) {
for (int j = 0; j < K; ++j) {
result[i][j] = 0;
for (int k = 0; k < Cols; ++k) {
result[i][j] += a[i][k] * b[k][j];
}
}
}
}
};

int main() {
169

const int a[2][3] = {{1, 2, 3}, {4, 5, 6}};


const int b[3][2] = {{7, 8}, {9, 10}, {11, 12}};
int result[2][2] = {0};

MatrixMultiply<2, 3, 2>::multiply(a, b, result);

for (int i = 0; i < 2; ++i) {


for (int j = 0; j < 2; ++j) {
std::cout << result[i][j] << " ";
}
std::cout << std::endl;
}
}

• Output:

58 64
139 154

5.1.5 Conclusion
Advanced C++ programming techniques such as multithreading and template metaprogramming
are essential for creating high-performance and robust software systems. By mastering these
techniques, you can write efficient, maintainable, and scalable applications that leverage the full
power of modern C++.
This section has provided a comprehensive overview of advanced C++ programming, covering
multithreaded applications and template metaprogramming. With this knowledge, you are
well-equipped to tackle complex programming challenges and develop high-quality software
170

systems. These techniques are essential for advanced learners and professionals who want to
excel in modern C++ development.
Chapter 6

Lock-free and Memory Management

6.1 Lock-Free Programming

Lock-free programming is a sophisticated and powerful paradigm in concurrent programming


that allows multiple threads to operate on shared data structures without the need for traditional
locking mechanisms like mutexes, semaphores, or condition variables. Instead, lock-free
algorithms rely on atomic operations, memory ordering, and careful design to ensure that
threads can make progress without blocking each other. This approach is particularly valuable in
high-performance systems where minimizing latency, maximizing throughput, and ensuring
scalability are critical.
In this section, we will explore lock-free programming in depth, covering its foundational
concepts, design principles, challenges, and practical implementations. By the end of this
section, advanced learners and professionals will have a comprehensive understanding of how to
design, implement, and reason about lock-free data structures and algorithms in modern C++.

171
172

6.1.1 Foundations of Lock-Free Programming


1. What is Lock-Free Programming?
Lock-free programming is a concurrency control technique where threads operate on
shared data without acquiring locks. Instead of blocking threads when conflicts occur,
lock-free algorithms ensure that at least one thread makes progress at any given time. This
is achieved through the use of atomic operations, which are indivisible and guaranteed to
complete without interference from other threads.
Lock-free programming is often contrasted with blocking algorithms, where threads may
be put to sleep (blocked) if they cannot acquire a lock. Blocking algorithms can lead to
issues like priority inversion, deadlocks, and reduced scalability, especially in highly
concurrent systems. Lock-free algorithms, on the other hand, avoid these issues by
allowing threads to make progress independently.

2. Why Use Lock-Free Programming?


Lock-free programming offers several advantages:

• Improved Scalability: Since threads do not block, lock-free algorithms can scale
better on multi-core systems.
• Reduced Latency: Without the overhead of acquiring and releasing locks, lock-free
algorithms can achieve lower latency.
• Avoidance of Deadlocks: Lock-free algorithms are immune to deadlocks because
they do not rely on locking mechanisms.
• Progress Guarantees: Lock-free algorithms ensure that at least one thread makes
progress, even if other threads are delayed or fail.

However, lock-free programming is not a silver bullet. It introduces significant complexity


and requires a deep understanding of concurrency, memory models, and hardware
173

behavior.

6.1.2 Key Concepts in Lock-Free Programming


1. Atomic Operations
Atomic operations are the building blocks of lock-free programming. An atomic operation
is indivisible, meaning it appears to occur instantaneously from the perspective of other
threads. In C++, the <atomic> header provides a suite of atomic types and operations,
such as:

• std::atomic<T>: A template class for atomic types.


• std::atomic flag: A lightweight atomic boolean flag.
• Atomic operations like load, store, exchange,
compare exchange strong, and fetch add.

Example:

std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed); // Atomically
,→ increment counter

2. Memory Ordering
Memory ordering defines the visibility of memory operations across threads. In a
multi-threaded environment, the order in which memory operations are observed can vary
due to compiler optimizations and hardware reordering. C++ provides several memory
ordering options to control this behavior:

• Relaxed (memory order relaxed): No synchronization or ordering


constraints.
174

• Acquire (memory order acquire): Ensures that subsequent operations are not
reordered before the atomic operation.
• Release (memory order release): Ensures that previous operations are not
reordered after the atomic operation.
• Acquire-Release (memory order acq rel): Combines both acquire and release
semantics.
• Sequentially Consistent (memory order seq cst): The strongest ordering,
providing a total order of all atomic operations.

Example:

std::atomic<bool> flag{false};
int data = 0;

// Thread 1
data = 42;
flag.store(true, std::memory_order_release);

// Thread 2
while (!flag.load(std::memory_order_acquire));
assert(data == 42); // Guaranteed to be true

3. Compare-and-Swap (CAS)
Compare-and-Swap (CAS) is a fundamental operation in lock-free programming. It
atomically compares the value at a memory location with an expected value and, if they
match, updates the memory location to a new value. CAS is typically implemented using
compare exchange strong or compare exchange weak in C++.
Example:
175

std::atomic<int> value{10};
int expected = 10;
while (!value.compare_exchange_weak(expected, 20)) {
// CAS failed, retry
}

4. Hazard Pointers

Hazard pointers are a memory reclamation technique used in lock-free data structures.
They allow threads to mark nodes that are being accessed, preventing them from being
deallocated prematurely. This is particularly important in lock-free algorithms where
nodes may be accessed concurrently by multiple threads.

5. Read-Modify-Write (RMW) Operations

Read-Modify-Write (RMW) operations are atomic operations that read a value from
memory, modify it, and write it back in a single, indivisible step. Examples include
fetch add, fetch sub, and exchange.

Example:

std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed); // Atomically
,→ increment counter

6.1.3 Designing Lock-Free Data Structures


Designing lock-free data structures is challenging due to the need to handle concurrent
modifications without locks. Below, we discuss some common lock-free data structures and
their design principles.
176

1. Lock-Free Stack
A lock-free stack is typically implemented as a singly-linked list with a head pointer. Push
and pop operations are performed using CAS to update the head pointer atomically.
Example:

template <typename T>


class LockFreeStack {
private:
struct Node {
std::shared_ptr<T> data;
Node* next;
Node(T const& data_) : data(std::make_shared<T>(data_)) {}
};

std::atomic<Node*> head;

public:
void push(T const& data) {
Node* const new_node = new Node(data);
new_node->next = head.load();
while (!head.compare_exchange_weak(new_node->next,
,→ new_node));
}

std::shared_ptr<T> pop() {
Node* old_head = head.load();
while (old_head && !head.compare_exchange_weak(old_head,
,→ old_head->next));
return old_head ? old_head->data : std::shared_ptr<T>();
}
};
177

2. Lock-Free Queue
A lock-free queue is often implemented as a linked list with head and tail pointers.
Enqueue and dequeue operations must carefully manage the head and tail pointers to
avoid race conditions.
Example:

template <typename T>


class LockFreeQueue {
private:
struct Node {
std::shared_ptr<T> data;
std::atomic<Node*> next;
Node(T const& data_) : data(std::make_shared<T>(data_)),
,→ next(nullptr) {}
};

std::atomic<Node*> head;
std::atomic<Node*> tail;

public:
LockFreeQueue() : head(new Node(T())), tail(head.load()) {}
˜LockFreeQueue() {
while (Node* const old_head = head.load()) {
head.store(old_head->next);
delete old_head;
}
}

void enqueue(T const& data) {


Node* new_node = new Node(data);
while (true) {
Node* old_tail = tail.load();
178

Node* next = old_tail->next.load();


if (old_tail == tail.load()) {
if (next == nullptr) {
if (old_tail->next.compare_exchange_weak(next,
,→ new_node)) {
tail.compare_exchange_weak(old_tail,
,→ new_node);
return;
}
} else {
tail.compare_exchange_weak(old_tail, next);
}
}
}
}

std::shared_ptr<T> dequeue() {
while (true) {
Node* old_head = head.load();
Node* old_tail = tail.load();
Node* next = old_head->next.load();
if (old_head == head.load()) {
if (old_head == old_tail) {
if (next == nullptr) {
return std::shared_ptr<T>();
}
tail.compare_exchange_weak(old_tail, next);
} else {
std::shared_ptr<T> res = next->data;
if (head.compare_exchange_weak(old_head, next)) {
delete old_head;
return res;
179

}
}
}
}
}
};

3. Lock-Free Hash Table

A lock-free hash table can be implemented using an array of lock-free linked lists. Each
bucket in the array is managed independently, allowing concurrent access to different
buckets.

6.1.4 Challenges and Pitfalls


1. ABA Problem

The ABA problem occurs when a value is changed from A to B and back to A, causing a
CAS operation to succeed incorrectly. This can be mitigated using techniques like
double-word CAS or versioning.

2. Memory Reclamation

Managing memory in lock-free data structures is complex because nodes cannot be


deallocated immediately if they might still be accessed by other threads. Techniques like
hazard pointers, epoch-based reclamation, or reference counting are used to address this
issue.

3. Progress Guarantees

Lock-free algorithms provide progress guarantees, ensuring that at least one thread makes
progress. However, they do not guarantee fairness or starvation-freedom, which can be a
180

concern in some applications.

6.1.5 Conclusion
Lock-free programming is a powerful technique for building high-performance concurrent
systems. By leveraging atomic operations, memory ordering, and careful design, developers can
create data structures that scale well on modern multi-core processors. However, the complexity
and subtlety of lock-free algorithms require a deep understanding of concurrency, memory
models, and hardware behavior. As such, lock-free programming is best suited for advanced
learners and professionals who are willing to invest the time to master these concepts.
181

6.2 Custom Memory Management


Custom memory management is a cornerstone of advanced C++ programming, especially in
high-performance and concurrent systems. While the C++ Standard Library provides
general-purpose memory management facilities like new and delete, these may not always
meet the specific needs of performance-critical applications. Custom memory management
allows developers to design and implement memory allocation and deallocation strategies
tailored to their application's requirements, optimizing performance, reducing fragmentation,
and ensuring safe memory reclamation in multi-threaded environments.
In this section, we will delve deeply into the principles, techniques, and practical considerations
of custom memory management, with a particular focus on its application in lock-free
programming. By the end of this section, advanced learners and professionals will have a
comprehensive understanding of how to design, implement, and integrate custom memory
management systems in modern C++.

6.2.1 Why Custom Memory Management?


1. Performance Optimization

The default memory allocator provided by the C++ Standard Library is designed for
general-purpose use and may not be optimal for specific workloads. Custom memory
allocators can be tailored to the allocation patterns of an application, reducing overhead
and improving performance. For example, a custom allocator can pre-allocate memory in
large blocks, minimizing the number of system calls and reducing allocation latency.

2. Reduced Fragmentation

Memory fragmentation occurs when free memory is divided into small, non-contiguous
blocks, making it difficult to allocate large contiguous chunks. Custom memory allocators
can employ strategies like memory pooling or slab allocation to minimize fragmentation,
182

ensuring efficient use of memory.

3. Concurrency and Lock-Free Programming

In concurrent environments, especially those using lock-free data structures, memory


management becomes more complex. The default allocator may introduce contention or
blocking, undermining the benefits of lock-free algorithms. Custom memory allocators
can be designed to work efficiently in multi-threaded contexts, often using techniques like
thread-local storage (TLS) or lock-free memory pools.

4. Deterministic Behavior

Real-time systems and embedded applications often require deterministic memory


allocation and deallocation behavior. Custom memory allocators can provide predictable
performance, avoiding the variability of general-purpose allocators.

5. Specialized Use Cases

Certain applications, such as games, simulations, and high-frequency trading systems,


have unique memory allocation patterns that can benefit from custom memory
management. For example, a game engine might use a custom allocator to manage
memory for game objects, ensuring fast allocation and deallocation during gameplay.

6.2.2 Techniques for Custom Memory Management


1. Memory Pools

A memory pool pre-allocates a large block of memory and divides it into fixed-size
chunks. Allocations and deallocations are performed by managing these chunks, avoiding
the overhead of general-purpose allocators.

Example:
183

class MemoryPool {
private:
struct Block {
Block* next;
};

Block* freeList;
std::vector<char> memory;

public:
MemoryPool(size_t blockSize, size_t blockCount)
: memory(blockSize * blockCount) {
freeList = reinterpret_cast<Block*>(memory.data());
Block* current = freeList;
for (size_t i = 1; i < blockCount; ++i) {
current->next = reinterpret_cast<Block*>(memory.data() +
,→ i * blockSize);
current = current->next;
}
current->next = nullptr;
}

void* allocate() {
if (!freeList) throw std::bad_alloc();
Block* block = freeList;
freeList = freeList->next;
return block;
}

void deallocate(void* ptr) {


Block* block = static_cast<Block*>(ptr);
block->next = freeList;
184

freeList = block;
}
};

2. Slab Allocation
Slab allocation is a technique where memory is divided into ”slabs,” each containing
objects of a fixed size. This approach is particularly efficient for allocating small objects
of the same type.
Example:

class SlabAllocator {
private:
std::vector<std::vector<char>> slabs;
size_t objectSize;
size_t slabSize;

public:
SlabAllocator(size_t objectSize, size_t slabSize)
: objectSize(objectSize), slabSize(slabSize) {}

void* allocate() {
for (auto& slab : slabs) {
if (slab.size() + objectSize <= slab.capacity()) {
void* ptr = slab.data() + slab.size();
slab.resize(slab.size() + objectSize);
return ptr;
}
}
slabs.emplace_back(slabSize);
return allocate();
185

void deallocate(void* ptr) {


// Slab allocators typically do not support individual
,→ deallocation
// Memory is reclaimed when the entire slab is no longer
,→ needed
}
};

3. Arenas
An arena allocator manages a large block of memory and allocates from it sequentially.
This is useful for scenarios where objects have similar lifetimes and can be deallocated in
bulk.
Example:

class Arena {
private:
std::vector<char> memory;
size_t offset;

public:
Arena(size_t size) : memory(size), offset(0) {}

void* allocate(size_t size) {


if (offset + size > memory.size()) throw std::bad_alloc();
void* ptr = memory.data() + offset;
offset += size;
return ptr;
}
186

void reset() {
offset = 0;
}
};

4. Thread-Local Storage (TLS)


Thread-local storage allows each thread to have its own instance of a memory allocator,
reducing contention and improving performance in multi-threaded applications.
Example:

thread_local MemoryPool threadLocalPool(64, 1024);

void* allocateThreadLocal(size_t size) {


return threadLocalPool.allocate();
}

5. Lock-Free Memory Allocators


Lock-free memory allocators are designed to work efficiently in concurrent environments
without using locks. These allocators often rely on atomic operations and careful memory
management to avoid contention.
Example:

class LockFreeMemoryPool {
private:
struct Block {
std::atomic<Block*> next;
};
187

std::atomic<Block*> freeList;

public:
LockFreeMemoryPool(size_t blockSize, size_t blockCount) {
std::vector<char> memory(blockSize * blockCount);
Block* head = reinterpret_cast<Block*>(memory.data());
freeList.store(head);

Block* current = head;


for (size_t i = 1; i < blockCount; ++i) {
current->next.store(reinterpret_cast<Block*>(memory.data()
,→ + i * blockSize));
current = current->next.load();
}
current->next.store(nullptr);
}

void* allocate() {
Block* block = freeList.load();
while (block && !freeList.compare_exchange_weak(block,
,→ block->next.load()));
return block;
}

void deallocate(void* ptr) {


Block* block = static_cast<Block*>(ptr);
block->next.store(freeList.load());
while (!freeList.compare_exchange_weak(block->next.load(),
,→ block));
}
};
188

6.2.3 Memory Reclamation in Lock-Free Programming


Memory reclamation is a significant challenge in lock-free programming. Since threads may
access shared data concurrently, memory cannot be deallocated immediately when it is no longer
needed. Several techniques are used to address this issue:

1. Hazard Pointers

Hazard pointers are a memory reclamation technique where threads mark nodes they are
accessing, preventing them from being deallocated prematurely.

Example:

std::atomic<void*> hazardPointers[MAX_THREADS];

void* readWithHazardPointer(std::atomic<void*>& ptr) {


void* value;
do {
value = ptr.load();
hazardPointers[threadId] = value;
} while (value != ptr.load());
return value;
}

void reclaimMemory(void* ptr) {


for (size_t i = 0; i < MAX_THREADS; ++i) {
if (hazardPointers[i] == ptr) return; // Still in use
}
delete ptr;
}

2. Epoch-Based Reclamation
189

Epoch-based reclamation divides time into epochs and defers memory reclamation until
no threads are in an older epoch. This ensures that memory is only deallocated when it is
safe to do so.

Example:

class EpochManager {
private:
std::atomic<size_t> currentEpoch{0};
std::vector<std::atomic<size_t>> threadEpochs;
std::vector<std::vector<void*>> retiredLists;

public:
EpochManager(size_t numThreads) : threadEpochs(numThreads),
,→ retiredLists(numThreads) {}

void enterEpoch(size_t threadId) {


threadEpochs[threadId].store(currentEpoch.load());
}

void retire(void* ptr, size_t threadId) {


retiredLists[threadId].push_back(ptr);
}

void reclaim() {
size_t oldestEpoch = currentEpoch.load();
for (auto& epoch : threadEpochs) {
oldestEpoch = std::min(oldestEpoch, epoch.load());
}
for (auto& list : retiredLists) {
for (auto it = list.begin(); it != list.end();) {
if (threadEpochs[threadId].load() < oldestEpoch) {
delete *it;
190

it = list.erase(it);
} else {
++it;
}
}
}
currentEpoch.fetch_add(1);
}
};

3. Reference Counting
Reference counting tracks the number of references to a memory block and deallocates it
when the count reaches zero. This technique can be combined with atomic operations to
work in concurrent environments.
Example:

template <typename T>


class RefCounted {
private:
std::atomic<size_t> refCount{0};

public:
void addRef() {
refCount.fetch_add(1);
}

void release() {
if (refCount.fetch_sub(1) == 1) {
delete static_cast<T*>(this);
}
191

}
};

6.2.4 Practical Considerations


1. Alignment and Padding

Custom memory allocators must ensure proper alignment of allocated memory to avoid
performance penalties or crashes. Padding may be required to align memory blocks
correctly.

2. Debugging and Profiling

Custom memory allocators can make debugging and profiling more challenging. Tools
like Valgrind or AddressSanitizer can help identify memory-related issues.

3. Integration with Standard Library

Custom memory allocators can be integrated with the C++ Standard Library by providing
custom allocators for containers like std::vector or std::map.

Example:

template <typename T>


class CustomAllocator {
public:
using value_type = T;

CustomAllocator() = default;

template <typename U>


CustomAllocator(const CustomAllocator<U>&) {}
192

T* allocate(size_t n) {
return static_cast<T*>(::operator new(n * sizeof(T)));
}

void deallocate(T* ptr, size_t n) {


::operator delete(ptr);
}
};

std::vector<int, CustomAllocator<int>> vec;

6.2.5 Conclusion
Custom memory management is a powerful tool for advanced C++ programmers, enabling them
to optimize performance, reduce fragmentation, and handle memory safely in concurrent
environments. By understanding and applying techniques like memory pools, slab allocation,
hazard pointers, and lock-free allocators, developers can build high-performance systems that
meet the demands of modern applications.
Appendices

Appendix A: Advanced C++ Features and Techniques


This appendix explores advanced features of modern C++ that are essential for writing
high-performance, maintainable, and expressive code. It covers topics such as:

1. Move Semantics and Perfect Forwarding:

• Explanation of rvalue references (&&).


• The std::move and std::forward utilities.
• Implementing move constructors and move assignment operators.
• Avoiding common pitfalls with move semantics.

Example:

class MyClass {
public:
MyClass(MyClass&& other) noexcept {
// Move resources from 'other'
}
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {

193
194

// Move resources from 'other'


}
return *this;
}
};

2. Variadic Templates:

• Introduction to variadic templates and parameter packs.

• Writing generic functions and classes using variadic templates.

• Practical examples like std::tuple and std::variant.

Example:

template <typename... Args>


void print(Args... args) {
(std::cout << ... << args) << '\n';
}

3. Type Traits and Metaprogramming:

• Using std::enable if, std::is same, and other type traits.

• Compile-time conditional logic with if constexpr.

• Writing custom type traits.

Example:
195

template <typename T>


void process(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integral type: " << value << '\n';
} else {
std::cout << "Non-integral type\n";
}
}

4. Lambda Expressions and Closures:

• Capturing variables by value and reference.


• Generic lambdas and mutable lambdas.
• Using lambdas with STL algorithms.

Example:

auto adder = [](int x) {


return [x](int y) { return x + y; };
};
auto add5 = adder(5);
std::cout << add5(10); // Output: 15
196

Appendix B: Concurrency and Parallelism


This appendix provides a deeper dive into concurrency and parallelism in modern C++. It covers:

1. Thread Management:

• Creating and managing threads using std::thread.

• Thread synchronization with std::mutex, std::lock guard, and


std::unique lock.

• Thread-local storage (thread local).

Example:

std::mutex mtx;
void threadFunc() {
std::lock_guard<std::mutex> lock(mtx);
std::cout << "Thread ID: " << std::this_thread::get_id() << '\n';
}

2. Atomic Operations and Memory Ordering:

• Using std::atomic for lock-free programming.

• Memory ordering: relaxed, acquire, release, and sequentially consistent.

• Practical examples of atomic counters and flags.

Example:
197

std::atomic<int> counter{0};
void increment() {
counter.fetch_add(1, std::memory_order_relaxed);
}

3. Parallel Algorithms:

• Using std::execution::par and std::execution::par unseq with


STL algorithms.
• Parallelizing loops and data processing tasks.

Example:

std::vector<int> data = {1, 2, 3, 4, 5};


std::for_each(std::execution::par, data.begin(), data.end(), [](int&
,→ x) {
x *= 2;
});

4. Futures and Promises:

• Asynchronous programming with std::future and std::promise.


• Using std::async for task-based parallelism.

Example:

std::future<int> fut = std::async([]() { return 42; });


std::cout << fut.get() << '\n'; // Output: 42
198

Appendix C: Performance Optimization Techniques


This appendix focuses on techniques for optimizing C++ code for performance. Topics include:

1. Profiling and Benchmarking:

• Using tools like gprof, perf, and Google Benchmark.


• Identifying performance bottlenecks.

2. Cache-Friendly Programming:

• Understanding CPU caches and memory hierarchy.


• Optimizing data structures for cache locality.

3. Inline Assembly and Intrinsics:

• Writing performance-critical code using inline assembly.


• Using compiler intrinsics for SIMD (Single Instruction, Multiple Data) operations.

4. Compiler Optimizations:

• Leveraging compiler flags like -O2, -O3, and -march=native.


• Understanding the impact of inlining and loop unrolling.
199

Appendix D: Advanced STL and Custom Containers


This appendix explores advanced usage of the Standard Template Library (STL) and techniques
for creating custom containers. Topics include:

1. Custom Allocators:

• Implementing custom allocators for STL containers.

• Integrating custom allocators with std::vector, std::map, and other


containers.

Example:

template <typename T>


class CustomAllocator {
public:
using value_type = T;
T* allocate(size_t n) {
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* ptr, size_t n) {
::operator delete(ptr);
}
};

std::vector<int, CustomAllocator<int>> vec;

2. Custom Iterators:

• Writing custom iterators for user-defined containers.


200

• Implementing iterator traits and operations.

3. Advanced STL Algorithms:

• Using std::transform, std::accumulate, and other algorithms with


custom predicates.
• Combining algorithms with lambda expressions.
201

Appendix E: Cross-Platform Development


This appendix covers techniques for writing cross-platform C++ code. Topics include:

1. Platform-Specific Code:

• Using preprocessor directives (#ifdef, #ifndef) for platform-specific code.


• Handling differences in file systems, threading, and networking.

2. Build Systems:

• Using CMake for cross-platform builds.


• Integrating third-party libraries and dependencies.

3. Testing and Debugging:

• Writing unit tests with Google Test or Catch2.


• Debugging cross-platform issues with tools like gdb and Valgrind.
202

Appendix F: Case Studies and Real-World Examples


This appendix provides real-world case studies and examples of advanced C++ techniques in
action. Topics include:

1. High-Frequency Trading Systems:

• Low-latency design and lock-free data structures.


• Custom memory management for performance-critical applications.

2. Game Development:

• Optimizing game loops and rendering pipelines.


• Managing memory for large-scale game worlds.

3. Embedded Systems:

• Writing efficient C++ code for resource-constrained environments.


• Interfacing with hardware using C++.
203

Appendix G: Further Reading and Resources


This appendix provides a curated list of resources for further learning, including:

1. Books:

• ”Effective Modern C++” by Scott Meyers.


• ”C++ Concurrency in Action” by Anthony Williams.

2. Online Resources:

• CppReference (https://en.cppreference.com/).
• Stack Overflow and C++ forums.

3. Tools and Libraries:

• Boost C++ Libraries (https://www.boost.org/).


• Clang and LLVM tools for static analysis and optimization.
References

Books
Books are an invaluable resource for mastering advanced C++ concepts. The following books
are highly recommended for readers who want to explore topics in greater depth:

1. ”Effective Modern C++” by Scott Meyers

• A comprehensive guide to modern C++ features, including move semantics, lambda


expressions, and concurrency.
• Focuses on best practices and pitfalls to avoid when using C++11, C++14, and
C++17.

2. ”C++ Concurrency in Action” by Anthony Williams

• An in-depth exploration of concurrent and parallel programming in C++.


• Covers threads, mutexes, condition variables, atomic operations, and lock-free
programming.

3. ”The C++ Standard Library: A Tutorial and Reference” by Nicolai M. Josuttis

• A detailed reference for the C++ Standard Library, including containers, algorithms,
and utilities.

204
205

• Provides practical examples and explanations of advanced STL features.

4. ”Programming: Principles and Practice Using C++” by Bjarne Stroustrup

• A foundational book by the creator of C++, focusing on programming principles and


modern C++ techniques.
• Suitable for both beginners and advanced learners.

5. ”Modern C++ Design” by Andrei Alexandrescu

• Explores advanced design patterns and generic programming techniques using


templates.
• Introduces concepts like policy-based design and type lists.

6. ”Optimized C++” by Kurt Guntheroth

• Focuses on performance optimization techniques for C++ programs.


• Covers profiling, memory management, and cache-friendly programming.

Research Papers and Academic Publications


Research papers provide cutting-edge insights into advanced programming techniques and
algorithms. The following papers are particularly relevant to the topics covered in this book:

1. ”Lock-Free Linked Lists and Skip Lists” by M. Michael

• A seminal paper on lock-free data structures, including linked lists and skip lists.
• Discusses memory reclamation techniques like hazard pointers.

2. ”Epoch-Based Reclamation” by Keir Fraser and Tim Harris


206

• Introduces epoch-based memory reclamation, a technique for safe memory


management in lock-free data structures.

3. ”A Practical Multi-Word Compare-and-Swap Operation” by Timothy L. Harris,


Keir Fraser, and Ian Pratt

• Explores multi-word CAS operations and their applications in concurrent


programming.

4. ”The Art of Multiprocessor Programming” by Maurice Herlihy and Nir Shavit

• A comprehensive textbook on concurrent programming, covering both theoretical


and practical aspects.

Online Resources
Online resources provide up-to-date information, tutorials, and community support for C++
programmers. The following websites and forums are highly recommended:

1. CppReference (https://en.cppreference.com/)

• A comprehensive online reference for the C++ Standard Library and language
features.

• Includes detailed documentation, examples, and explanations.

2. Stack Overflow (https://stackoverflow.com/)

• A popular Q&A platform for programmers.

• Search for answers to specific C++ questions or ask your own.


207

3. C++ Weekly (YouTube Channel by Jason Turner)

• A series of short, informative videos on modern C++ features and techniques.

• Great for quick learning and staying updated with the latest trends.

4. ISO C++ Standards Committee (https://isocpp.org/)

• The official website for the C++ Standards Committee.

• Provides updates on the latest C++ standards (C++20, C++23, etc.).

5. Godbolt Compiler Explorer (https://godbolt.org/)

• An online tool for exploring compiler output and understanding how C++ code is
translated into assembly.

• Useful for performance analysis and optimization.

Tools and Libraries


Tools and libraries are essential for writing, debugging, and optimizing C++ code. The following
tools are widely used in the C++ community:

1. CMake (https://cmake.org/)

• A cross-platform build system for managing C++ projects.

• Simplifies the process of compiling and linking code across different platforms.

2. Clang and LLVM (https://llvm.org/)

• A collection of modular and reusable compiler and toolchain technologies.


208

• Includes tools like clang-format for code formatting and clang-tidy for
static analysis.

3. Valgrind (https://valgrind.org/)

• A tool for detecting memory leaks, memory corruption, and threading issues.
• Essential for debugging and profiling C++ programs.

4. Google Benchmark (https://github.com/google/benchmark)

• A library for benchmarking C++ code.


• Helps measure the performance of functions and algorithms.

5. Boost C++ Libraries (https://www.boost.org/)

• A collection of peer-reviewed, portable C++ libraries.


• Includes libraries for concurrency, networking, and data structures.

6. AddressSanitizer and ThreadSanitizer

• Tools for detecting memory errors (AddressSanitizer) and data races


(ThreadSanitizer).
• Integrated into modern compilers like GCC and Clang.

Community and Conferences


Engaging with the C++ community is a great way to stay updated and learn from others. The
following resources are highly recommended:

1. CppCon (https://cppcon.org/)
209

• The largest annual conference for C++ developers.

• Features talks, workshops, and networking opportunities.

2. Meeting C++ (https://meetingcpp.com/)

• A platform for C++ conferences, blogs, and community events.

• Provides resources for learning and staying connected with the C++ community.

3. Reddit C++ Community (https://www.reddit.com/r/cpp/)

• A subreddit for discussing C++ programming, news, and resources.

• Great for asking questions and sharing knowledge.

4. C++ Slack and Discord Channels

• Online communities for real-time discussions and collaboration.

• Join channels like #cpp on Slack or C++ Enthusiasts on Discord.

C++ Standards and Documentation


Understanding the C++ standards is essential for writing modern and portable code. The
following resources provide access to the official standards and documentation:

1. ISO/IEC 14882:2020 (C++20 Standard)

• The official standard for C++20, including new features like concepts, ranges, and
coroutines.

• Available for purchase from the ISO website.


210

2. Working Draft, Standard for Programming Language C++


(https://eel.is/c++draft/)

• The latest draft of the C++ standard, updated regularly.


• Provides insights into upcoming features and changes.

3. C++ Core Guidelines (https:


//isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)

• A set of guidelines for writing modern, safe, and efficient C++ code.
• Maintained by Bjarne Stroustrup and Herb Sutter.

Additional Learning Resources


For readers who want to explore specific topics in greater depth, the following resources are
recommended:

1. Pluralsight (https://www.pluralsight.com/)

• Online courses on modern C++ features, concurrency, and performance optimization.


• Taught by industry experts.

2. Udemy (https://www.udemy.com/)

• Affordable courses on advanced C++ topics, including game development and


embedded systems.

3. Leanpub (https://leanpub.com/)

• A platform for self-published books on programming, including C++.


211

• Offers books on niche topics like template metaprogramming and lock-free


programming.

You might also like