Sitemap
Towards Dev

A publication for sharing projects, ideas, codes, and new theories.

C++26 <debugging>: A Standard Way to Break Into the Debugger

C++26 introduces three standard debugging functions to replace compiler-specific breakpoint hacks and simplify debugging in C++.

6 min readOct 8, 2026

--

Press enter or click to view image in full size

One small problem I’ve run into in almost every C++ project that i have worked on is breaking into the debugger programmatically.

Before C++26, there wasn’t a single portable way to do it. Depending on your compiler, you might use __debugbreak(), __builtin_debugtrap(), int3, or some other platform-specific trick. If you've ever looked at the source code for massive projects like Unreal Engine, Catch2, or EASTL, you'll see they all maintain custom macros to handle this.

Eventually, most codebases end up with a messy, global macro file that looks something like this:

#ifdef _MSC_VER
__debugbreak();
#elif defined(__clang__)
__builtin_debugtrap();
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
__asm__ volatile("int3");
#else
raise(SIGTRAP);
#endif

The code itself isn’t particularly complicated. The annoying part is having to maintain it across different architectures and platforms.

Now that C++26 is here, it finally gives us a standard way to handle this. Thanks to P2546R5 (Debugging Support) and a later refinement in P2810R4 (is_debugger_present is replaceable), we have the new <debugging> header:

#include <debugging>

std::breakpoint();
std::is_debugger_present();
std::breakpoint_if_debugging();

Three small functions. No compiler-specific intrinsics in your application code.

Here is a quick look at what this replaces:

Press enter or click to view image in full size

And here is a visual breakdown of the shift:

Press enter or click to view image in full size

Let’s see what these functions actually do.

The Three Functions

The new <debugging> header provides three functions inside the std namespace:

#include <debugging>

namespace std {
void breakpoint() noexcept;
bool is_debugger_present() noexcept;
void breakpoint_if_debugging() noexcept;
}

They have fairly obvious names:

Press enter or click to view image in full size

All three are noexcept. They are also available to freestanding environments (like bare-metal microcontrollers), which also makes them useful beyond normal desktop applications.

Keep in mind that these are runtime functions, not constexpr.

std::breakpoint()

This is the most direct one.

#include <debugging>

void process_request()
{
// Something went seriously wrong.
std::breakpoint();
}

When execution reaches std::breakpoint(), it requests a breakpoint. The exact mechanism is implementation-defined. On x86, an implementation may use the int3 instruction, but your application code no longer needs to care about those details.

What happens without a debugger?

This part is improtant. std::breakpoint() does not mean: "Break if a debugger exists, otherwise continue."

The behavior when no debugger is attached is implementation-defined. On a system where the breakpoint results in a trap or signal (like SIGTRAP on POSIX), the program will likely terminate if the trap isn't handled.

So, if your goal is specifically: “Stop here when I’m debugging, but don’t deliberately trap the program when I’m not,” then you want std::breakpoint_if_debugging().

std::is_debugger_present()

Sometimes you don’t want to halt execution at all. You just want to know if someone is watching.

That’s what std::is_debugger_present() is for:

void print_diagnostics()
{
if (std::is_debugger_present()) {
std::cout << "[DEBUG] Extra internal state info...\n";
}
}

There are a few other places where this can be useful. For example, suppose your application has a timeout for network requests. When you’re stepping through the code in a debugger, an operation that normally takes milliseconds can easily trigger a timeout while you’re reading variables.

You can account for that:

auto timeout = std::chrono::seconds(2);

// Relax the timeout if a developer is stepping through the code.
if (std::is_debugger_present()) {
timeout = std::chrono::seconds(300);
}

std::is_debugger_present() reports whether a debugger is present at the time it is called. A debugger can attach to or detach from a process while it is running, so it is evaluated at runtime.

The standard defines what the function tells you, but leaves the underlying implementation up to the OS. Windows uses the Win32 IsDebuggerPresent() API, while POSIX might check for a tracer parent process.

std::is_debugger_present() can be replaced

There is one more detail worth knowing and i really liked the design appraoch here.

For normal OS-hosted programs, the standard library handles checking for the debugger. But in a freestanding environment, it is often impossible for the standard library to know what debugging environment or hardware is attached.

To solve this, std::is_debugger_present() is specified as a replaceable function, which allows a freestanding implementation to provide its own debugger-detection mechanism.

