READMEC++

README

Advanced Topics / Modern Cpp

Concept Lesson
Intermediate
4 min

Learning Objective

Understand Advanced Topics well enough to explain it, recognize it in C++, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

TopicsTable Of ContentsAuto And Type InferenceDecltypeSmart Pointers
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

README
2 min read18 headings

Modern C++ Features (C++11/14/17/20/23)

Table of Contents

  1. Auto and Type Inference
  2. Smart Pointers
  3. Move Semantics
  4. Lambda Expressions
  5. Structured Bindings
  6. Optional, Variant, Any
  7. String View
  8. Constexpr and Compile-Time
  9. Ranges (C++20)
  10. Concepts (C++20)
  11. Coroutines (C++20)
  12. Modules (C++20)
  13. std::format (C++20)
  14. std::span (C++20)
  15. std::expected (C++23)
  16. Three-Way Comparison (C++20)
  17. Additional C++23 Features

Auto and Type Inference

auto

auto x = 42;              // int
auto y = 3.14;            // double
auto s = "hello"s;        // string (with s suffix)
auto v = vector<int>{1,2,3};

// With functions
auto add(int a, int b) -> int {
    return a + b;
}

// Return type deduction (C++14)
auto multiply(int a, int b) {
    return a * b;
}

decltype

int x = 10;
decltype(x) y = 20;       // int

// Get return type
decltype(add(1, 2)) result;

// Common with templates
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
    return a + b;
}

Smart Pointers

unique_ptr

#include <memory>

// Exclusive ownership
unique_ptr<int> p1 = make_unique<int>(42);
cout << *p1 << endl;

// Transfer ownership
unique_ptr<int> p2 = move(p1);
// p1 is now nullptr

// Custom deleter
unique_ptr<FILE, decltype(&fclose)> file(fopen("f.txt", "r"), fclose);

// Array
unique_ptr<int[]> arr = make_unique<int[]>(10);

shared_ptr

// Shared ownership
shared_ptr<int> p1 = make_shared<int>(42);
shared_ptr<int> p2 = p1;  // Both own

cout << p1.use_count() << endl;  // 2

// Automatically deleted when last owner destroyed

weak_ptr

shared_ptr<int> shared = make_shared<int>(42);
weak_ptr<int> weak = shared;

// Check and access
if (auto locked = weak.lock()) {
    cout << *locked << endl;
}

// Check if still valid
if (!weak.expired()) {
    // Still valid
}

Move Semantics

Rvalue References

string s = "Hello";
string&& rref = move(s);  // s is now moved-from

// Move constructor
class MyClass {
    string data;
public:
    MyClass(MyClass&& other) noexcept
        : data(move(other.data)) {}

    MyClass& operator=(MyClass&& other) noexcept {
        data = move(other.data);
        return *this;
    }
};

std::move and std::forward

// move - cast to rvalue
vector<string> v;
string s = "Hello";
v.push_back(move(s));  // Moves instead of copies

// forward - perfect forwarding
template<typename T>
void wrapper(T&& arg) {
    process(forward<T>(arg));
}

Lambda Expressions

Basic Syntax

auto add = [](int a, int b) { return a + b; };
cout << add(1, 2) << endl;  // 3

// With captures
int x = 10;
auto addX = [x](int a) { return a + x; };
auto addXRef = [&x](int a) { return a + x; };

// Capture all
auto all = [=]() { };  // By value
auto allRef = [&]() { };  // By reference

Advanced Lambdas

// Mutable lambda
int count = 0;
auto counter = [count]() mutable { return ++count; };

// Generic lambda (C++14)
auto print = [](auto x) { cout << x << endl; };

// Init capture (C++14)
auto p = [ptr = make_unique<int>(42)]() { return *ptr; };

// Constexpr lambda (C++17)
auto square = [](int n) constexpr { return n * n; };

Structured Bindings

// C++17
// Pairs and tuples
pair<int, string> p = {1, "hello"};
auto [id, name] = p;

// Arrays
int arr[] = {1, 2, 3};
auto [a, b, c] = arr;

// Structs
struct Point { int x, y; };
Point pt = {10, 20};
auto [x, y] = pt;

// Maps
map<string, int> m = {{"a", 1}, {"b", 2}};
for (const auto& [key, value] : m) {
    cout << key << ": " << value << endl;
}

Optional, Variant, Any

optional

#include <optional>

optional<int> findValue(bool found) {
    if (found) return 42;
    return nullopt;
}

auto result = findValue(true);
if (result) {
    cout << *result << endl;
}

// value_or
cout << result.value_or(-1) << endl;

variant

#include <variant>

variant<int, string, double> v;
v = 42;
v = "hello";
v = 3.14;

// Access
cout << get<double>(v) << endl;

// Visit
visit([](auto&& arg) { cout << arg << endl; }, v);

any

#include <any>

any a = 42;
a = string("hello");
a = 3.14;

if (a.type() == typeid(double)) {
    cout << any_cast<double>(a) << endl;
}

String View

#include <string_view>

// Non-owning view of string
string_view sv = "Hello, World!";

// Efficient substring
string_view sub = sv.substr(0, 5);  // "Hello"

// From string
string s = "Test";
string_view sv2 = s;

// Function parameter
void print(string_view sv) {
    cout << sv << endl;
}

print("literal");     // No allocation
print(s);             // No copy

Constexpr and Compile-Time

constexpr

// Compile-time constant
constexpr int square(int n) {
    return n * n;
}

constexpr int val = square(5);  // Computed at compile time

// constexpr if (C++17)
template<typename T>
auto process(T value) {
    if constexpr (is_integral_v<T>) {
        return value * 2;
    } else {
        return value;
    }
}

consteval (C++20)

consteval int mustBeCompileTime(int n) {
    return n * n;
}

constexpr int x = mustBeCompileTime(5);  // OK
// int y = mustBeCompileTime(runtime);   // ERROR

Ranges (C++20)

#include <ranges>

vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// Pipe syntax
auto result = v
    | views::filter([](int n) { return n % 2 == 0; })
    | views::transform([](int n) { return n * n; });

for (int x : result) {
    cout << x << " ";  // 4 16 36 64 100
}

// views
views::iota(1, 10)           // 1-9
views::take(v, 3)            // First 3
views::drop(v, 2)            // Skip 2
views::reverse(v)            // Reversed
views::keys(map)             // Map keys
views::values(map)           // Map values

Ranges Visual Flow

┌─────────────────────────────────────────────────────────────────────────┐
│                    RANGES PIPELINE VISUALIZATION                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   Input: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}                                │
│            │                                                             │
│            ▼                                                             │
│   ┌────────────────────┐                                                │
│   │ views::filter      │  Keep only even numbers                        │
│   │ (n % 2 == 0)       │                                                │
│   └────────────────────┘                                                │
│            │                                                             │
│            ▼                                                             │
│   {2, 4, 6, 8, 10}                                                      │
│            │                                                             │
│            ▼                                                             │
│   ┌────────────────────┐                                                │
│   │ views::transform   │  Square each number                            │
│   │ (n * n)            │                                                │
│   └────────────────────┘                                                │
│            │                                                             │
│            ▼                                                             │
│   Output: {4, 16, 36, 64, 100}                                          │
│                                                                          │
├─────────────────────────────────────────────────────────────────────────┤
│   KEY: Views are lazy - no intermediate containers created!             │
└─────────────────────────────────────────────────────────────────────────┘

Common Range Adaptors

// take - first N elements
views::take(v, 3)                    // {1, 2, 3}

// drop - skip first N elements
views::drop(v, 3)                    // {4, 5, 6, 7, 8, 9, 10}

// take_while / drop_while
views::take_while(v, [](int n) { return n < 5; })  // {1, 2, 3, 4}

// filter - keep elements matching predicate
views::filter(v, [](int n) { return n > 5; })      // {6, 7, 8, 9, 10}

// transform - apply function to each element
views::transform(v, [](int n) { return n * 2; })   // {2, 4, 6, ...}

// reverse - reverse order
views::reverse(v)                    // {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}

// split - split by delimiter
views::split(str, ' ')               // Split string by spaces

// join - flatten nested ranges
views::join(nested_vec)              // Flatten 2D to 1D

// zip (C++23) - combine multiple ranges
views::zip(v1, v2)                   // Pairs of elements

// enumerate (C++23) - add index
views::enumerate(v)                  // (0, elem0), (1, elem1), ...

Range Algorithms

#include <ranges>
#include <algorithm>

vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};

// Range-based algorithms (take range directly)
ranges::sort(v);
ranges::find(v, 5);
ranges::count(v, 1);
ranges::copy(v, output.begin());

// With projections
struct Person { string name; int age; };
vector<Person> people;

// Sort by age using projection
ranges::sort(people, {}, &Person::age);

// Find by name
auto it = ranges::find(people, "Alice", &Person::name);

Concepts (C++20)

Concepts provide a way to constrain template parameters with readable, named requirements.

┌─────────────────────────────────────────────────────────────────────────┐
│                          CONCEPTS OVERVIEW                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   ┌──────────────┐      ┌──────────────┐      ┌──────────────┐         │
│   │   Template   │ ───► │   Concept    │ ───► │   Better     │         │
│   │   Error Msg  │      │   Constraint │      │   Diagnostics│         │
│   │   (Cryptic)  │      │   (Named)    │      │   (Clear)    │         │
│   └──────────────┘      └──────────────┘      └──────────────┘         │
│                                                                          │
│   Before C++20:                                                          │
│   error: no match for 'operator<<' ...                                  │
│   ... 50 lines of template instantiation trace ...                      │
│                                                                          │
│   With Concepts:                                                         │
│   error: constraint 'Printable<T>' not satisfied                        │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Defining Concepts

#include <concepts>

// Basic concept definition
template<typename T>
concept Numeric = std::is_arithmetic_v<T>;

// Concept with requirements
template<typename T>
concept Printable = requires(T t) {
    { std::cout << t } -> std::same_as<std::ostream&>;
};

// Concept with multiple requirements
template<typename T>
concept Container = requires(T c) {
    { c.begin() } -> std::input_or_output_iterator;
    { c.end() } -> std::input_or_output_iterator;
    { c.size() } -> std::convertible_to<std::size_t>;
    typename T::value_type;
};

// Compound requirements
template<typename T>
concept Hashable = requires(T a) {
    { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};

Using Concepts

// Method 1: requires clause
template<typename T>
requires Numeric<T>
T add(T a, T b) { return a + b; }

// Method 2: Concept as type constraint
template<Numeric T>
T multiply(T a, T b) { return a * b; }

// Method 3: Trailing requires
template<typename T>
T divide(T a, T b) requires Numeric<T> { return a / b; }

// Method 4: Abbreviated function template
void print(Printable auto value) {
    std::cout << value << std::endl;
}

Standard Library Concepts

#include <concepts>

// Type concepts
std::same_as<T, U>           // T is same as U
std::derived_from<D, B>      // D derives from B
std::convertible_to<From, To>// From can convert to To

// Comparison concepts
std::equality_comparable<T>  // == and !=
std::totally_ordered<T>      // <, <=, >, >=, ==

// Object concepts
std::movable<T>              // Move constructible/assignable
std::copyable<T>             // Copy + movable
std::regular<T>              // Copyable + default init + equality

// Arithmetic concepts
std::integral<T>             // int, long, char, etc.
std::floating_point<T>       // float, double
std::signed_integral<T>      // Signed integers
std::unsigned_integral<T>    // Unsigned integers

// Iterator concepts
std::input_iterator<T>
std::forward_iterator<T>
std::bidirectional_iterator<T>
std::random_access_iterator<T>
std::contiguous_iterator<T>

// Range concepts
std::ranges::range<T>
std::ranges::sized_range<T>
std::ranges::input_range<T>

Concept Example: Type-Safe Container

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

template<Arithmetic T>
class NumericVector {
    std::vector<T> data_;
public:
    T sum() const {
        return std::accumulate(data_.begin(), data_.end(), T{});
    }

    T average() const requires std::floating_point<T> {
        return sum() / data_.size();
    }

    void push(T value) { data_.push_back(value); }
};

// NumericVector<int> vi;      // OK
// NumericVector<double> vd;   // OK
// NumericVector<string> vs;   // ERROR: string is not Arithmetic

Coroutines (C++20)

Coroutines are functions that can suspend execution and resume later.

┌─────────────────────────────────────────────────────────────────────────┐
│                      COROUTINE EXECUTION FLOW                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   Caller                        Coroutine                               │
│     │                             │                                      │
│     │  create()                   │                                      │
│     │────────────────────────────►│                                      │
│     │                             │ Execute until suspend               │
│     │◄────────────────────────────│                                      │
│     │  (suspended)                │                                      │
│     │                             │                                      │
│     │  resume()                   │                                      │
│     │────────────────────────────►│                                      │
│     │                             │ Continue execution                  │
│     │                             │ until next suspend                  │
│     │◄────────────────────────────│                                      │
│     │  (value or suspended)       │                                      │
│     │                             │                                      │
│     │  resume()                   │                                      │
│     │────────────────────────────►│                                      │
│     │                             │ Final execution                     │
│     │◄────────────────────────────│                                      │
│     │  (done)                     │                                      │
│     ▼                             ▼                                      │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Generator Example

#include <coroutine>
#include <iostream>

// Simple generator that yields values
template<typename T>
struct Generator {
    struct promise_type {
        T current_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 {}; }

        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }

        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle<promise_type> handle;

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

    // Iterator interface
    struct iterator {
        std::coroutine_handle<promise_type> handle;

        iterator& operator++() {
            handle.resume();
            return *this;
        }

        T operator*() const { return handle.promise().current_value; }
        bool operator!=(std::default_sentinel_t) const { return !handle.done(); }
    };

    iterator begin() {
        handle.resume();
        return {handle};
    }

    std::default_sentinel_t end() { return {}; }
};

// Usage
Generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i;  // Suspend and yield value
    }
}

int main() {
    for (int x : range(0, 5)) {
        std::cout << x << " ";  // 0 1 2 3 4
    }
}

Coroutine Keywords

co_yield expr;    // Suspend and yield a value
co_return expr;   // Complete with a value
co_await expr;    // Suspend until operation completes

Modules (C++20)

Modules replace the traditional header/source separation with a more efficient compilation model.

