7 Utilities

Real programs deal with messy situations: a lookup might find nothing, a value could be one of several types, and functions sometimes need to return multiple values at once. Before C++17, programmers used raw pointers for “maybe no value,” unions for “one of several types,” and output parameters or custom structs for “return multiple things.” All of these are clunky and error-prone. The modern standard library provides clean, type-safe alternatives: std::optional, std::variant, std::any, std::tuple, and std::pair. The chapter also tours a handful of small utilities you will meet everywhere — std::swap, std::exchange, std::ref, std::function, std::bind, and std::initializer_list — and closes with user-defined literals, which let the type system enforce units like 500ms at compile time. In this chapter you will learn when and how to use each one.

7.1 std::optional

std::optional<T> (C++17, #include <optional>) holds either a value of type T or nothing at all. It is the right tool when “no value” is a valid result — like a database lookup that might not find a match or a configuration setting that might not be set:

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

std::optional<std::string> find_artist(int track_id) {
    if (track_id == 1) return "Outkast";
    if (track_id == 2) return "Missy Elliott";
    return std::nullopt;  // nothing found
}

int main() {
    auto result = find_artist(1);
    if (result.has_value()) {
        std::cout << "Found: " << result.value() << "\n";
    }

    auto missing = find_artist(99);
    if (!missing) {  // same as !missing.has_value()
        std::cout << "Not found\n";
    }

    return 0;
}
Found: Outkast
Not found

7.1.1 Accessing the Value

There are several ways to get the value out of an optional:

std::optional<int> opt = 42;

int a = opt.value();        // throws std::bad_optional_access if empty
int b = *opt;               // undefined behavior if empty --- no check!
int c = opt.value_or(0);    // returns 0 if empty

Trap: *opt does not check whether the optional contains a value. Use value() when you want an exception on empty access, or check with has_value() / if (opt) first.

7.1.2 Monadic Operations (C++23)

C++23 added three methods that let you chain operations on optionals without nested if checks:

std::optional<U> transform(F func);   // apply func if has value; U is func's result type
std::optional<U> and_then(F func);    // apply func that returns optional<U>
std::optional<T> or_else(F func);     // if empty, call func to provide fallback
#include <iostream>
#include <optional>
#include <string>

std::optional<std::string> lookup(int id) {
    if (id == 1) return "Nelly";
    return std::nullopt;
}

int main() {
    auto result = lookup(1)
        .transform([](const std::string& s) { return s + " Furtado"; })
        .value_or("Unknown");

    std::cout << result << "\n";  // Nelly Furtado

    auto empty = lookup(99)
        .transform([](const std::string& s) { return s + " Furtado"; })
        .value_or("Unknown");

    std::cout << empty << "\n";  // Unknown

    return 0;
}
Nelly Furtado
Unknown

transform applies the function only if the optional has a value, propagating nullopt otherwise. This avoids the pyramid of if (opt) checks.

7.2 std::variant

std::variant<Types...> (C++17, #include <variant>) holds exactly one value from a fixed set of types. It is a type-safe alternative to C unions:

#include <iostream>
#include <string>
#include <variant>

int main() {
    std::variant<int, double, std::string> v;

    v = 42;
    std::cout << std::get<int>(v) << "\n";       // 42

    v = "In the End";
    std::cout << std::get<std::string>(v) << "\n";  // In the End

    v = 3.14;
    std::cout << std::get<double>(v) << "\n";    // 3.14

    return 0;
}
42
In the End
3.14

A variant always holds exactly one of its types. Assigning a new value changes the active type.

7.2.1 Checking the Active Type

std::variant<int, std::string> v = "Hola";

if (std::holds_alternative<std::string>(v)) {
    std::cout << "It's a string: " << std::get<std::string>(v) << "\n";
}

// std::get throws std::bad_variant_access if the wrong type is active
// std::get_if returns a pointer (nullptr if wrong type)
if (auto* p = std::get_if<int>(&v)) {
    std::cout << "It's an int: " << *p << "\n";
}

7.2.2 std::visit

std::visit calls a function with the currently active value, whatever its type. The function must handle all possible types:

#include <iostream>
#include <string>
#include <variant>

int main() {
    std::variant<int, double, std::string> v = "Complicated";

    std::visit([](auto&& val) {
        std::cout << val << "\n";
    }, v);

    return 0;
}
Complicated

The lambda uses auto&& to accept any type. For type-specific behavior, you can use an overload set:

struct Visitor {
    void operator()(int i) const { std::cout << "int: " << i << "\n"; }
    void operator()(double d) const { std::cout << "double: " << d << "\n"; }
    void operator()(const std::string& s) const { std::cout << "string: " << s << "\n"; }
};

std::visit(Visitor{}, v);

Tip: std::variant is especially useful for representing states (e.g., variant<Loading, Loaded, Error>) or heterogeneous data (e.g., a JSON value that could be a number, string, bool, or null).

7.3 std::any

std::any (C++17, #include <any>) can hold a value of any type. Unlike variant, you do not need to list the possible types upfront:

#include <any>
#include <iostream>
#include <string>

int main() {
    std::any a = 42;
    std::cout << std::any_cast<int>(a) << "\n";  // 42

    a = std::string("Such Great Heights");
    std::cout << std::any_cast<std::string>(a) << "\n";  // Such Great Heights

    // Wrong type throws std::bad_any_cast
    try {
        std::cout << std::any_cast<double>(a) << "\n";
    } catch (const std::bad_any_cast& e) {
        std::cout << "Bad cast: " << e.what() << "\n";
    }

    return 0;
}
42
Such Great Heights
Bad cast: bad any_cast

std::any uses type erasure internally and can hold any copyable type. It is useful for plugin systems or generic containers where the set of types is not known at compile time.

Wut: Prefer std::variant over std::any when you know the possible types. variant checks types at compile time; any defers all checking to run time. any is essentially a type-safe void* — use it only when you genuinely do not know the type in advance.

7.4 std::tuple

std::tuple<Types...> (C++11, #include <tuple>) groups a fixed number of values of different types. It is a generalization of std::pair to any number of elements:

#include <iostream>
#include <string>
#include <tuple>

int main() {
    std::tuple<std::string, int, double> track("Yeah!", 2004, 4.5);

    std::cout << std::get<0>(track) << "\n";  // Yeah!
    std::cout << std::get<1>(track) << "\n";  // 2004
    std::cout << std::get<2>(track) << "\n";  // 4.5

    return 0;
}
Yeah!
2004
4.5

7.4.1 Structured Bindings

Accessing tuple elements by index is awkward. C++17 structured bindings let you unpack a tuple into named variables:

auto [title, year, rating] = track;
std::cout << title << " (" << year << ") - " << rating << " stars\n";

Structured bindings work with tuples, pairs, arrays, and structs:

// With std::pair
std::pair<std::string, int> album = {"Elephunk", 2003};
auto [name, yr] = album;

// With arrays
int arr[] = {10, 20, 30};
auto [a, b, c] = arr;

// With structs
struct Point { double x, y; };
Point p = {3.0, 4.0};
auto [px, py] = p;

7.4.2 std::make_tuple and std::tie

std::make_tuple creates a tuple with deduced types:

auto t = std::make_tuple("Breathe Me", 2005, true);

std::tie creates a tuple of references, useful for unpacking into existing variables or for comparison:

std::string title;
int year;
bool favorite;

std::tie(title, year, favorite) = t;
std::cout << title << "\n";  // Breathe Me

With C++17 structured bindings, you rarely need std::tie anymore.

7.4.3 Returning Multiple Values

Tuples are a natural way to return multiple values from a function:

#include <iostream>
#include <string>
#include <tuple>

std::tuple<std::string, int> parse_track(const std::string& entry) {
    auto dash = entry.find(" - ");
    return {entry.substr(0, dash), std::stoi(entry.substr(dash + 3))};
}

int main() {
    auto [artist, year] = parse_track("Snow Patrol - 2003");
    std::cout << artist << ", " << year << "\n";

    return 0;
}
Snow Patrol, 2003

Tip: For functions that return two or three related values, a named struct is often clearer than a tuple. auto [name, age, score] is fine; auto [a, b, c, d, e, f] is not — the reader has no idea what each element means.

7.5 std::pair Revisited

You have already used std::pair with std::map in Chapter 3. A pair is just a two-element tuple with named members first and second:

std::pair<std::string, int> song("Lollipop", 2008);
std::cout << song.first << ": " << song.second << "\n";  // Lollipop: 2008

// C++17: CTAD
std::pair p("Stacy's Mom", 2003);  // deduces pair<const char*, int>

You can also create pairs with std::make_pair:

auto p = std::make_pair("Float On", 2004);

Structured bindings make pairs much more readable than accessing .first and .second:

for (const auto& [song, year] : my_map) {
    std::cout << song << " (" << year << ")\n";
}

7.6 Small Utilities Worth Knowing

7.6.1 std::swap and std::exchange

std::swap(a, b) exchanges two values; std::exchange(a, new_value) writes new_value into a and returns the old value. Both are tiny but show up everywhere in the standard library and idiomatic code.

void swap   (T& a, T& b);
T    exchange(T& obj, U&& new_value);
#include <iostream>
#include <utility>

int main() {
    int a = 1, b = 2;
    std::swap(a, b);                            // a == 2, b == 1
    int old = std::exchange(a, 99);             // a == 99, old == 2
    std::cout << a << " " << old << "\n";
    return 0;
}

exchange is the cleanest way to “set this variable to a new value, but tell me what it used to be” — the canonical use is the body of a move assignment operator that needs to take resources from other and leave it in a valid empty state:

this->ptr_ = std::exchange(other.ptr_, nullptr);

Tip: When implementing a custom swap, write a non-member swap(T&, T&) in the same namespace as T. That lets the standard library find your version through ADL (Chapter 8) instead of falling back to the move-and-assign default.

7.6.2 std::ref and std::cref

How a template deduces a parameter type depends on the form of the parameter (T, T&, T&&). std::thread and std::bind go a step further: they decay-copy the arguments they store, stripping references entirely, so each thread or bound callable owns its own copy. If you actually want to share a single object, std::ref and std::cref produce reference wrappers that these templates know to unwrap into real references:

std::reference_wrapper<T>       std::ref (T& x);
std::reference_wrapper<const T> std::cref(const T& x);
#include <functional>
#include <iostream>
#include <thread>

void increment(int& n) { ++n; }

int main() {
    int count = 0;
    std::thread t(increment, std::ref(count));   // share the int, do not copy
    t.join();
    std::cout << count << "\n";                  // 1
    return 0;
}

Without std::ref, this does not even compile: std::thread decay-copies its arguments, and the stored copy of count cannot bind to the int& parameter of increment, so g++ rejects the call with static assertion failed: std::thread arguments must be invocable after conversion to rvalues. std::ref(count) stores a reference_wrapper instead, which unwraps to a real int& inside the new thread. std::bind, by contrast, would silently increment its own private copy — arguably a worse failure mode, since nothing stops you at compile time.

Trap: A std::reference_wrapper does not extend the lifetime of what it refers to. Storing one in a container that outlives the referent is undefined behavior, just like a dangling raw reference.

7.6.3 std::function

A std::function<R(Args...)> is a type-erased wrapper that can hold any callable with the matching signature — a lambda, a free function, a member function pointer, a functor. The trade-off is small overhead per call (an indirect call through a vtable-like dispatch), but in exchange you get a single concrete type you can store, copy, and pass around without templates.

template<class R, class... Args>
class function<R(Args...)>;        // lives in namespace std
#include <functional>
#include <iostream>
#include <vector>

int main() {
    std::vector<std::function<int(int)>> ops = {
        [](int x) { return x + 1; },
        [](int x) { return x * 2; },
        [](int x) { return x * x; },
    };
    for (auto& op : ops) {
        std::cout << op(5) << " ";
    }
    std::cout << "\n";
    return 0;
}

std::function is the right tool when you need a heterogeneous container of callables, when you want a callback parameter that does not turn the surrounding function into a template, or when the callable needs to outlive the lambda’s enclosing scope. For inner loops where the dispatch cost matters, prefer a template parameter so the compiler can inline the call.

7.6.4 std::bind

std::bind is a C++11-era tool to “partially apply” a function: bake some arguments in now, leave the rest for later.

#include <functional>
#include <iostream>

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

int main() {
    auto add5 = std::bind(add, 5, std::placeholders::_1);
    std::cout << add5(10) << "\n";              // 15
    return 0;
}

In modern code, lambdas have largely retired bind:

auto add5 = [](int b) { return add(5, b); };   // clearer, no placeholders

You will still see std::bind in older codebases, so it is worth recognizing. For new code, write a lambda — it is shorter, easier to read, and the compiler can inline through it.

7.6.5 std::initializer_list

std::initializer_list<T> is the type the compiler manufactures when you write a brace-enclosed list of values. You have been using it implicitly every time you wrote std::vector<int> v = {1, 2, 3}; — the right-hand side is an initializer_list<int> that the vector’s constructor consumes.

You only need to write std::initializer_list yourself when you are designing a container-like class that should accept the same {a, b, c} syntax:

#include <initializer_list>
#include <iostream>
#include <string>
#include <vector>

class Setlist {
    std::vector<std::string> tracks_;
public:
    Setlist(std::initializer_list<std::string> list)
        : tracks_(list) {}

    void print() const {
        for (const auto& t : tracks_) std::cout << t << "\n";
    }
};

int main() {
    Setlist s = {"Crazy in Love", "Yeah!", "Toxic"};
    s.print();
    return 0;
}

Wut: A constructor that takes std::initializer_list<T> is preferred over other constructors when the call site uses {}. std::vector<int> v(3, 5); calls the (size, value) constructor and gives you {5, 5, 5}. std::vector<int> v{3, 5}; calls the initializer-list constructor and gives you {3, 5}. The braces flip the choice.

7.6.6 Custom operator<< for User Types

Every type you build will eventually want to print to a stream. The standard idiom is a non-member operator<< that takes the stream by reference and returns it for chaining:

std::ostream& operator<<(std::ostream& os, const T& value);
#include <iostream>
#include <string>

struct Track {
    std::string title;
    int         year;
};

std::ostream& operator<<(std::ostream& os, const Track& t) {
    return os << t.title << " (" << t.year << ")";
}

int main() {
    Track t{"Crazy in Love", 2003};
    std::cout << t << "\n";                     // Crazy in Love (2003)
    return 0;
}

The function must be non-member because a member operator<< would have to live in the left-hand operand’s class, and you cannot add members to std::ostream. It returns the stream by reference so calls chain: std::cout << a << b << c. You can also define operator>> symmetrically for input.

Tip: If your operator<< needs access to private members, declare it a friend inside the class — Gorgo Starting C++ covered the friend mechanism in the chapter on Special Members and Friends. The “hidden friend” idiom in Chapter 8 of this book is the modern, ADL-friendly form.

7.7 User-Defined Literals

Plain numbers and strings in source code carry no context. Look at this call:

schedule_reminder(30);

Is 30 minutes? Seconds? Milliseconds? Days? You cannot tell without opening the function. Worse, if you guess wrong, the compiler will not stop you — it just sees an int. Gorgo Starting C++ introduced literal suffixes (U, L, f, s). User-defined literals (UDLs) extend the same idea to types you write yourself, so the unit travels with the value.

7.7.1 Standard Library UDLs

The standard library ships several UDL suffixes that you have probably already used or seen. Each lives in its own inline namespace; pulling in std::literals (or one of the more specific namespaces) makes them visible.

#include <chrono>
#include <string>
#include <string_view>

using namespace std::literals;

auto greeting = "Hola"s;          // std::string
auto view     = "Hola"sv;         // std::string_view
auto pause    = 250ms;            // std::chrono::milliseconds
auto workday  = 8h;               // std::chrono::hours

The chrono suffixes are where UDLs really pay off. Compare these two function signatures:

void poll_for(int milliseconds);
void poll_for(std::chrono::milliseconds duration);

With the first one, every caller has to remember the unit, and poll_for(5) may or may not be what was intended. With the second one, the type system enforces the unit:

poll_for(500ms);       // OK
poll_for(2s);          // OK --- automatic conversion to milliseconds
poll_for(5);           // ERROR: int is not chrono::milliseconds

Mixing units is also safe. The chrono library knows how to combine durations of different units — adding 1s + 250ms produces 1250ms, and you cannot accidentally add a duration to a temperature, an angle, or a raw int.

Tip: Reach for chrono UDLs whenever you would otherwise pass a bare number that represents time. The few extra characters buy you compile-time unit checking, automatic unit conversion, and code that reads like English: sleep_for(2s), timeout = 500ms, cache.expires_in(24h).

7.7.2 Defining Your Own

UDLs are not magic — they are operator overloads for operator"". You provide a function whose name is operator"" followed by your suffix, and the compiler calls it whenever it sees a literal with that suffix.

The suffix must start with an underscore. Suffix names without a leading _ are reserved for the standard library, and most compilers will warn if you try to define one.

Here is a temperature example. We define two simple types and a UDL for each:

#include <print>

struct Celsius {
    double value;
};

struct Fahrenheit {
    double value;
};

constexpr Celsius operator""_C(long double v) {
    return Celsius{static_cast<double>(v)};
}

constexpr Fahrenheit operator""_F(long double v) {
    return Fahrenheit{static_cast<double>(v)};
}

constexpr Celsius to_celsius(Fahrenheit f) {
    return Celsius{(f.value - 32.0) * 5.0 / 9.0};
}

void describe(Celsius c) {
    std::println("{:.1f} C", c.value);
}

int main() {
    describe(22.5_C);                  // 22.5 C
    describe(to_celsius(450.0_F));     // 232.2 C (pizza oven)
    // describe(450.0_F);              // ERROR: Fahrenheit is not Celsius
}

The last commented-out line is the whole point. Without UDLs, both temperatures would be plain double and the compiler would happily call describe(450.0) as if it were Celsius — producing a 450-degree room.

7.7.3 Parameter Signatures

The literal kind dictates which parameter list your operator"" must use. The most common forms are:

Literal kind Signature
Integer (42_x) T operator""_x(unsigned long long)
Floating-point (3.14_x) T operator""_x(long double)
Character ('a'_x) T operator""_x(char)
String ("text"_x) T operator""_x(const char*, std::size_t)

Your function receives the literal in its widest form (unsigned long long for any integer literal, long double for any floating-point literal) and returns whatever type you like. Mark the function constexpr so the literal can be used in compile-time contexts.

// distance UDLs --- store everything internally as meters
struct Meters {
    double value;
};

constexpr Meters operator""_m(long double v) {
    return Meters{static_cast<double>(v)};
}
constexpr Meters operator""_km(long double v) {
    return Meters{static_cast<double>(v) * 1000.0};
}
constexpr Meters operator""_cm(long double v) {
    return Meters{static_cast<double>(v) / 100.0};
}

constexpr Meters total = 1.5_km + 200.0_m + 50.0_cm;
// requires operator+ for Meters; total.value == 1700.5

7.7.4 When UDLs Are (and Aren’t) the Right Tool

UDLs shine when:

  • the literal stands for a unit (time, distance, mass, money, angles)
  • you want the compiler to catch unit mix-ups at compile time
  • the suffix makes a call site read like English (5s, 30deg, 100USD)

UDLs are overkill when:

  • the value is a one-off and the surrounding code already makes the unit obvious
  • a constructor like Money{100, "USD"} is just as clear and you do not have many call sites

Trap: A UDL suffix without a leading underscore (e.g. operator""F) clashes with the standard library’s reserved namespace. Modern compilers warn, and a future standard could break your code outright. Always prefix your suffixes with _.

Tip: UDLs are not the only way to enforce units — you can also use a strong-typedef library or std::chrono-style class templates. But UDLs make call sites dramatically more readable, and they are essentially free at run time when the operator function is constexpr.

7.8 Try It: Utility Sampler

Here is a program that exercises the utility types from this chapter. Type it in, compile with g++ -std=c++23, and experiment:

#include <any>
#include <iostream>
#include <optional>
#include <string>
#include <tuple>
#include <variant>
#include <vector>

// optional: safe lookup
std::optional<int> find_year(const std::string& title) {
    if (title == "Take Me Out") return 2004;
    if (title == "Float On") return 2004;
    if (title == "Naive") return 2006;
    return std::nullopt;
}

// variant: a value that could be several types
using JsonValue = std::variant<int, double, std::string, bool>;

void print_json(const JsonValue& v) {
    std::visit([](auto&& val) {
        using T = std::decay_t<decltype(val)>;
        if constexpr (std::is_same_v<T, bool>) {
            std::cout << (val ? "true" : "false");
        } else if constexpr (std::is_same_v<T, std::string>) {
            std::cout << "\"" << val << "\"";
        } else {
            std::cout << val;
        }
    }, v);
}

int main() {
    // optional
    for (const auto& title : {"Take Me Out", "Unknown Song", "Naive"}) {
        auto year = find_year(title);
        std::cout << title << ": " << year.value_or(0) << "\n";
    }

    // variant
    std::cout << "\nJSON values: ";
    std::vector<JsonValue> values = {42, 3.14, std::string("Hola"), true};
    for (const auto& v : values) {
        print_json(v);
        std::cout << " ";
    }
    std::cout << "\n";

    // tuple
    auto [artist, album, year] = std::make_tuple("Franz Ferdinand", "Franz Ferdinand", 2004);
    std::cout << "\n" << artist << " - " << album << " (" << year << ")\n";

    // any
    std::any wild = 42;
    std::cout << "\nany<int>: " << std::any_cast<int>(wild) << "\n";
    wild = std::string("Anything goes");
    std::cout << "any<string>: " << std::any_cast<std::string>(wild) << "\n";

    return 0;
}

Try adding a function that returns std::optional<std::tuple<...>> for a lookup that returns multiple values or nothing.

7.9 Key Points

  • std::optional<T> represents a value that may or may not be present. Use it instead of sentinel values (-1, nullptr) or output parameters.
  • Access optional values with value() (throws if empty), *opt (UB if empty), or value_or(default).
  • C++23 monadic operations (transform, and_then, or_else) let you chain operations on optionals without nested checks.
  • std::variant<Types...> holds one value from a fixed set of types. It is a type-safe replacement for C unions.
  • std::visit dispatches to a function based on the active type. Use std::holds_alternative or std::get_if for type checks.
  • std::any can hold any copyable type, checked only at run time. Prefer variant when you know the possible types.
  • std::tuple groups multiple values of different types. Structured bindings (auto [a, b, c]) make tuples readable.
  • std::pair is a two-element tuple with first and second members. Structured bindings are preferred over accessing .first/.second directly.
  • For functions returning multiple values, prefer a named struct when there are more than two or three elements.
  • User-defined literals (operator""_suffix) attach a unit-like suffix to a literal so the type system can enforce units at compile time. The standard library provides s (string), sv (string_view), and chrono suffixes (ms, s, h); custom suffixes must start with _.

7.10 Exercises

  1. Think about it: Why is std::optional better than returning a magic value like -1 or an empty string to indicate “no result”?

  2. What does this print?

    std::optional<int> opt;
    std::cout << opt.value_or(42) << "\n";
    opt = 7;
    std::cout << opt.value_or(42) << "\n";
  3. Where is the bug?

    std::optional<std::string> name;
    std::cout << *name << "\n";
  4. What does this print?

    std::variant<int, std::string> v = 42;
    v = "changed";
    std::cout << std::holds_alternative<int>(v) << "\n";
    std::cout << std::holds_alternative<std::string>(v) << "\n";
  5. Think about it: When would you use std::any instead of std::variant? Give a concrete example.

  6. Calculation: Given:

    auto t = std::make_tuple(10, 20, 30, 40);
    auto [a, b, c, d] = t;

    What are the values of a, b, c, and d?

  7. Where is the bug?

    std::variant<int, double, std::string> v = 3.14;
    int x = std::get<int>(v);
  8. What does this print?

    std::pair p(std::string("Naive"), 2006);
    auto [title, year] = p;
    year = 2007;
    std::cout << p.second << "\n";
  9. Think about it: The text suggests using a named struct instead of a large tuple for function return values. Why? What are the advantages of each approach?

  10. Write a program that defines a function parse_color which takes a string like "rgb(255,128,0)" and returns std::optional<std::tuple<int, int, int>>. Return std::nullopt if the format is wrong. Test it with valid and invalid inputs, and use structured bindings to unpack successful results.

  11. Write a move_assign function that uses std::exchange to write the canonical move-assignment body:

    struct Buffer {
        int*        data_  = nullptr;
        std::size_t size_  = 0;
        // ... constructors, destructor that delete[]s data_ ...
    };
    
    Buffer& move_assign(Buffer& dst, Buffer&& src) noexcept;

    The function should:

    • free dst’s existing storage,
    • take src’s data_ pointer (replacing it with nullptr in src),
    • take src’s size_ (replacing it with 0 in src), and
    • return a reference to dst.

    Use std::exchange for the two takes-and-replaces in a single line each. Why is std::exchange cleaner here than reading + writing the source manually?

  12. Where is the bug?

    #include <iostream>
    #include <thread>
    
    void inc(int& n) { ++n; }
    
    int main() {
        int count = 0;
        std::thread t(inc, count);
        t.join();
        std::cout << count << "\n";
        return 0;
    }

    This program does not compile. Why does the compiler reject it, what is the fix, and what does the program print once it is fixed?

  13. What does this print?

    #include <iostream>
    #include <vector>
    
    int main() {
        std::vector<int> a(3, 5);
        std::vector<int> b{3, 5};
        std::cout << a.size() << " " << b.size() << "\n";
        return 0;
    }

    Why do (3, 5) and {3, 5} give different vectors?

  14. Write a Track class with private title and year members and define operator<< so this works:

    Track t{"Crazy in Love", 2003};
    std::cout << t << "\n";

    Mention what the friend declaration inside the class buys you here, and how the chapter on hidden friends in Chapter 8 changes the recommendation.

  15. Where is the bug? The intent is for wait_a_bit to pause for half a second. What does this code actually do, and what is the one-line fix?

    #include <chrono>
    #include <thread>
    void wait_a_bit() {
        std::this_thread::sleep_for(std::chrono::milliseconds{500000});
    }
  16. What does this print, and which line fails to compile?

    #include <chrono>
    #include <print>
    using namespace std::chrono_literals;
    
    auto a = 1s + 250ms;
    auto b = 24h - 30min;
    auto c = 5 + 250ms;            // ?
    std::println("{} {}", a.count(), b.count());
  17. Write your own UDL for angles in degrees that converts to radians at compile time:

    constexpr double operator""_deg(long double v) { /* ... */ }
    
    constexpr double right_angle = 90.0_deg;   // pi / 2

    Then write std::sin(right_angle) and confirm it produces approximately 1.0.

  18. Think about it: The standard library reserves UDL suffixes that do not start with an underscore (e.g. s, ms, h, sv). Why does forcing user-defined suffixes to start with _ matter for forward compatibility?