This ensures that even if you’re on a proprietary platform, you don’t have to abandon the standard <debugging> header. Your toolchain can plug in its own detection logic, and std::breakpoint_if_debugging() will use it seamlessly.

Get Sagar’s stories in your inbox

Join Medium for free to get updates from this writer.

Note:
I will create a separate write-up with example how to do this. For now let’s keep things simple.

std::breakpoint_if_debugging()

This is probably the function you’ll use most often. std::breakpoint_if_debugging(); Conceptually, it does exactly this:

if (std::is_debugger_present()) {
std::breakpoint();
}

Here is how the logic flows:

When you’re running the application normally, the program doesn’t trap on an error. But when you are debugging, it stops at the exact point where the problem was detected, without you having to manually set a breakpoint. That makes it useful for assertions, tests, and error handling.

Replacing DEBUG_BREAK()

You might still keep your project’s custom macro if you want application-specific policies (like entirely disabling breakpoints in Release builds):

#include <debugging>

#ifndef NDEBUG
#define DEBUG_BREAK() std::breakpoint_if_debugging()
#else
#define DEBUG_BREAK() ((void)0)
#endif

The difference is that now the macro is purely about your project’s policy. It no longer needs to carry compiler-specific code.

Here is A small assertion helper for your reference

Here’s a small example combining <debugging> with C++20's std::source_location.

#include <debugging>
#include <iostream>
#include <source_location>
#include <string_view>

void debug_assert(
bool condition,
std::string_view expression,
std::source_location location = std::source_location::current())
{
if (!condition) {
std::cerr
<< "Assertion failed: " << expression
<< " at " << location.file_name()
<< ':' << location.line() << '\n';

std::breakpoint_if_debugging();
}
}

Now you can write:

debug_assert(ptr != nullptr, "Pointer must not be null");
debug_assert(size > 0, "Size must be positive");

If the assertion fails in your IDE, execution halts exactly inside debug_assert, giving you an instant stack trace.

Now that we’ve covered the basics, let’s talk about when to use these functions — and when not to.

Where can you use it?

Test frameworks:
A testing framework can report a failure and immediately pause:

if (!test_passed) {
report_failure();
std::breakpoint_if_debugging();
}

Instead of digging through console output to figure out which test failed, your debugger just takes you there directly.

Error handling:
It can also be useful when something unexpected happens in low-level code:

if (state == invalid_state) {
log_error("Invalid state detected. Recovering...");

std::breakpoint_if_debugging();

return fallback_value;
}

When not to use these functions?

There are a few things I’d keep in mind.

Don’t use debugger detection for security: Don’t treat this:

if (std::is_debugger_present()) {
// Security decision
}

as a security mechanism. Debugger detection in C++26 is intended strictly for diagnostics and debugging. A determined attacker can bypass such checks, so it shouldn’t be used as an anti-tampering mechanism.

Don’t put it in a hot loop(Very Improtant): The function still needs to determine whether a debugger is present (often querying the OS). That takes some amount of time.

for (size_t i = 0; i < 1'000'000'000; ++i) {
if (something_is_wrong()) {
std::breakpoint_if_debugging();
}
}

It is perfectly fine for error paths, but keep it out of tight, latency-critical happy-paths.

What about assert()?

You might be thinking: Why not just use assert()?
You can. For example: assert(ptr != nullptr);
But assert() and std::breakpoint_if_debugging() are solving different problems.

  • assert() is an assertion facility that typically terminates the program if the condition is false.
  • std::breakpoint_if_debugging() is specifically about stopping execution when a debugger is attached. Your application still controls what happens after the breakpoint.

Final thoughts

<debugging> isn't a huge C++26 feature. It doesn't change how we design applications.

But it fixes a small problem that has been around for a long time. It replaces fragile #ifdef blocks with a standard interface, and gives implementations the power to override it when necessary.

The three functions are easy to remember:

  • std::breakpoint() – Break here.
  • std::is_debugger_present() – Is a debugger attached?
  • std::breakpoint_if_debugging() – Break here, but only if a debugger is attached.

For most application code, that last one is probably the most useful. One less macro to maintain — and that’s a nice improvement for such a small C++26 feature.

Found this article helpful? Please Clap 👏 generously and follow for more C++ and system programming content.

--

--