# Using absl::MakeCheckOpString for Custom CHECK Macros in Abseil

> Learn to create custom CHECK macros with absl::MakeCheckOpString. Generate formatted failure messages showing operand values for type-safe, low-overhead logging in Abseil.

- Repository: [Abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)
- Tags: how-to-guide
- Published: 2026-07-16

---

**Use `absl::MakeCheckOpString` from [`absl/log/internal/check_op.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/log/internal/check_op.h) to generate formatted failure messages that display operand values, enabling custom CHECK macros with the same type-safe formatting and low-overhead characteristics as the built-in Abseil logging checks.**

When building custom assertion macros in C++, formatting rich failure messages that display actual operand values requires significant template machinery. The **abseil-cpp** library exposes `absl::MakeCheckOpString`, a utility function that powers the standard `CHECK_EQ`, `CHECK_NE`, and related macros, allowing developers to extend the CHECK family with custom logic while leveraging Abseil's optimized type detection and string formatting infrastructure.

## How absl::MakeCheckOpString Works

The `absl::MakeCheckOpString` function is the core string-building routine that generates the `expr (value1 vs. value2)` message when a CHECK fails. Implemented in [[`absl/log/internal/check_op.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/log/internal/check_op.h)](https://github.com/abseil/abseil-cpp/blob/master/absl/log/internal/check_op.h) at lines 326-344, it is deliberately marked **`ABSL_ATTRIBUTE_NOINLINE`** to prevent code bloat in hot paths, with common instantiations declared `extern` (lines 46-66) to further reduce binary size.

### The Three Core Components

The implementation relies on three cooperating parts:

- **`CheckOpMessageBuilder`** (lines 199-209): Holds a temporary `std::ostringstream` and concatenates the expression text, opening parenthesis, the two formatted values, and the closing parenthesis.
- **`MakeCheckOpValueString`** (lines 215-226): Streams a single value to the builder using the best available overload, prioritizing `operator<<`, then `AbslStringify`, or falling back to a placeholder.
- **`MakeCheckOpString`** (lines 326-344): Orchestrates the process by detecting whether operands are printable, invoking the builder, and returning a `const char*` suitable for the logging infrastructure.

### Type Detection and Template Optimization

Heavy lifting for type resolution occurs in the `detect_specialization` namespace (lines 360-447). This machinery resolves ambiguous overloads, promotes integral types to 64-bit variants, and substitutes `UnprintableWrapper` for types lacking streamable representations. Instead of an O(n²) template explosion for every `(T, U)` pair, only distinct printable categories are instantiated, dramatically reducing compilation times and binary sizes.

## Implementing a Custom CHECK Macro

To create a custom CHECK macro like `MY_CHECK`, you must wrap `MakeCheckOpString` in a template helper and follow the Abseil pattern of using a `while` loop with `GetReferenceableValue` to safely handle operands.

First, include the internal header and define a forwarder function:

```cpp
#include "absl/log/internal/check_op.h"

namespace mylog {

template <typename T1, typename T2>
inline const char* MyCheckImpl(const T1& v1, const T2& v2,
                               const char* exprtext) {
  // Re-use the existing MakeCheckOpString machinery.
  return ::absl::log_internal::MakeCheckOpString(v1, v2, exprtext);
}

// Provide an overload for ints to avoid the unnamed-enum issue.
inline const char* MyCheckImpl(int v1, int v2, const char* exprtext) {
  return MyCheckImpl<int, int>(v1, v2, exprtext);
}

}  // namespace mylog

```

Next, define the macro following the `ABSL_LOG_INTERNAL_CHECK_OP` pattern:

```cpp
#define MY_CHECK(op, v1, v2)                                           \
  while (const char* _absl_chk_res =                                   \
             mylog::MyCheckImpl(::absl::log_internal::GetReferenceableValue(v1), \
                               ::absl::log_internal::GetReferenceableValue(v2), \
                               "MY_CHECK: " #v1 " " #op " " #v2))    \
    ::absl::log_internal::ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, true) \
  ::absl::log_internal::ABSL_LOG_INTERNAL_CHECK(_absl_chk_res).InternalStream()

```

The `while` loop ensures the check is evaluated once, and `GetReferenceableValue` prevents dangling references to temporary objects. The macro only builds the error string when the condition evaluates to true (the check fails).

### Usage Example

```cpp
int a = 5, b = 10;
MY_CHECK(<, a, b);   // Succeeds silently
MY_CHECK(==, a, b);  // Fails with: MY_CHECK: a == b (5 vs. 10)

```

This automatically supports any type that implements `operator<<`, `AbslStringify`, or Abseil's custom streaming protocols.

## Key Files and Dependencies

Extending CHECK macros requires understanding these internal components:

- **[`absl/log/internal/check_op.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/log/internal/check_op.h)**: Contains `MakeCheckOpString`, `CheckOpMessageBuilder`, and the type detection logic.
- **[`absl/log/internal/nullstream.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/log/internal/nullstream.h)**: Provides `NullStream` for code paths where logging is disabled.
- **[`absl/log/internal/nullguard.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/log/internal/nullguard.h)**: Guards values before streaming to prevent use-after-free scenarios.
- **[`absl/strings/has_ostream_operator.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/has_ostream_operator.h)**: Metaprogramming utilities to detect `operator<<` support.
- **[`absl/strings/has_absl_stringify.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/has_absl_stringify.h)**: Detects `AbslStringify` support for user-defined types.

## Summary

- **`absl::MakeCheckOpString`** in [`absl/log/internal/check_op.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/log/internal/check_op.h) generates failure messages showing operand values when CHECK conditions fail.
- The implementation uses **`CheckOpMessageBuilder`** and **`MakeCheckOpValueString`** to handle formatting, with **`detect_specialization`** logic to minimize template bloat.
- Custom macros must follow the `while` loop pattern with **`GetReferenceableValue`** and **`ABSL_LOG_INTERNAL_CONDITION_FATAL`** to maintain safety and performance characteristics.
- The system supports any printable type through `operator<<` or `AbslStringify`, with graceful degradation for unprintable types.

## Frequently Asked Questions

### What is the performance impact of using MakeCheckOpString?

The function is marked `ABSL_ATTRIBUTE_NOINLINE` to keep the fast path (successful checks) free of string formatting code. Common instantiations are declared `extern` (lines 46-66) to avoid duplicate template instantiations across translation units, minimizing binary size while preserving rich error messages only when assertions fail.

### How does MakeCheckOpString handle types that cannot be streamed?

When a type lacks both `operator<<` and `AbslStringify` support, the `detect_specialization` machinery (lines 360-447) substitutes an **`UnprintableWrapper`** that renders a placeholder string. This prevents compilation failures while still logging the expression text and the fact that values could not be displayed.

### Why is GetReferenceableValue used in CHECK macros?

**`GetReferenceableValue`** ensures that temporary objects and expressions are properly bound before being passed to the comparison and formatting functions. This prevents dangling references and use-after-free bugs that could occur if operands were evaluated multiple times or stored as references to temporaries.

### Can I use MakeCheckOpString with custom comparison operators?

Yes. `MakeCheckOpString` is agnostic to the comparison logic itself—it only formats the operands and expression text when the check fails. You can implement any custom predicate (e.g., checking floating-point tolerance) and use `MakeCheckOpString` to generate the diagnostic message when your predicate returns false.