How to Implement Custom Exception Translators in Catch2

Catch2 provides the CATCH_TRANSLATE_EXCEPTION macro to register functions that convert user-defined C++ exceptions into human-readable strings for test output.

Catch2 is a modern, header-only C++ testing framework that enables developers to implement custom exception translators for domain-specific error types. When tests throw user-defined exceptions, these translators ensure the framework displays descriptive messages rather than cryptic failure reports. The mechanism relies on static registration via macros and a centralized registry that processes exceptions at runtime.

How Exception Translation Works in Catch2

The exception translation system in Catch2 operates through three coordinated components defined in the src/catch2/ directory.

The Core Components

CATCH_TRANSLATE_EXCEPTION macro – Defined in [catch_translate_exception.hpp](https://github.com/catchorg/Catch2/blob/devel/src/catch2/catch_translate_exception.hpp), this macro generates a static function with your custom signature and automatically registers it with the framework during program startup.

ExceptionTranslatorRegistrar – Located in the same header (lines 55‑61), this template class wraps your translation function inside an IExceptionTranslator implementation and passes it to the global registry.

ExceptionTranslatorRegistry – Implemented in [catch_exception_translator_registry.hpp](https://github.com/catchorg/Catch2/blob/devel/src/catch2/internal/catch_exception_translator_registry.hpp), this class maintains a list of all registered translators. When a test throws an exception, the registry's translateActiveException() method iterates through translators until one successfully converts the exception to a std::string.

The registration occurs during static initialization, ensuring translators are available before any test cases execute. If you compile with CATCH_CONFIG_DISABLE, the macro expands to a plain function definition without registration overhead.

Implementing a Custom Exception Translator

To implement custom exception translators in your test suite, define your exception class, register a translation function, and write tests that trigger the exceptions.

Define Your Exception Class

Create a custom exception that inherits from std::runtime_error or implements the standard exception interface.

// my_exceptions.hpp
#pragma once
#include <stdexcept>
#include <string>

class MyError : public std::runtime_error {
public:
    explicit MyError( std::string const& msg ) 
        : std::runtime_error(msg) {}
};

Register the Translator

Include catch_translate_exception.hpp and use the CATCH_TRANSLATE_EXCEPTION macro to register a function that converts your exception to a string.

// translator.cpp
#include <catch2/catch_translate_exception.hpp>
#include "my_exceptions.hpp"

CATCH_TRANSLATE_EXCEPTION( MyError const& e ) {
    return std::string("MyError: ") + e.what();
}

The macro automatically generates the boilerplate to register this function with the ExceptionTranslatorRegistry at static initialization time.

Write Tests That Use the Translator

When tests throw MyError, Catch2 catches the exception, invokes your translator, and displays the resulting string in the failure output.

// test_my_error.cpp
#include <catch2/catch_test_macros.hpp>
#include "my_exceptions.hpp"

TEST_CASE( "custom exception translation", "[exception]" ) {
    REQUIRE_THROWS_AS( 
        throw MyError("something went wrong"), 
        MyError 
    );
}

Running this test produces output like:

================================================================================
MyError: something went wrong
================================================================================

Working with Non-Standard Exception Types

The translation mechanism works for any C++ type, not just standard exceptions. You can implement custom exception translators for structs or primitive wrappers without inheritance.

struct Foo { int id; };

CATCH_TRANSLATE_EXCEPTION( Foo const& f ) {
    return "Foo with id = " + std::to_string(f.id);
}

When a test throws Foo, the registry invokes this translator and displays the formatted ID in the test report.

Key Implementation Files

Understanding the source structure helps when debugging translation issues:

Summary

  • Use the CATCH_TRANSLATE_EXCEPTION macro to register translation functions that convert custom exceptions to std::string representations.
  • Registration happens automatically at static initialization time via ExceptionTranslatorRegistrar before tests run.
  • The ExceptionTranslatorRegistry iterates through registered translators when translateActiveException() is called, stopping at the first successful match.
  • Non-standard types are supported – any C++ type can be translated, not just std::exception derivatives.
  • Conditional compilation respects CATCH_CONFIG_DISABLE, expanding the macro to a plain function when exceptions are disabled.

Frequently Asked Questions

What happens if no translator matches my exception type?

If the ExceptionTranslatorRegistry exhausts all registered translators without a match, Catch2 falls back to default exception handling. For types inheriting from std::exception, it displays the what() message; for other types, it shows a generic "unknown exception" message according to the implementation in catch_exception_translator_registry.hpp.

Can I register multiple exception translators in the same test executable?

Yes. Each CATCH_TRANSLATE_EXCEPTION invocation generates a distinct registrar object, and the ExceptionTranslatorRegistry maintains a vector of all translators. The system attempts translation in registration order, returning the first successful result. You should register translators for specific derived types before base types to ensure precise matching.

Does exception translation work when exceptions are disabled?

When you define CATCH_CONFIG_DISABLE, the CATCH_TRANSLATE_EXCEPTION macro expands to only the function definition without the registration code. The translator function remains available for manual invocation, but Catch2 will not automatically catch and translate exceptions during test execution since exception handling is disabled.

Where should I place translator registration code?

Place CATCH_TRANSLATE_EXCEPTION definitions in .cpp files rather than headers to avoid multiple definition errors. Since registration occurs during static initialization, you can place translators in any compilation unit linked into your test binary. Ensure you include catch_translate_exception.hpp and link against the Catch2 library or include the amalgamated header.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →