Errors in C++

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

// In C++, we typically use exceptions for error handling, but we can
// simulate Go-style error returns using std::pair or a custom struct.
std::pair<int, std::string> f(int arg) {
    if (arg == 42) {
        // Instead of errors.New, we return a string as the error.
        return {-1, "can't work with 42"};
    }
    // A empty string in the error position indicates that there was no error.
    return {arg + 3, ""};
}

// We can use custom exception classes to represent specific error types.
class OutOfTeaError : public std::runtime_error {
public:
    OutOfTeaError() : std::runtime_error("no more tea available") {}
};

class PowerError : public std::runtime_error {
public:
    PowerError() : std::runtime_error("can't boil water") {}
};

void makeTea(int arg) {
    if (arg == 2) {
        throw OutOfTeaError();
    } else if (arg == 4) {
        // In C++, we can't directly wrap exceptions, but we can catch and rethrow
        // with additional context.
        try {
            throw PowerError();
        } catch (const PowerError& e) {
            throw std::runtime_error(std::string("making tea: ") + e.what());
        }
    }
}

int main() {
    for (int i : {7, 42}) {
        auto [result, error] = f(i);
        if (!error.empty()) {
            std::cout << "f failed: " << error << std::endl;
        } else {
            std::cout << "f worked: " << result << std::endl;
        }
    }

    for (int i = 0; i < 5; ++i) {
        try {
            makeTea(i);
            std::cout << "Tea is ready!" << std::endl;
        } catch (const OutOfTeaError& e) {
            std::cout << "We should buy new tea!" << std::endl;
        } catch (const PowerError& e) {
            std::cout << "Now it is dark." << std::endl;
        } catch (const std::exception& e) {
            std::cout << "unknown error: " << e.what() << std::endl;
        }
    }

    return 0;
}

This C++ code demonstrates error handling techniques that are similar to those used in the original example. Here are some key points:

  1. We use std::pair<int, std::string> to simulate Go’s multiple return values for the f function. The second element of the pair represents the error.

  2. Custom exception classes OutOfTeaError and PowerError are used to represent specific error conditions, similar to sentinel errors in the original code.

  3. The makeTea function throws exceptions instead of returning error values.

  4. In the main function, we use a range-based for loop and structured bindings to handle the result of f.

  5. For error checking in makeTea, we use a try-catch block to handle different types of exceptions.

  6. The dynamic_cast is used to check for specific exception types, similar to the errors.Is function in the original code.

While C++ uses exceptions as its primary error-handling mechanism, this example shows how you can implement a error-return style similar to Go’s approach. However, it’s important to note that this is not idiomatic C++ and in real C++ code, you would typically use exceptions for error handling.