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"

18 Replies to “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.

          • OP has a good point, though. LLVM is a PITA to integrate (especially in a higher language such as Python, where users expect introspectable errors, not hard crashes with a minimalistic context-free error message) because of its decision to assert on user input errors. This might not matter for very simple library APIs where it’s reasonable to tell the user to validate inputs themselves (if you’re exposing a factorial function, you can tell the user to pass non-negative numbers), but it really matters when exposing something very complex such as a compiler framework.

            I get that this a larger cultural problem in C++. There is no single consensual way to report parameter errors to a caller (for some reason, exceptions are still frowned upon), therefore many library authors use assert() out of convenience. Convenience for them, that is, since they are offloading the development and maintenance cost of parameter validation to each and every of their users.

            Oh, and sure, there’s the argument that “I can disable assertions in production code so that I shave off 1% of CPU usage at runtime”. That’s probably the most braindead excuse against proper parameter validation. That 1% CPU you saved at runtime will come back multiplied under the form of maintenance pain when invalid inputs leads to weird bugs, data corruption, etc.

    • 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”?

  4. Just pointing out that for:


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

    GCC (at least), with -Wall, will emit a warning that throw always calls std::terminate(). constexpr is often found in headers, so this may create very many warnings in a project, which I just found out the hard way. That said, I much appreciate the discussion, and I’m going to try Andrzej KrzemieƄski’s suggestion next.

  5. Thanks for this blog post! It helped me in my thought process.

    Here is a simplified form of the solution I ended up with:

    int illegal_value() { return -1; }

    constexpr int do_something(const int value) {
    return value < 100 ? value * 2 + 3 : illegal_value();
    }

    Since illegal_value() is not declared as constexpr, it will cause a build problem whenever the condition is not fulfilled.

Leave a Reply

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

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.