Boring C++
Boring C++

Exercise Solutions

11 min read

Appendix C: Exercise Solutions

Selected solutions for the exercises at the end of each chapter. Not every exercise has a solution here — for stretch exercises especially, there are multiple valid approaches. The goal of this appendix is to give you something to compare against, not to give you something to copy.

If your solution produces the right output but looks different from mine, it is probably fine. There is rarely one correct way to write a function.


Chapter 3: Types, Variables, and Control Flow

Exercise 1 (Easy): Multiplication table

#include <iostream>
#include <iomanip>

int main() {
    const int N = 10;
    for (int i = 1; i <= N; ++i) {
        for (int j = 1; j <= N; ++j) {
            std::cout << std::setw(4) << i * j;
        }
        std::cout << "\n";
    }
    return 0;
}

Exercise 2 (Easy): FizzBuzz

#include <iostream>

int main() {
    for (int i = 1; i <= 100; ++i) {
        if (i % 15 == 0)      std::cout << "FizzBuzz\n";
        else if (i % 3 == 0)  std::cout << "Fizz\n";
        else if (i % 5 == 0)  std::cout << "Buzz\n";
        else                   std::cout << i << "\n";
    }
    return 0;
}

Exercise 3 (Medium): Word frequency

#include <iostream>
#include <map>
#include <string>
#include <sstream>

int main() {
    std::string text = "the quick brown fox jumps over the lazy dog the fox";
    std::map<std::string, int> freq;

    std::istringstream ss{text};
    std::string word;
    while (ss >> word) {
        ++freq[word];
    }

    for (const auto& [w, count] : freq) {
        std::cout << w << ": " << count << "\n";
    }
    return 0;
}

Exercise 4 (Stretch): Top scorer

#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>

std::string topScorer(const std::vector<std::pair<std::string, int>>& scores) {
    if (scores.empty()) throw std::invalid_argument{"No scores provided"};

    std::string best;
    int bestScore = std::numeric_limits<int>::min();

    for (const auto& [name, score] : scores) {
        if (score > bestScore) {
            bestScore = score;
            best = name;
        }
    }
    return best;
}

int main() {
    std::vector<std::pair<std::string, int>> scores{
        {"Alice", 88}, {"Bob", 95}, {"Carol", 91}, {"Dave", 82}
    };
    std::cout << "Top scorer: " << topScorer(scores) << "\n";  // Bob
    return 0;
}

Chapter 4: Functions, Lambdas, and Callables

Exercise 1 (Easy): Clamp overloads

#include <iostream>

int   clamp(int   val, int   lo, int   hi) { return val < lo ? lo : (val > hi ? hi : val); }
float clamp(float val, float lo, float hi) { return val < lo ? lo : (val > hi ? hi : val); }
double clamp(double val, double lo, double hi) { return val < lo ? lo : (val > hi ? hi : val); }

int main() {
    std::cout << clamp(-5, 0, 10) << "\n";     // 0
    std::cout << clamp(5, 0, 10) << "\n";      // 5
    std::cout << clamp(15, 0, 10) << "\n";     // 10
    std::cout << clamp(3.14f, 0.0f, 1.0f) << "\n";  // 1.0
    return 0;
}

Exercise 3 (Medium): applyAll

#include <iostream>
#include <vector>
#include <functional>

void applyAll(const std::vector<int>& data,
              const std::vector<std::function<int(int)>>& transforms) {
    for (int x : data) {
        std::cout << x << " -> ";
        for (const auto& fn : transforms) {
            std::cout << fn(x) << " ";
        }
        std::cout << "\n";
    }
}

int main() {
    std::vector<int> data{1, 2, 3, 4, 5};
    int factor = 3;
    std::vector<std::function<int(int)>> fns{
        [](int x) { return x * x; },
        [](int x) { return x + 10; },
        [factor](int x) { return x * factor; }
    };
    applyAll(data, fns);
    return 0;
}

Exercise 4 (Stretch): compose

#include <iostream>

template<typename F, typename G>
auto compose(F f, G g) {
    return [f, g](auto x) { return f(g(x)); };
}

int main() {
    auto doubleIt = [](int x) { return x * 2; };
    auto square   = [](int x) { return x * x; };

    auto squareThenDouble = compose(doubleIt, square);  // double(square(x))
    auto doubleThenSquare = compose(square, doubleIt);  // square(double(x))

    std::cout << squareThenDouble(3) << "\n";  // 2*(3*3) = 18
    std::cout << doubleThenSquare(3) << "\n";  // (2*3)^2 = 36
    return 0;
}

Chapter 5: Classes and Objects

Exercise 1 (Easy): transfer function

#include <iostream>
#include <string>
#include <stdexcept>

class BankAccount {
public:
    BankAccount(const std::string& owner, double balance)
        : owner_{owner}, balance_{balance} {}

    bool withdraw(double amount) {
        if (amount > balance_) return false;
        balance_ -= amount;
        return true;
    }
    void deposit(double amount) { balance_ += amount; }
    double balance() const { return balance_; }
    const std::string& owner() const { return owner_; }

private:
    std::string owner_;
    double balance_;
};

bool transfer(BankAccount& from, BankAccount& to, double amount) {
    if (!from.withdraw(amount)) return false;
    to.deposit(amount);
    return true;
}

int main() {
    BankAccount alice{"Alice", 1000.0};
    BankAccount bob{"Bob", 200.0};

    if (transfer(alice, bob, 300.0)) {
        std::cout << "Transfer OK\n";
    }
    std::cout << "Alice: $" << alice.balance() << "\n";  // 700
    std::cout << "Bob:   $" << bob.balance() << "\n";    // 500

    if (!transfer(alice, bob, 900.0)) {
        std::cout << "Insufficient funds\n";
    }
    return 0;
}

Exercise 4 (Stretch): Shape hierarchy

#include <iostream>
#include <vector>
#include <memory>
#include <cmath>
#include <numeric>

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
    virtual double perimeter() const = 0;
    virtual std::string name() const = 0;
};

class Circle : public Shape {
public:
    explicit Circle(double r) : r_{r} {}
    double area() const override { return 3.14159265358979 * r_ * r_; }
    double perimeter() const override { return 2.0 * 3.14159265358979 * r_; }
    std::string name() const override { return "Circle"; }
private:
    double r_;
};

class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : w_{w}, h_{h} {}
    double area() const override { return w_ * h_; }
    double perimeter() const override { return 2.0 * (w_ + h_); }
    std::string name() const override { return "Rectangle"; }
private:
    double w_, h_;
};

class Triangle : public Shape {
public:
    Triangle(double a, double b, double c) : a_{a}, b_{b}, c_{c} {}
    double perimeter() const override { return a_ + b_ + c_; }
    double area() const override {
        double s = perimeter() / 2.0;
        return std::sqrt(s * (s-a_) * (s-b_) * (s-c_));
    }
    std::string name() const override { return "Triangle"; }
private:
    double a_, b_, c_;
};

double totalArea(const std::vector<std::unique_ptr<Shape>>& shapes) {
    double sum = 0.0;
    for (const auto& s : shapes) sum += s->area();
    return sum;
}

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(5.0));
    shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));
    shapes.push_back(std::make_unique<Triangle>(3.0, 4.0, 5.0));

    for (const auto& s : shapes) {
        std::cout << s->name() << ": area=" << s->area()
                  << ", perimeter=" << s->perimeter() << "\n";
    }
    std::cout << "Total area: " << totalArea(shapes) << "\n";
    return 0;
}

Chapter 6: Memory — Ownership and Lifetime

Exercise 1 (Easy): sumVector

#include <iostream>
#include <memory>
#include <vector>
#include <numeric>

int sumVector(std::unique_ptr<std::vector<int>> v) {
    return std::accumulate(v->begin(), v->end(), 0);
}

int main() {
    auto v = std::make_unique<std::vector<int>>(std::initializer_list<int>{1, 2, 3, 4, 5});
    std::cout << "Sum: " << sumVector(std::move(v)) << "\n";  // 15
    return 0;
}

Exercise 3 (Medium): ScopedTimer

#include <iostream>
#include <string>
#include <chrono>

class ScopedTimer {
public:
    explicit ScopedTimer(const std::string& label)
        : label_{label}, start_{std::chrono::steady_clock::now()}
    {
        std::cout << "[" << label_ << "] starting\n";
    }

    ~ScopedTimer() {
        auto end = std::chrono::steady_clock::now();
        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start_).count();
        std::cout << "[" << label_ << "] elapsed: " << ms << " ms\n";
    }

    ScopedTimer(const ScopedTimer&) = delete;
    ScopedTimer& operator=(const ScopedTimer&) = delete;

private:
    std::string label_;
    std::chrono::steady_clock::time_point start_;
};

int main() {
    ScopedTimer t{"main loop"};
    volatile long sum = 0;
    for (long i = 0; i < 100'000'000L; ++i) sum += i;
    std::cout << "Sum: " << sum << "\n";
    return 0;
}

Chapter 7: Templates and Generic Programming

Exercise 1 (Easy): clamp template

#include <iostream>
#include <string>

template<typename T>
T clamp(const T& val, const T& lo, const T& hi) {
    if (val < lo) return lo;
    if (val > hi) return hi;
    return val;
}

int main() {
    std::cout << clamp(-5, 0, 10) << "\n";         // 0
    std::cout << clamp(5, 0, 10) << "\n";          // 5
    std::cout << clamp(15, 0, 10) << "\n";         // 10
    std::cout << clamp(3.14, 0.0, 1.0) << "\n";   // 1.0
    std::string lo{"apple"}, hi{"mango"};
    std::cout << clamp(std::string{"cherry"}, lo, hi) << "\n";  // cherry
    return 0;
}

Exercise 3 (Medium): Pair class template

#include <iostream>
#include <string>

template<typename T, typename U>
class Pair {
public:
    T first;
    U second;

    Pair(const T& f, const U& s) : first{f}, second{s} {}

    bool operator==(const Pair& other) const {
        return first == other.first && second == other.second;
    }

    bool operator!=(const Pair& other) const {
        return !(*this == other);
    }
};

template<typename T, typename U>
Pair<T, U> make_pair_boring(const T& first, const U& second) {
    return Pair<T, U>{first, second};
}

int main() {
    auto p1 = make_pair_boring(42, std::string{"hello"});
    auto p2 = make_pair_boring(42, std::string{"hello"});
    auto p3 = make_pair_boring(42, std::string{"world"});

    std::cout << p1.first << ", " << p1.second << "\n";
    std::cout << std::boolalpha;
    std::cout << "p1 == p2: " << (p1 == p2) << "\n";  // true
    std::cout << "p1 == p3: " << (p1 == p3) << "\n";  // false
    return 0;
}

Chapter 8: The Standard Library

Exercise 1 (Easy): Character frequency

#include <iostream>
#include <map>
#include <string>

int main() {
    std::string text{"hello world"};
    std::map<char, int> freq;

    for (char c : text) {
        if (c != ' ') ++freq[c];
    }

    for (const auto& [ch, count] : freq) {
        std::cout << "'" << ch << "': " << count << "\n";
    }
    return 0;
}

Exercise 2 (Easy): Sorted unique vector

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

int main() {
    std::vector<std::string> words{"banana", "apple", "cherry", "apple", "banana", "date"};

    std::sort(words.begin(), words.end());
    auto newEnd = std::unique(words.begin(), words.end());
    words.erase(newEnd, words.end());

    for (const auto& w : words) std::cout << w << "\n";
    return 0;
}

Chapter 9: Error Handling

Exercise 1 (Easy): safeDivide

#include <iostream>
#include <optional>

std::optional<double> safeDivide(double a, double b) {
    if (b == 0.0) return std::nullopt;
    return a / b;
}

int main() {
    auto r1 = safeDivide(10.0, 3.0);
    auto r2 = safeDivide(5.0, 0.0);

    std::cout << r1.value_or(-1.0) << "\n";   // 3.333...
    std::cout << r2.value_or(-1.0) << "\n";   // -1
    std::cout << std::boolalpha;
    std::cout << r1.has_value() << "\n";       // true
    std::cout << r2.has_value() << "\n";       // false
    return 0;
}

Exercise 2 (Easy): Custom exception hierarchy

#include <iostream>
#include <stdexcept>
#include <string>

class RegistrationError : public std::runtime_error {
public:
    explicit RegistrationError(const std::string& msg) : std::runtime_error{msg} {}
};

class UserAlreadyExistsError : public RegistrationError {
public:
    explicit UserAlreadyExistsError(const std::string& username)
        : RegistrationError{"User already exists: " + username} {}
};

class InvalidEmailError : public RegistrationError {
public:
    explicit InvalidEmailError(const std::string& email)
        : RegistrationError{"Invalid email address: " + email} {}
};

class WeakPasswordError : public RegistrationError {
public:
    WeakPasswordError()
        : RegistrationError{"Password must be at least 8 characters"} {}
};

void registerUser(const std::string& name, const std::string& email, const std::string& password) {
    if (name == "alice") throw UserAlreadyExistsError{name};
    if (email.find('@') == std::string::npos) throw InvalidEmailError{email};
    if (password.size() < 8) throw WeakPasswordError{};
    std::cout << "User registered: " << name << "\n";
}

int main() {
    std::vector<std::tuple<std::string, std::string, std::string>> attempts{
        {"bob", "[email protected]", "securepassword"},
        {"alice", "[email protected]", "password123"},
        {"carol", "carolATbad.com", "password123"},
        {"dave", "[email protected]", "short"}
    };

    for (const auto& [name, email, pass] : attempts) {
        try {
            registerUser(name, email, pass);
        } catch (const RegistrationError& e) {
            std::cerr << "Registration failed: " << e.what() << "\n";
        }
    }
    return 0;
}

Chapter 10: Concurrency

Exercise 1 (Easy): Threads with mutex

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

std::mutex printMutex;

void printId(int id, int times) {
    for (int i = 0; i < times; ++i) {
        std::lock_guard<std::mutex> lock{printMutex};
        std::cout << "Thread " << id << " (step " << i << ")\n";
    }
}

int main() {
    std::vector<std::thread> threads;
    for (int i = 0; i < 4; ++i) {
        threads.emplace_back(printId, i, 5);
    }
    for (auto& t : threads) t.join();
    return 0;
}

Exercise 2 (Easy): Parallel squares with async

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

int main() {
    std::vector<std::future<int>> futures;

    for (int i = 1; i <= 10; ++i) {
        futures.push_back(
            std::async(std::launch::async, [i]() { return i * i; })
        );
    }

    for (int i = 0; i < 10; ++i) {
        std::cout << (i + 1) << "^2 = " << futures[i].get() << "\n";
    }
    return 0;
}

Chapter 11: Modern Idioms

Exercise 2 (Easy): formatTable with std::format

#include <iostream>
#include <format>
#include <vector>
#include <string>

void formatTable(const std::vector<std::pair<std::string, int>>& rows) {
    std::cout << std::format("{:<20} {:>10}\n", "Name", "Score");
    std::cout << std::format("{:-<20} {:->10}\n", "", "");
    for (const auto& [name, score] : rows) {
        std::cout << std::format("{:<20} {:>10}\n", name, score);
    }
}

int main() {
    std::vector<std::pair<std::string, int>> data{
        {"Alice", 95}, {"Bob", 82}, {"Carol", 91}, {"Dave", 78}
    };
    formatTable(data);
    return 0;
}

Exercise 3 (Medium): statistics with std::span

#include <iostream>
#include <span>
#include <vector>
#include <cmath>
#include <limits>
#include <format>

struct Stats {
    double min, max, mean, stddev;
};

Stats statistics(std::span<const double> data) {
    if (data.empty()) return {0, 0, 0, 0};

    double mn = std::numeric_limits<double>::max();
    double mx = std::numeric_limits<double>::lowest();
    double sum = 0.0;
    double sumSq = 0.0;

    for (double x : data) {
        if (x < mn) mn = x;
        if (x > mx) mx = x;
        sum += x;
        sumSq += x * x;
    }

    double mean = sum / data.size();
    double variance = sumSq / data.size() - mean * mean;
    double stddev = std::sqrt(variance);

    return {mn, mx, mean, stddev};
}

int main() {
    std::vector<double> v{2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0};
    auto s = statistics(v);

    std::cout << std::format("Min:    {:.2f}\n", s.min);
    std::cout << std::format("Max:    {:.2f}\n", s.max);
    std::cout << std::format("Mean:   {:.2f}\n", s.mean);
    std::cout << std::format("Stddev: {:.2f}\n", s.stddev);
    return 0;
}

Solutions for Chapter 12 exercises are in the full source repository at <TODO: book code repository URL>/tree/main/chapter12.

Boring C++

Modern C++ Mastery for Programmers Who Prefer Clarity Over Cleverness.

View book →