Custom Errors in C++

Here’s the translation of the custom errors example to C++:

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

// A custom error type usually has the suffix "Error".
class ArgError : public std::runtime_error {
private:
    int arg;
    std::string message;

public:
    ArgError(int a, const std::string& m) : std::runtime_error(m), arg(a), message(m) {}

    int getArg() const { return arg; }
    const std::string& getMessage() const { return message; }

    // Override what() to provide a custom error message
    const char* what() const noexcept override {
        static std::string error_message;
        error_message = std::to_string(arg) + " - " + message;
        return error_message.c_str();
    }
};

std::pair<int, std::unique_ptr<std::exception>> f(int arg) {
    if (arg == 42) {
        // Return our custom error.
        return {-1, std::make_unique<ArgError>(arg, "can't work with it")};
    }
    return {arg + 3, nullptr};
}

int main() {
    auto [result, err] = f(42);
    
    // dynamic_cast is somewhat similar to errors.As in Go.
    // It checks if the exception can be cast to ArgError*,
    // returning nullptr if it can't.
    if (auto ae = dynamic_cast<ArgError*>(err.get())) {
        std::cout << ae->getArg() << std::endl;
        std::cout << ae->getMessage() << std::endl;
    } else {
        std::cout << "err doesn't match ArgError" << std::endl;
    }
}

This C++ code demonstrates how to create and use custom error types. Here’s a breakdown of the translation:

  1. We define a custom ArgError class that inherits from std::runtime_error. This is similar to implementing the error interface in Go.

  2. The ArgError class includes the arg and message fields, and provides methods to access them.

  3. We override the what() method to provide a custom error message, similar to the Error() method in Go.

  4. The f function returns a std::pair with the result and a std::unique_ptr<std::exception> for the error. This is similar to returning multiple values in Go.

  5. In the main function, we use structured binding to get the result and error from f.

  6. We use dynamic_cast to check if the error is of type ArgError. This is similar to errors.As in Go, but with some differences:

    • dynamic_cast returns nullptr if the cast fails, while errors.As returns a boolean.
    • dynamic_cast only works with polymorphic types (classes with virtual functions).
  7. If the cast succeeds, we print the arg and message from the ArgError.

Note that C++ exception handling is typically used for error handling, but this example demonstrates how to create a custom error type and use it in a way similar to Go’s error handling.

To compile and run this program:

$ g++ -std=c++17 custom_errors.cpp -o custom_errors
$ ./custom_errors
42
can't work with it

This example shows how C++ can implement custom error types and provide similar functionality to Go’s error handling, albeit with some differences due to the language’s design.