Language[lang]

assert in C++: how assert and static_assert work

Section
Language [lang]
Standard
C++17
Compilers
MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
Published
Length
6 min read
In this answer7 clauses
  1. 1What does a failed assert look like?
  2. 2How do you add a message to an assert?
  3. 3How do you disable assert in C++ (NDEBUG)?
  4. 4When should you not use assert?
  5. 5What is static_assert in C++?
  6. 6What do contracts change in C++26?
  7. 7assert, static_assert, exception or if?

Abstract

assert in C++ is a macro from <cassert> that checks a condition while the program runs: when the condition is false, it prints the condition, the file and the line, then ends the program with std::abort. Defining NDEBUG, as CMake's Release configuration does, removes every assert, so no work the program needs may sit inside one. static_assert checks at compile time instead: a false condition stops the build, and NDEBUG does not touch it.

A straight path seen from above as two long parallel lines. Halfway along, two hatched posts narrow it to a gap closed by a blue bar. On the left, a square with an arrow leading up to the bar.
Figure 1 — A check the program must pass.

assert in C++ is a macro from <cassert> that checks a condition while the program runs: assert(level <= 10); does nothing when the condition is true, and when it is false prints the condition, the file and the line, then stops the program. Defining NDEBUG, as release builds usually do, removes every assert. static_assert checks at compile time instead.

A volume control that accepts 0 to 10, as a whole program:

[Example 1:
#include <cassert>
#include <iostream>

void set_volume(int level) {
    assert(level >= 0 && level <= 10);
    std::cout << "volume set to " << level << '\n';
}

int main() {
    set_volume(7);  // prints "volume set to 7"
}

— end example]

It printed volume set to 7: the condition was true, so the assert did nothing.

The samples are C++17 (the one on contracts C++26), built with MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2 and Clang 23.1 (libc++). Apart from contracts, all four behaved the same; only the wording of a failure differs. The samples whose assert fails were built as volume.cpp, the file name their messages show. Since When lists, for each standard, the first compiler releases that support its features.

What does a failed assert look like?

A failed assert writes one line to standard error, the output meant for error messages, naming the condition, the file and the line. Then it calls std::abort, which ends the program at once. Call the function above with 11 instead, set_volume(11);, and GCC 16.2 and Clang 23.1, both on Linux, printed:

[Example 2:
output.s: volume.cpp:5: void set_volume(int): Assertion `level >= 0 && level <= 10' failed.

— end example]

The line starts with the program’s name, here output.s. volume set to 11 never appeared: the program stopped at the assert. On Windows, MSVC and Clang 19.1 (both on Microsoft’s runtime library) print the condition, the file and the line, without the function name: Assertion failed: level >= 0 && level <= 10, file volume.cpp, line 5. Linked with Microsoft’s debug runtime library, a failed assert opens a dialog box with Abort, Retry and Ignore buttons; Retry opens the debugger (Microsoft’s assert page).

How do you add a message to an assert?

Join a string literal to the condition with &&. A string literal counts as true, so the check is unchanged, and the text shows up in the failure message:

[Example 3:
void set_volume(int level) {
    assert(level <= 10 && "volume goes from 0 to 10");
    std::cout << "volume set to " << level << '\n';
}

— end example]

Called with 11, MSVC printed Assertion failed: level <= 10 && "volume goes from 0 to 10", file volume.cpp, line 5. All four printed the text, quotes included, and none warned about the &&. assert takes no message argument of its own, so this is the portable way to add one.

How do you disable assert in C++ (NDEBUG)?

Define the macro NDEBUG before <cassert> is included. Every assert after it becomes ((void)0), an expression that does nothing, and its condition is never evaluated:

[Example 4:
#define NDEBUG  // before <cassert>: every assert below is removed
#include <cassert>
#include <iostream>

void set_volume(int level) {
    assert(level >= 0 && level <= 10);  // gone: nothing is checked
    std::cout << "volume set to " << level << '\n';
}

int main() {
    set_volume(11);  // prints "volume set to 11"
}

— end example]

It printed volume set to 11 on all four.

You rarely write the #define yourself. The build passes NDEBUG on the compiler’s command line instead: -DNDEBUG for GCC and Clang, /DNDEBUG for MSVC. CMake adds it in its Release, RelWithDebInfo and MinSizeRel configurations, as its default flags for GCC and for MSVC show; its Debug configuration leaves NDEBUG out. That is why an assert that fired while you tested seems to stop working in a Release build: it is no longer there.

Three stacked boxes joined by arrows: source code, compiler, running program. Compiler: static_assert is checked here; if false, the build stops. Running program: assert is checked here; if false, the program stops; greyed, with NDEBUG, assert is removed.
Figure 2 — static_assert is checked when the program is built, assert while it runs, and NDEBUG removes assert altogether.

Because the whole assert(...) disappears, condition included, never put work the program needs inside one:

[Example 5:
int main() {
    int attempts = 0;
    assert(++attempts > 0);  // wrong: the counting is inside the assert
    std::cout << "attempts: " << attempts << '\n';
}

— end example]

Built as it stands, it printed attempts: 1. Built with -DNDEBUG (/DNDEBUG on MSVC), the same code printed attempts: 0, and no compiler warned. Do the work on its own line, ++attempts;, and assert on the result: assert(attempts > 0);.

When should you not use assert?

Do not use it for what can go wrong in a correct program, such as a missing file or a number the user typed. Check those with an if or an exception, caught with try and catch, because the check must still be there in a release build.

An assert is for a mistake in the code: a condition that cannot be false if the program is written correctly. If only your own code calls set_volume, a level of 11 is a bug and the assert is right; if the level comes from a keypress, 11 is input to reject with an if.

What is static_assert in C++?

static_assert is a check the compiler makes while it builds the program: the condition must be something the compiler can work out, such as a size, and when it is false the build stops with your message. It costs nothing when the program runs, NDEBUG does not remove it, and it needs no header. Since C++17 the message is optional (static_assert on cppreference):

[Example 6:
constexpr int max_volume = 10;

static_assert(sizeof(int) >= 4,
              "this code needs an int of at least 32 bits");
static_assert(max_volume > 0);  // no message: allowed since C++17

— end example]

Both checks passed on all four, where int has 4 bytes. When the condition is false, there is no program to run:

[Example 7:
static_assert(sizeof(int) == 8, "this code needs a 64-bit int");

— end example]

GCC stopped the build with your text in its error:

[Example 8:
error: static assertion failed: this code needs a 64-bit int

— end example]

All four put the text in their error. Because the check happens while the program is built, a static_assert cannot test a value known only when it runs, such as level: that is assert’s job.

What do contracts change in C++26?

C++26 adds contract assertions: contract_assert(condition); checks a condition inside a function, as assert does, and pre and post attach conditions to a function’s declaration. Of the four compilers here, only GCC 16.2 built it, with -std=c++26 and -lstdc++exp added to link the program; Clang 23.1 and MSVC 19.44 do not have it yet:

[Example 9:
void set_volume(int level) {
    contract_assert(level <= 10);  // C++26: a contract assertion
    std::cout << "volume set to " << level << '\n';
}

— end example]

Called with 11, the program GCC built stopped with a contract violation message naming the function, the file and the line.

assert, static_assert, exception or if?

Pick by what you are checking and when the check has to run:

You are checking Use Checked
A fact known at compile time, such as a size or a type static_assert when the program is built
A bug in your own code’s logic assert while it runs, unless NDEBUG is defined
A failure the caller should handle an exception while it runs, in every build
Input that may simply be wrong if while it runs, in every build

The Language section has the other answers on the core language.

Questions

Which header is assert in?

assert is in <cassert>: write #include <cassert> at the top of the file that uses it. Because assert is a macro, it is written without std::. static_assert needs no header at all, because it is part of the language itself.

Is assert a function or a macro in C++?

A macro, defined in <cassert>. That is why it has no std:: in front of it, and why NDEBUG can remove it: with NDEBUG defined, every assert(...) is replaced by ((void)0), an expression that does nothing, before the code is compiled. The condition inside is then never evaluated.

Does assert work in release builds?

Not when NDEBUG is defined, and release builds usually define it: CMake’s Release configuration adds -DNDEBUG with GCC and /DNDEBUG with MSVC. Every assert then disappears, condition included. A check that must stay in the program you ship belongs in an if or an exception.

What is the difference between assert and static_assert?

assert checks a condition while the program runs and stops the program when it is false; NDEBUG removes it. static_assert checks a condition the compiler can work out, such as sizeof(int), while it builds the program, and stops the build when it is false; NDEBUG does not affect it.

Why does assert not compile with a comma in the condition?

Because assert is a macro, and a macro's argument is split at every comma that is not inside parentheses, so assert(std::is_same_v<int, int>) passes it two arguments. All four compilers used here rejected it, as C++17 and as C++26. Doubled parentheses, assert((std::is_same_v<int, int>)), compiled and passed on all four. C++26 lets assert take the comma, but all four still rejected it in their C++26 modes.

What happens when an assert fails?

The program writes the condition, the file and the line to standard error, then calls std::abort, which ends it at once and without the usual cleanup: the destructors of local and static objects do not run. Nothing after the failed assert executes.

Sources

  1. assertcppreference.com
  2. [assertions] AssertionsC++ standard working draft
  3. std::abortcppreference.com
  4. static_assert declarationcppreference.com
  5. Contract assertionscppreference.com
  6. Modules/Compiler/GNU.cmakeKitware, CMake source
  7. Modules/Platform/Windows-MSVC.cmakeKitware, CMake source
  8. assert Macro, _assert, _wassertMicrosoft Learn

Read next

  1. C++ try catch: how to throw and catch exceptionsLanguage · Miriam Castellanos · written for C++17
  2. Polymorphism in C++: one call, many behaviorsLanguage · Miriam Castellanos · written for C++17
  3. using namespace std: what it does and what to write insteadLanguage · Miriam Castellanos · written for C++17