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.