Video Coming Soon...

Created by Zed A. Shaw Updated 2026-06-19 17:00:40

53: Exception Alternatives

This exercise is pending. Quick notes about this exercise:

  1. Talk about how optional is usually more appropriate, and expected should be weighed with exceptions depending on how severe the error is.
  2. Demonstrate the terrible error messages you get from templates which makes std::expected difficult.
  3. Show how going crazy with ranges produces worse results.
  4. 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

Previous Lesson Next Lesson

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.