Assert and Constexpr in C++11

Here’s a simple question: in C++11 what’s the best way to put debug checks in constexpr functions? Since assert is not constexpr, the obvious doesn’t work:

constexpr bool in_range(int val, int min, int max)
{
    assert(min <= max); // OOPS, not constexpr
    return min <= val && val <= max;
}

In C++14 this is OK, but in code that must be portable to C++11 it’s not. This is a known problem, and the recommended solution is to use a throw expression on failure. This totally non-obvious solution has the neat effect of causing a compile-time error when the arguments are known at compile-time:

constexpr bool in_range(int val, int min, int max)
{
    return (min <= max)
      ? min <= val && val <= max
      : throw std::logic_error("Assertion failed!");
}

constexpr bool res1 = in_range(4, 1, 5); // OK
constexpr bool res2 = in_range(4, 5, 1); // Compile error!

int min = 1, max = 5;
bool b = in_range( 4, max, min ); // Exception!

That’s fine except for one thing: it turns runtime assertions — which should be unrecoverable — into “recoverable” runtime exceptions. That’s really, really bad. Assertions are used to detect logic errors in your program. If one fires, it means your your program state is suspect. There is no way to safely recover from an invalid program state, so an exception is the wrong tool for the job.

Let’s look at a couple of solutions:

Fix #1: noexcept

One fix is pretty simple: add noexcept to the constexpr function:

constexpr
bool in_range(int val, int min, int max) noexcept
{
    return (min <= max)
      ? min <= val && val <= max
      : throw std::logic_error("Assertion failed!");
}

int min = 1, max = 5;
bool b = in_range( 4, max, min ); // Terminate!

Notice that in_range is declared noexcept — but it throws on error! What happens when a propagating exception hits a noexcept? It’s game over, man. The runtime calls std::terminate, which shuts the process down. That’s what an assert is supposed to do.

Fix #2: std::quick_exit

Here’s another simple fix: We could define a assert_failure exception type that shuts the process down in its constructor:

struct assert_failure
{
    explicit assert_failure(const char *sz)
    {
        std::fprintf(stderr, "Assertion failure: %s\n", sz);
        std::quick_exit(EXIT_FAILURE);
    }
};

Now, we can use assert_failure in our constexpr functions to catch bugs as follows:

constexpr bool in_range(int val, int min, int max)
{
    return (min <= max)
      ? min <= val && val <= max
      : throw assert_failure("min > max!");
}

Notice how the assert_failure constructor reports the error and then calls std::quick_exit. quick_exit is a new function in C++11 that pretty much just shuts the process down without calling any destructors for locals or global objects. That’s almost certainly what you want. If your program state is borked, executing a pile of arbitrary code is a bad idea. It could do more harm than good. Any code that absolutely must execute on termination no matter what should be registered with std::at_quick_exit. You should limit that to things like saving user edits into a look-aside for attempted recovery later. (But don’t corrupt known-good data!)

Fix #3: assert

The trouble with Fix #2 is that it interferes with debuggers, which know about assert and what to do when one fires. The third fix is to just use assert, but to do it in a sneaky way. Once again, we define an assert_failure type, but this time pass to the constructor a function that does the assert for us:

struct assert_failure
{
    template<typename Fun>
    explicit assert_failure(Fun fun)
    {
        fun();
        // For good measure:
        std::quick_exit(EXIT_FAILURE);
    }
};

constexpr bool in_range(int val, int min, int max)
{
    return (min <= max)
      ? min <= val && val <= max
      : throw assert_failure(
          []{assert(!"input not in range");}
        );
}

Now on runtime failures, we get the assert we really wanted in the first place:

assertion "!"input not in range"" failed: file "main.cpp",
line 41, function: auto in_range(int, int, int)::(anonymou
s class)::operator()() const
Aborted (core dumped)

Summary

If you want to add debug checks your C++11 constexpr functions, throw and noexcept are your friends. Alternately, you can define an assert_failure exception type that makes no bones about shutting your process down RIGHT NOW, either with std::quick_exit or with the trusty ol’ assert macro.

"\e"
"\e"

15 thoughts on “Assert and Constexpr in C++11

  1. Your usage of assert is incorrect. Process termination should only be employed when the state of the entire process is irrevocably suspect. What if I bound your in_range function to a script that I interpret at runtime? The script may be incorrectly programmed, but there’s no reason to believe the state of the rest of my program is suspect. I may need to do things like convert the error to give proper error locations for my script.

    I hate people who terminate the process on precondition violation. Process termination should only be employed when the state of the process is, absolutely and completely, corrupted.

    • You hate me? Goodness. And you seem quite certain that my use of assert is incorrect. What do you mean, the entire program state is irrevocably suspect? Do you mean, if there are one or two bits in memory that haven’t been stomped, it’s OK to try to recover? Even if you don’t know which two bits they are? When you start waffling about what corruption is OK and what isn’t, you immediately run into problems. Which part of your program is confused? You don’t know. That’s the problem, and that’s why bringing the process down is — in most cases — absolutely the correct thing to do. It’s what assert is for.

      Now, are there some circumstances where bringing the process down is not acceptable? Absolutely. It’s not OK for flight control software to just give up, for instance. But these cases are extreme.

      I don’t expect you to agree. You’re wrong, but I don’t hate you. 😉

      • “You don’t know.”

        No, you don’t know. I most certainly do know, because I wrote the program. I know which component just failed when it called your function with bad arguments. There is nothing memory-corrupting about the condition on which you have asserted here. There is nothing suspect about any state of the process at large. Only the calling module has a bug in it.

        You don’t even know if that module is written in a language where it’s possible to corrupt memory.

        I’m using Clang and LLVM to build a compiler. They follow this principle. Now if there is a bug in my compiler, how the hell am I going to tell my user which part of his code my compiler bug appears on, so he can try to work around it or submit it as a compiler bug? How the hell am I going to save the user’s work, that he has invested his time and effort on, when someone builds an IDE based on my compiler? I can’t, because some idiot took that choice away from me, even though I know that unless the compiler corrupted memory or something, the process is absolutely recoverable.

        There is no reason to believe that just because the compiler accidentally attempted to generate code that de-references an integer that therefore I cannot save the user’s work.

        The previous example I gave of a bound script function should be plenty sufficient for you to comprehend what I’m saying here. In order to give my user a proper error that refers to the code they actually wrote, instead of a meaningless VM stack trace (if that), I would have to duplicate all your error handling to catch it beforehand, and that’s dumb.

        Asserts should only be used on conditions where the entire process is unrecoverable. This example definitely does not have that property.

        • “I most certainly do know, because I wrote the program.” That is…an astoundingly arrogant statement, in my opinion. Programmers make mistakes. Period.

    • assert is designed so that it can be disabled for production builds. If the in_range function is only used internally, and is not part of a published API, then an assert is just what you want.

  2. I sure hope that C++11 will have a very short support period and that C++14 becomes the “LTS” version. The difference in constexpr usability between the two language versions is Stone Age vs Iron Age.

  3. Shouldn’t be the message in Fix #3 something like “precondition violated: min>max” instead “input not in range”?

Leave a Reply

Your email address will not be published. Required fields are marked *

*