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_TRYexpands totrywhen exceptions are enabled, otherwiseif (true)ABSL_INTERNAL_CATCH_ANYexpands tocatch (...)when enabled, otherwiseelse if (false)ABSL_INTERNAL_RETHROWexpands tothrow;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::BadStatusOrAccessif 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.hhelpers instead of rawthrowstatements in headers or library code that may be compiled with-fno-exceptions. - Wrap
try/catchwithABSL_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()onStatusOrobjects unless you intend to propagate the error via exception.
Summary
- Abseil detects exception support automatically via the
ABSL_HAVE_EXCEPTIONSmacro defined inabsl/base/config.h. - Use throwing helpers like
absl::ThrowStdInvalidArgumentfromabsl/base/throw_delegate.hto safely throw standard exceptions even when compiling with-fno-exceptions. - Write portable exception handlers using
ABSL_INTERNAL_TRY,ABSL_INTERNAL_CATCH_ANY, andABSL_INTERNAL_RETHROWfromabsl/base/macros.h. - Handle errors with
absl::StatusOr<T>to return values or statuses explicitly, noting that.value()throwsabsl::BadStatusOrAccessonly when exceptions are enabled. - Never call
throwdirectly 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →