16

I have several brief constexpr functions in my libraries that perform some simple calculations. I use them both in run-time and compile-time contexts.

I would like to perform some assertions in the body of these functions, however assert(...) is not valid in a constexpr function and static_assert(...) can not be used to check function parameters.

Example:

constexpr int getClamped(int mValue, int mMin, int mMax) noexcept
{
    assert(mMin <= mMax); // does not compile!
    return mValue < mMin ? mMin : (mValue > mMax ? mMax : mValue);
}

Is there a way to check whether the function is being executed in a run-time or compile-time constant and execute the assert only if it's being executed at run-time?

constexpr int getClamped(int mValue, int mMin, int mMax) noexcept
{
    assert_if_runtime(mMin <= mMax); 
    return mValue < mMin ? mMin : (mValue > mMax ? mMax : mValue);
}
4
  • 1
    @dasblinkenlight: What I meant is that static_assert does not make sense in this occasion. Commented Sep 27, 2014 at 8:28
  • (Disclaimer: I'm a noob and never used a constexpr in real life.) Based on my initial Google search, unless your compiler supports N3652 which relaxes the C++11 constexpr, it can't do what you ask for. Once this is available, you will be able to throw an exception such as std::range_error in lieu of a static_assert. You can try your hand using Clang 3.4 with std=c++14. Commented Sep 27, 2014 at 9:06
  • your claim is not true for Clang in -std=c++1y mode, assert will work just fine. you should retag to c++11 if you are limited to that standard. Commented Sep 27, 2014 at 22:14
  • In C++20, we’ll have std::is_constant_evaluated which would allow you to easily switch between static_assert and assert Commented Jan 23, 2020 at 14:59

3 Answers 3

9

assert works now that g++ has implemented N3652, Relaxing constraints on constexpr functions. This status page indicates that this has been implemented in gcc5.

assert also works (in constexpr functions) on the current clang compiler shipped by Apple, with -std=c++1y.

At this time, I see nothing in the standard that assures one that assert will work in constexpr functions, and such an assurance would be a welcome addition to the standard (at least by me).

Update

Richard Smith drew my attention to LWG 2234 submitted by Daniel Krügler which is attempting to create the assurance I refer to above. This has been incorporated into C++17.

Sign up to request clarification or add additional context in comments.

6 Comments

+1 I have been using asserts all over constexpr code and am very happy with it
No, it won't work, because the assert macro must eventually call abort (usually via intermediate functions like __assert_fail), which is not a constexpr function.
@o11c If assert always called abort, it would be quite broken.
Relaxed constexpr implements support for the semicolon after assert, but the macro itself expands to an expression which should be usable in C++11.
@Potatoswatter Some branch in the assert must. the linked draft does not permit calls to non-constexpr expressions on any control path. The expansion of assert(b) is ((b) ? static_cast<void> (0) : __assert_fail ("b", "assert.cpp", 5, __PRETTY_FUNCTION__));, so being able to declare and mutate local variables isn't needed; the external function call is the problem.
|
8

Throwing an exception might be useful as the compiler will ignore the run-time part when it knows at compile-time that the exception is not thrown.

#include <cassert>

constexpr int getClamped(int mValue, int mMin, int mMax)
{
    return ( mMin <= mMax ) ? 
           ( mValue < mMin ? mMin : (mValue > mMax ? mMax : mValue) ) :
           throw "mMin must be less than or equal to mMax";
}

int main( int argc, char** argv )
{
    // These two work:
    static_assert( getClamped( 42, 0, 100 ) == 42, "CT" );
    assert( getClamped( argc, 0, 100 ) == argc );

    // Fails at compile-time:
    // static_assert( getClamped( 42, 100, 0 ) == 42, "CT" );

    // Fails at run-time:
    // assert( getClamped( argc, 100, 0 ) == argc );
}

Live example

1 Comment

After some playing around (trying to find a way to make overloading on constexpr work), I think this is the best/only way to do it. But the thought of throwing from a clamp function horrifies me...
8

A refinement on Daniel Frey's answer is to use noexcept on the constexpr function to turn the runtime error into a call to std::terminate. Assertion failures are unrecoverable; they should halt the process immediately. Turning them into exceptions is a very bad idea.

#include <exception>
#include <stdexcept>

struct assert_failure
  : std::logic_error
{
    explicit assert_failure(const char *sz)
      : std::logic_error(sz)
    {}
};

constexpr bool in_range(int i, int j, int k) noexcept
{
    return (i <= j && j <= k) ? true : throw assert_failure("input not in range");
}

int main(int argc, char* argv[])
{
    constexpr bool b1 = in_range(0, 4, 5); // OK!
    constexpr bool b2 = in_range(0, 6, 5); // Compile-time error!
    bool b3 = in_range(0, 4, argc);        // May or may not terminate the process
}

The runtime error for me looks like:

terminate called after throwing an instance of 'assert_failure'
  what():  input not in range
Aborted (core dumped)

Hope that helps.

1 Comment

+1 I explicitly removed the noexcept, but it does have its uses so thanks for pointing that out. Of course, it's a decision you need to make, in my usual work-environment, terminating the process is something we try to avoid but I have a lot of sympathy for fail early, fail hard :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.