How to Use Abseil C++ Exceptions: Portable Exception Handling for Library Authors

Abseil C++ exceptions are managed through centralized throwing helpers in absl/base/throw_delegate.h, portable exception-aware macros in absl/base/macros.h, and the absl::StatusOr<T> value-or-error wrapper, enabling your code to compile and execute safely with or without -fno-exceptions.

Abseil is designed to function correctly in environments where C++ exceptions are disabled. The abseil/abseil-cpp repository provides abstraction mechanisms that detect exception support at compile time and adapt behavior accordingly. Understanding these facilities ensures your library code remains portable across different build configurations.

Detecting Exception Support with ABSL_HAVE_EXCEPTIONS

Abseil automatically detects whether exception handling is available via the ABSL_HAVE_EXCEPTIONS preprocessor macro. This definition is controlled by your build system (Bazel or CMake) based on compiler flags and is declared in absl/base/config.h.

When ABSL_HAVE_EXCEPTIONS is undefined—typically when compiling with -fno-exceptions—Abseil substitutes safe alternatives that avoid throwing. You can inspect this macro to conditionally compile exception-specific logic, though the Abseil macros handle this automatically.

Throwing Standard Exceptions with throw_delegate.h

Never call throw directly in code that might be compiled without exception support. Instead, use the exception-throwing helpers declared in absl/base/throw_delegate.h. These functions are marked [[noreturn]] and delegate to standard library exception types.

Available helpers include:

  • absl::ThrowStdInvalidArgument(const std::string&)
  • absl::ThrowStdOutOfRange(const std::string&)
  • absl::ThrowStdRuntimeError(const std::string&)
  • absl::ThrowStdLogicError(const std::string&)

When exceptions are enabled, these functions throw the corresponding std:: exception. When disabled, they call std::terminate() via the implementation in throw_delegate.cc.

#include "absl/base/throw_delegate.h"

void ValidateIndex(int idx, int size) {
  if (idx < 0 || idx >= size) {
    // Throws std::out_of_range, or terminates if exceptions are disabled.
    absl::ThrowStdOutOfRange("index out of range");
  }
}

Writing Portable Exception Handlers with Abseil Macros

To write try/catch blocks that compile regardless of exception settings, use the exception-aware macros defined in absl/base/macros.h:

  • ABSL_INTERNAL_TRY expands to try when exceptions are enabled, otherwise if (true)
  • ABSL_INTERNAL_CATCH_ANY expands to catch (...) when enabled, otherwise else if (false)
  • ABSL_INTERNAL_RETHROW expands to throw; when enabled, otherwise a no-op
#include "absl/base/macros.h"
#include "absl/base/throw_delegate.h"
#include <iostream>

void ProcessWithFallback(bool trigger_error) {
  ABSL_INTERNAL_TRY {
    if (trigger_error) {
      absl::ThrowStdRuntimeError("processing failed");
    }
    std::cout << "Success\n";
  } ABSL_INTERNAL_CATCH_ANY {
    std::cout << "Error caught\n";
    ABSL_INTERNAL_RETHROW;  // Only rethrows if exceptions are enabled.
  }
}

When ABSL_HAVE_EXCEPTIONS is undefined, the ABSL_INTERNAL_TRY block becomes a simple if (true) statement with zero runtime overhead for exception handling.

Handling Errors with absl::StatusOr<T> and BadStatusOrAccess

For functions that may fail, prefer returning absl::StatusOr<T> from absl/status/statusor.h instead of throwing. This wrapper holds either a value of type T or an absl::Status error.

The .value() accessor behaves differently based on exception settings:

  • With exceptions: Throws absl::BadStatusOrAccess if the status is not OK.
  • Without exceptions: Calls std::terminate().
#include "absl/status/statusor.h"
#include <string>
#include <iostream>

absl::StatusOr<int> ParseInt(std::string_view s) {
  try {
    return std::stoi(std::string(s));
  } catch (const std::invalid_argument&) {
    return absl::InvalidArgumentError("not an integer");
  }
}

void UseValue() {
  auto result = ParseInt("42");
  // Check status explicitly to avoid throwing/terminating.
  if (!result.ok()) {
    std::cerr << "Failed: " << result.status() << '\n';
    return;
  }
  // Safe to access value here.
  int v = result.value();
  std::cout << "Value: " << v << '\n';
}

Always call result.ok() before accessing .value() if you want to avoid the exception or termination path.

Best Practices for Exception-Safe Abseil Code

Follow these guidelines when working with Abseil C++ exceptions:

  • Use throw_delegate.h helpers instead of raw throw statements in headers or library code that may be compiled with -fno-exceptions.
  • Wrap try/catch with ABSL_INTERNAL_* macros to maintain compatibility across different build configurations.
  • Prefer absl::StatusOr<T> over exceptions for recoverable errors and expected failure modes.
  • Reserve exceptions for unrecoverable logic errors or contract violations where program termination is acceptable.
  • Check result.ok() before calling .value() on StatusOr objects unless you intend to propagate the error via exception.

Summary

  • Abseil detects exception support automatically via the ABSL_HAVE_EXCEPTIONS macro defined in absl/base/config.h.
  • Use throwing helpers like absl::ThrowStdInvalidArgument from absl/base/throw_delegate.h to safely throw standard exceptions even when compiling with -fno-exceptions.
  • Write portable exception handlers using ABSL_INTERNAL_TRY, ABSL_INTERNAL_CATCH_ANY, and ABSL_INTERNAL_RETHROW from absl/base/macros.h.
  • Handle errors with absl::StatusOr<T> to return values or statuses explicitly, noting that .value() throws absl::BadStatusOrAccess only when exceptions are enabled.
  • Never call throw directly in header-only or library code that must support exception-disabled builds.

Frequently Asked Questions

What happens if I call absl::ThrowStdOutOfRange when exceptions are disabled?

When exceptions are disabled, absl::ThrowStdOutOfRange and other helpers in absl/base/throw_delegate.h call std::terminate() instead of throwing std::out_of_range. This ensures the program exits safely rather than entering undefined behavior, maintaining a consistent failure mode across build configurations.

How does ABSL_INTERNAL_CATCH_ANY work without exceptions?

The ABSL_INTERNAL_CATCH_ANY macro expands to else if (false) when ABSL_HAVE_EXCEPTIONS is undefined. This makes the catch block dead code that the compiler optimizes away, eliminating any runtime overhead while preserving source compatibility with exception-enabled builds.

Should I use absl::StatusOr<T> or exceptions for error handling in Abseil?

Prefer absl::StatusOr<T> for recoverable errors and expected failure modes, as it works identically with or without exceptions enabled. Reserve exceptions thrown via absl/base/throw_delegate.h for unrecoverable logic errors or contract violations where program termination is an acceptable alternative.

Where is the ABSL_HAVE_EXCEPTIONS macro defined?

The ABSL_HAVE_EXCEPTIONS macro is defined in absl/base/config.h based on compiler feature detection. It is set automatically by Abseil's build configuration (Bazel or CMake) when the compiler supports exceptions, allowing the library to adapt its behavior without manual intervention.

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 →