14

From what I understand, a constexpr function can be executed at compile time as well as runtime, depending on if the entire evaluation can be done at compile time or not.

However, you cannot overload this function to have a runtime and a compile time counterpart.

So my question is, how can I put in a runtime assert to ensure that the execution of the runtime function is passed valid parameters along with my static_assert?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Adrian
  • 10,246
  • 4
  • 44
  • 110
  • A more recent take at it: https://akrzemi1.wordpress.com/2017/05/18/asserts-in-constexpr-functions/ and https://gist.github.com/oliora/928424f7675d58fadf49c70fdba70d2f – alfC Aug 22 '21 at 04:36

2 Answers2

12

Eric Niebler covers this issue well in Assert and Constexpr in C++11, he points out that using assert in a constexpr function is not allowed in C++11 but it is allowed in C++14(As part of the Relaxing constraints on constexpr functions proposal) and provides the following snippet:

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

If we have to support C++11 then there are some alternatives. The obvious one is the using throw but this as he points out this turns what should be a unrecoverable error into a recoverable one since you can catch the exception.

He suggest some alternatives:

  1. Using throw with the noexcept specifier:

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

    if an exception leaves the function std::terminate will be called.

  2. Call std::quick_exit from the constructor of an exception type:

    struct assert_failure
    {
        explicit assert_failure(const char *sz)
        {
            std::fprintf(stderr, "Assertion failure: %s\n", sz);
            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("min > max!");
    }
    
  3. Pass a lambda expression that asserts to the constructor of an exception type:

    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");}
            );
    }
    
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • 2
    I've also found out that you can use assert directly in a list context. `constexpr bool in_range(int val, int min, int max) { return (assert(min <= max), min <= val && val <= max); }` Basically, you have to make it so that it can never get to the non-constexpr call if used in a constexpr context on failure. This works because, assert is a macro to a ternary expression, which evaluates to call the underlying non-constexpr function on failure. – Adrian Sep 09 '15 at 16:36
  • @Adrian interesting to note that [the comma operator was only allowed in constant expressions in C++11](http://stackoverflow.com/q/27324573/1708801). – Shafik Yaghmour Sep 09 '15 at 17:26
  • 1
    @Adrian although that is not going to be portable since it relies on the implementation detail of `assert` which is not covered by the standard. – Shafik Yaghmour Sep 09 '15 at 17:45
1

You can throw an exception. If an exception is thrown at compile time from a constexpr function, it basically counts as failing a static assert. If it happens at runtime, it will just be an exception as usual.

This question shows a code example where this happens: Passing constexpr objects around

Also related: What happens when an exception is thrown while computing a constexpr?

Community
  • 1
  • 1
Chris Beck
  • 15,614
  • 4
  • 51
  • 87