Video Coming Soon...
53: Exception Alternatives
This exercise is pending. Quick notes about this exercise:
Talk about the possible problems with exceptions using a demo
Show how to not use them
std::optional, std::expected
Talk about nodiscard as a hack for error handling.
- Talk about how optional is usually more appropriate, and expected should be weighed with exceptions depending on how severe the error is.
- Demonstrate the terrible error messages you get from templates which makes std::expected difficult.
- Show how going crazy with ranges produces worse results.
- Get into a little way to evaluate whether to use optiona/expected/exceptions.
The Code
View Source file ex53.cpp Only
#include <fmt/core.h>
#include <chrono>
#include <thread>
#include <string>
#include <optional>
#include <expected>
#include <variant>
#include <ranges>
enum class find_error {
not_found, empty_list
};
using FoundNumbers = std::vector<std::tuple<size_t, int>>;
void handle_error(find_error err) {
switch(err) {
case find_error::not_found:
fmt::println("Error not found.");
break;
case find_error::empty_list:
fmt::println("Empty list, nothing to find.");
break;
default:
fmt::println("Unknown error code: {}", int(err));
}
}
std::optional<FoundNumbers> find_int(const std::vector<int>& numbers, int num) {
auto it = std::ranges::find(numbers, num);
FoundNumbers found;
for(const auto& [i, value] : std::views::enumerate(numbers)) {
// found it, because iterator is not equal to """end""" LOL
if(value == num) {
found.emplace_back(it - numbers.begin(), value);
}
}
if(found.empty()) return std::nullopt;
return found;
}
FoundNumbers must_find_throws(const std::vector<int>& numbers, int num) {
if(numbers.empty()) throw std::invalid_argument("No empty list allowed.");
FoundNumbers found;
for(const auto& [i, value] : std::views::enumerate(numbers)) {
if(value == num) found.emplace_back(i, value);
}
if(found.empty()) throw std::length_error("No found.");
return found;
}
std::expected<FoundNumbers, find_error> must_find_expected(const std::vector<int>& numbers, int num) {
if(numbers.empty()) return std::unexpected(find_error::empty_list);
FoundNumbers found;
for(const auto& [i, value] : std::views::enumerate(numbers)) {
if(value == num) found.emplace_back(i, value);
}
if(found.empty()) return std::unexpected(find_error::not_found);
return found;
}
std::expected<FoundNumbers, find_error> must_find_horror(const std::vector<int>& numbers, int num) {
using namespace std::ranges;
if(numbers.empty()) {
return std::unexpected{find_error::empty_list};
}
// remove const and watch the horror show
auto matched = [&](const auto &tuple) -> bool {
auto [i, value] = tuple;
return value == num;
};
auto found_nums = numbers | views::enumerate | views::filter(matched) | to<FoundNumbers>();
if(found_nums.empty()) {
return std::unexpected{find_error::not_found};
}
return found_nums;
}
int main() {
std::vector<int> numbers{1,2,3,3,4,5,10};
if(auto found = find_int(numbers, 100)) {
fmt::println("Found {} count numbers", found->size());
} else {
fmt::println("Not found.");
}
auto needs = must_find_expected(numbers, 3);
if(needs) {
fmt::println("Found {} count", needs->size());
} else {
handle_error(needs.error());
}
std::vector<int> empty;
auto bad_empty = must_find_expected(empty, 1000);
handle_error(bad_empty.error());
try {
auto found_bs = must_find_throws(empty, 3);
} catch(const std::exception& e) {
fmt::println("ERROR: {}", e.what());
}
auto bad = must_find_horror(numbers, 3);
if(bad) {
fmt::println("The horrific version found {}", bad->size());
} else {
handle_error(bad.error());
}
}
The Breakdown
line of code- Description.
The Discussion
Blah blah.
Further Study
- Do this next.
Register for Learn C++ the Hard Way
Register to gain access to additional videos which demonstrate each exercise. Videos are priced to cover the cost of hosting.