┌─────────────────────────────────────────────────────────────────────────┐
│                  HEADERS vs MODULES COMPARISON                           │
├────────────────────────────────┬────────────────────────────────────────┤
│         HEADERS                │            MODULES                      │
├────────────────────────────────┼────────────────────────────────────────┤
│                                │                                         │
│  // header.h                   │  // math.ixx (module interface)        │
│  #pragma once                  │  export module math;                    │
│  #include <vector>             │                                         │
│  class MyClass { ... };        │  export int add(int a, int b) {        │
│                                │      return a + b;                      │
│  // Parsed every #include      │  }                                      │
│  // Macros leak across files   │                                         │
│  // Slow compilation           │  // Compiled once, imported quickly    │
│                                │  // No macro leakage                    │
│  // source.cpp                 │  // Much faster compilation             │
│  #include "header.h"           │                                         │
│  // ... uses MyClass           │  // main.cpp                            │
│                                │  import math;                           │
│                                │  int x = add(1, 2);                     │
│                                │                                         │
└────────────────────────────────┴────────────────────────────────────────┘

Module Interface Unit

// math.ixx (or math.cppm)
export module math;

// Import standard library (C++23 style)
import <iostream>;

// Private (not exported)
int internal_helper(int x) {
    return x * 2;
}

// Public (exported)
export int add(int a, int b) {
    return a + b;
}

export int multiply(int a, int b) {
    return a * b;
}

export class Calculator {
public:
    int compute(int a, int b) {
        return internal_helper(a) + b;
    }
};

Module Implementation Unit

// math_impl.cpp
module math;  // No 'export' - this is implementation

// Can access non-exported symbols
int optimized_compute(int a, int b) {
    return internal_helper(a + b);
}

Using Modules

// main.cpp
import math;
import <iostream>;

int main() {
    std::cout << add(1, 2) << std::endl;       // 3
    std::cout << multiply(3, 4) << std::endl;  // 12

    Calculator calc;
    std::cout << calc.compute(5, 6) << std::endl;
}

Compilation

# Compile module (creates .pcm or .ifc)
g++ -std=c++20 -fmodules-ts -c math.ixx -o math.pcm

# Compile and link with module
g++ -std=c++20 -fmodules-ts main.cpp math.pcm -o main

std::format (C++20)

Type-safe, extensible formatting library replacing printf and stringstream.

#include <format>
#include <iostream>

// Basic formatting
std::string s1 = std::format("Hello, {}!", "World");
std::string s2 = std::format("{} + {} = {}", 1, 2, 3);

// Positional arguments
std::string s3 = std::format("{1} before {0}", "second", "first");
// "first before second"

// Format specifiers
std::format("{:d}", 42);      // "42" (decimal)
std::format("{:x}", 255);     // "ff" (hex)
std::format("{:X}", 255);     // "FF" (uppercase hex)
std::format("{:b}", 5);       // "101" (binary)
std::format("{:o}", 8);       // "10" (octal)

// Width and alignment
std::format("{:10}", "hi");   // "hi        " (left, width 10)
std::format("{:>10}", "hi");  // "        hi" (right align)
std::format("{:^10}", "hi");  // "    hi    " (center)
std::format("{:*^10}", "hi"); // "****hi****" (fill with *)

// Floating point
std::format("{:.2f}", 3.14159);  // "3.14" (2 decimals)
std::format("{:e}", 1234.5);     // "1.234500e+03" (scientific)
std::format("{:g}", 1234.5);     // "1234.5" (general)

Format Specifier Syntax

┌─────────────────────────────────────────────────────────────────────────┐
│                     FORMAT SPECIFIER SYNTAX                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   {:[[fill]align][sign][#][0][width][.precision][type]}                 │
│                                                                          │
│   Examples:                                                              │
│   {:>10}      Right align, width 10                                     │
│   {:0>10}     Right align, pad with zeros                               │
│   {:.3f}      3 decimal places, fixed-point                             │
│   {:+.2f}     Always show sign, 2 decimals                              │
│   {:#x}       Hex with 0x prefix                                        │
│   {:*^20}     Center, width 20, fill with *                             │
│                                                                          │
│   align: < (left) > (right) ^ (center)                                  │
│   sign:  + (always) - (negative only) space (space for positive)        │
│   type:  d b o x X (integers) e E f F g G (floats) s (string)          │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Custom Formatter

struct Point {
    int x, y;
};

template<>
struct std::formatter<Point> {
    constexpr auto parse(std::format_parse_context& ctx) {
        return ctx.begin();
    }

    auto format(const Point& p, std::format_context& ctx) const {
        return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
    }
};

// Usage
Point p{10, 20};
std::cout << std::format("Point: {}", p);  // "Point: (10, 20)"

std::print (C++23)

#include <print>

std::print("Hello, {}!\n", "World");
std::println("Line with newline: {}", 42);

std::span (C++20)

A non-owning view over contiguous memory (like string_view for arrays).

┌─────────────────────────────────────────────────────────────────────────┐
│                          std::span CONCEPT                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   Original Array:  [10][20][30][40][50]                                 │
│                     ▲               ▲                                    │
│                     │               │                                    │
│                   data()         data()+size()                          │
│                                                                          │
│   span<int>:  ┌──────────┐                                              │
│               │ data_ ───┼──► [10][20][30][40][50]                      │
│               │ size_: 5 │                                              │
│               └──────────┘                                              │
│                                                                          │
│   subspan:    ┌──────────┐                                              │
│               │ data_ ───┼──────► [20][30][40]                          │
│               │ size_: 3 │                                              │
│               └──────────┘                                              │
│                                                                          │
│   ✓ No copying        ✓ Safe bounds checking                           │
│   ✓ Works with arrays, vectors, C arrays                               │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
#include <span>
#include <vector>
#include <array>

// Function that works with any contiguous container
void process(std::span<int> data) {
    for (int& x : data) {
        x *= 2;
    }
}

int main() {
    // Works with C arrays
    int arr[] = {1, 2, 3, 4, 5};
    process(arr);

    // Works with vectors
    std::vector<int> vec = {1, 2, 3, 4, 5};
    process(vec);

    // Works with std::array
    std::array<int, 5> sarr = {1, 2, 3, 4, 5};
    process(sarr);

    // Create span explicitly
    std::span<int> sp(arr);
    std::span<int> sp2(vec.data(), vec.size());
}

Span Operations

std::span<int> s(vec);

s.size();           // Number of elements
s.size_bytes();     // Size in bytes
s.empty();          // Check if empty
s.data();           // Pointer to first element

s.front();          // First element
s.back();           // Last element
s[2];               // Element access

s.first<3>();       // First 3 elements (static extent)
s.first(3);         // First 3 elements (dynamic extent)
s.last<2>();        // Last 2 elements
s.last(2);

s.subspan<1, 3>();  // Elements 1-3 (static)
s.subspan(1, 3);    // Elements 1-3 (dynamic)

Fixed-Size Span

// Static extent - size known at compile time
std::span<int, 5> fixed_span(arr);

// Dynamic extent - size determined at runtime
std::span<int> dynamic_span(vec);

// Function with fixed size requirement
void process_three(std::span<int, 3> data) {
    // Guaranteed 3 elements
}

int arr3[3] = {1, 2, 3};
process_three(arr3);  // OK
// process_three(arr);  // ERROR: arr has 5 elements

std::expected (C++23)

A type for returning either a value or an error (better than exceptions for expected errors).

┌─────────────────────────────────────────────────────────────────────────┐
│                       std::expected CONCEPT                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   expected<T, E>  - Contains either T (value) or E (error)              │
│                                                                          │
│   ┌─────────────────┐         ┌─────────────────┐                       │
│   │   has_value()   │         │   !has_value()  │                       │
│   ├─────────────────┤         ├─────────────────┤                       │
│   │    Success      │         │     Error       │                       │
│   │   ┌─────────┐   │         │   ┌─────────┐   │                       │
│   │   │ value T │   │         │   │ error E │   │                       │
│   │   └─────────┘   │         │   └─────────┘   │                       │
│   └─────────────────┘         └─────────────────┘                       │
│                                                                          │
│   Comparison with alternatives:                                          │
│                                                                          │
│   optional<T>     : Value or nothing (no error info)                    │
│   variant<T, E>   : Type-safe union (less ergonomic)                    │
│   Exceptions      : For exceptional cases, not expected errors          │
│   expected<T, E>  : Value OR error with full error details ✓           │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
#include <expected>
#include <string>

enum class ParseError {
    InvalidFormat,
    OutOfRange,
    Empty
};

std::expected<int, ParseError> parse_int(const std::string& s) {
    if (s.empty()) {
        return std::unexpected(ParseError::Empty);
    }

    try {
        int value = std::stoi(s);
        return value;  // Implicit conversion to expected
    } catch (const std::invalid_argument&) {
        return std::unexpected(ParseError::InvalidFormat);
    } catch (const std::out_of_range&) {
        return std::unexpected(ParseError::OutOfRange);
    }
}

// Usage
auto result = parse_int("42");

if (result) {  // or result.has_value()
    std::cout << "Value: " << *result << std::endl;
} else {
    switch (result.error()) {
        case ParseError::InvalidFormat:
            std::cout << "Invalid format" << std::endl;
            break;
        case ParseError::OutOfRange:
            std::cout << "Out of range" << std::endl;
            break;
        case ParseError::Empty:
            std::cout << "Empty string" << std::endl;
            break;
    }
}

Expected Operations

std::expected<int, std::string> exp = 42;

exp.has_value();        // true
exp.value();            // 42 (throws if error)
*exp;                   // 42 (undefined if error)
exp.value_or(0);        // 42 (or 0 if error)

exp.error();            // Error value (undefined if has value)

// Monadic operations (C++23)
exp.and_then([](int x) -> std::expected<int, std::string> {
    return x * 2;
});

exp.transform([](int x) { return x * 2; });

exp.or_else([](const std::string& err) -> std::expected<int, std::string> {
    std::cout << "Error: " << err << std::endl;
    return 0;  // Default value
});

Chaining Expected Operations

std::expected<std::string, Error> read_file(const std::string& path);
std::expected<Config, Error> parse_config(const std::string& content);
std::expected<App, Error> create_app(const Config& config);

// Chain operations (C++23)
auto result = read_file("config.json")
    .and_then(parse_config)
    .and_then(create_app);

if (result) {
    result->run();
} else {
    handle_error(result.error());
}

Three-Way Comparison (C++20)

The spaceship operator <=> provides all comparison operators in one.

┌─────────────────────────────────────────────────────────────────────────┐
│                   THREE-WAY COMPARISON (SPACESHIP)                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   a <=> b  returns one of:                                              │
│                                                                          │
│   ┌─────────────────┬─────────────────┬─────────────────┐              │
│   │ strong_ordering │ weak_ordering   │ partial_ordering│              │
│   ├─────────────────┼─────────────────┼─────────────────┤              │
│   │ • less          │ • less          │ • less          │              │
│   │ • equal         │ • equivalent    │ • equivalent    │              │
│   │ • greater       │ • greater       │ • greater       │              │
│   │                 │                 │ • unordered     │              │
│   ├─────────────────┼─────────────────┼─────────────────┤              │
│   │ int, char,      │ Case-insensitive│ floating-point  │              │
│   │ pointers        │ strings         │ (NaN issues)    │              │
│   └─────────────────┴─────────────────┴─────────────────┘              │
│                                                                          │
│   One <=> definition generates: ==, !=, <, <=, >, >=                    │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
#include <compare>

struct Point {
    int x, y;

    // Default spaceship operator
    auto operator<=>(const Point&) const = default;
};

Point p1{1, 2}, p2{1, 3};
bool less = (p1 < p2);     // true (compares x, then y)
bool equal = (p1 == p2);   // false

// Custom spaceship
struct Version {
    int major, minor, patch;

    std::strong_ordering operator<=>(const Version& other) const {
        if (auto cmp = major <=> other.major; cmp != 0) return cmp;
        if (auto cmp = minor <=> other.minor; cmp != 0) return cmp;
        return patch <=> other.patch;
    }

    bool operator==(const Version&) const = default;
};

Version v1{1, 2, 3}, v2{1, 3, 0};
bool newer = (v2 > v1);  // true

Additional C++23 Features

if consteval

constexpr int compute(int x) {
    if consteval {
        // Compile-time only code
        return x * 2;  // Can use consteval functions
    } else {
        // Runtime code
        return x * 2;  // Can use non-constexpr operations
    }
}

Deducing this (Explicit Object Parameter)

struct Widget {
    // Single implementation for both const and non-const
    template<typename Self>
    auto& value(this Self&& self) {
        return self.value_;
    }

    // CRTP without inheritance
    void print(this auto&& self) {
        std::cout << self.name() << std::endl;
    }

private:
    int value_;
};

std::mdspan (Multidimensional Span)

#include <mdspan>

std::vector<int> data(12);
std::iota(data.begin(), data.end(), 0);

// View as 3x4 matrix
std::mdspan matrix(data.data(), 3, 4);

for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < 4; ++j) {
        std::cout << matrix[i, j] << " ";
    }
    std::cout << "\n";
}
// 0 1 2 3
// 4 5 6 7
// 8 9 10 11

std::stacktrace

#include <stacktrace>

void problematic_function() {
    auto trace = std::stacktrace::current();
    std::cout << std::to_string(trace);
}

Lambda Improvements

// Attributes on lambdas
auto f = []() [[nodiscard]] { return 42; };

// static operator() (no captures, more efficient)
auto add = [](int a, int b) static { return a + b; };

Quick Reference

┌─────────────────────────────────────────────────────────────────────────┐
│                    MODERN C++ QUICK REFERENCE                            │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  C++11/14:                              C++17:                          │
│  ─────────                              ──────                          │
│  auto, decltype                         structured bindings             │
│  unique_ptr, shared_ptr                 optional, variant, any          │
│  move semantics                         string_view                     │
│  lambdas                                if constexpr                    │
│  range-based for                        fold expressions                │
│                                                                          │
│  C++20:                                 C++23:                          │
│  ──────                                 ──────                          │
│  concepts                               std::expected                   │
│  ranges                                 std::print/println              │
│  coroutines                             deducing this                   │
│  modules                                std::mdspan                     │
│  std::format                            std::stacktrace                 │
│  std::span                              if consteval                    │
│  three-way comparison (<=>)             ranges::zip, enumerate          │
│  consteval, constinit                   std::generator                  │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
// Type inference
auto x = expr;
decltype(expr) y;

// Smart pointers
unique_ptr<T> p = make_unique<T>(args);
shared_ptr<T> p = make_shared<T>(args);
weak_ptr<T> w = sharedPtr;

// Move semantics
T&& rref;
move(x);
forward<T>(x);

// Lambdas
[captures](params) { body }
[=] [&] [x] [&x] [x = expr]

// Structured bindings
auto [a, b] = pair;
auto& [x, y] = struct;

// Optional/Variant/Any
optional<T>, nullopt, .value_or()
variant<T1,T2>, get<T>(), visit()
any, any_cast<T>()

// string_view & span
string_view sv = str;
span<T> s = container;

// constexpr
constexpr T func();
if constexpr (cond) { }
consteval T must_be_compile_time();

// Concepts
template<Concept T> or requires Concept<T>

// Ranges
v | views::filter(pred) | views::transform(fn)

// Format
std::format("{} {}", arg1, arg2);
std::print("{}\n", value);  // C++23

// Expected (C++23)
std::expected<T, E> result = func();
result.value_or(default);
result.and_then(next_func);

// Three-way comparison
auto cmp = a <=> b;  // strong_ordering, weak_ordering, or partial_ordering

Compile & Run

# C++17
g++ -std=c++17 -Wall examples.cpp -o examples && ./examples

# C++20
g++ -std=c++20 -Wall examples.cpp -o examples && ./examples

# C++23 (requires GCC 13+ or Clang 17+)
g++ -std=c++23 -Wall examples.cpp -o examples && ./examples

# With modules (C++20)
g++ -std=c++20 -fmodules-ts module.cpp main.cpp -o main

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue