Step 4 of 7
A Result type
optional says that something failed, but not why. When there are several ways to fail, the caller (and the user reading an error message) needs to know which one happened. A small Result type carries either a value or an error description. C++23 standardizes the idea as std::expected<T, E>; you'll often see hand-written versions like this one.
#include <iostream>
#include <optional>
#include <string>
struct AgeResult {
std::optional<int> value;
std::string error;
bool ok() const { return value.has_value(); }
};
AgeResult parse_age(const std::string& s) {
if (s.empty()) return {std::nullopt, "empty"};
int v = 0;
for (char c : s) {
if (c < '0' || c > '9') return {std::nullopt, "not a number"};
v = v * 10 + (c - '0');
if (v > 150) return {std::nullopt, "unrealistic"};
}
return {v, ""};
}
int main() {
for (const char* in : {"42", "", "4x", "999"}) {
AgeResult r = parse_age(in);
if (r.ok()) std::cout << "'" << in << "' -> " << *r.value << "\n";
else std::cout << "'" << in << "' -> error: " << r.error << "\n";
}
}
'42' -> 42
'' -> error: empty
'4x' -> error: not a number
'999' -> error: unrealistic
How it works
- Each failure returns immediately with its own message. The checks run in order, so decide which error wins when several apply. Here an empty string is reported as "empty", not "not a number".
return {v, ""};builds the struct from braces: first the optional, then the error.v > 150is checked inside the loop, as soon as the number gets too big. Checking only at the end could overflowinton a very long string of digits first.
Your task: money
The same structure, in this order: check the leading $ (also catches the empty string), then check there's at least one character after it, then check that every character after it is a digit while building the value, then compare with the cap. Checking the cap inside the loop protects you from overflow on inputs like $99999999999.
Your task
Write Result parse_money(const std::string& s) for amounts like "$2700":
- no leading
$gives error"missing quot; - nothing after the
$, or a non-digit character, gives"not a number" - more than 16000 gives
"over the cap" - otherwise the value
Challenge 2
Write Result parse_time(const std::string& s) for times like "1:45" (minutes, a colon, then two-digit seconds), returning the total in seconds:
- no
:gives"missing :" - empty minutes, seconds that aren't exactly two digits, or any non-digit gives
"not a number" - seconds of 60 or more give
"seconds must be under 60"
Challenge 3
Make Result a template so it can carry any type. Write template <typename T> struct Result with std::optional<T> value, std::string error, bool ok() const, and two static helpers, ok_with(T v) and fail(std::string e). Then write Result<int> checked_div(int a, int b): "division by zero" for b == 0, otherwise a / b.