Search code examples
c++c++11g++assertconstexpr

g++ doesn't compile constexpr function with assert in it


template<typename T> constexpr inline 
T getClamped(const T& mValue, const T& mMin, const T& mMax) 
{ 
     assert(mMin < mMax); // remove this line to successfully compile
     return mValue < mMin ? mMin : (mValue > mMax ? mMax : mValue); 
}

error: body of constexpr function 'constexpr T getClamped(const T&, const T&, const T&) [with T = long unsigned int]' not a return-statement

Using g++ 4.8.1. clang++ 3.4 doesn't complain.

Who is right here? Any way I can make g++ compile the code without using macros?


Solution

  • GCC is right. However, there is a relatively simple workaround:

    #include "assert.h"
    
    inline void assert_helper( bool test ) {
      assert(test);
    }
    inline constexpr bool constexpr_assert( bool test ) {
      return test?true:(assert_helper(test),false);
    }
    
    template<typename T> constexpr
    inline T getClamped(const T& mValue, const T& mMin, const T& mMax)
    {
      return constexpr_assert(mMin < mMax), (mValue < mMin ? mMin : (mValue > mMax ? mMax : mValue));
    }
    

    where we abuse the comma operator, twice.

    The first time because we want to have an assert that, when true, can be called from a constexpr function. The second, so we can chain two functions into a single constexpr function.

    As a side benefit, if the constexpr_assert expression cannot be verified to be true at compile time, then the getClamped function is not constexpr.

    The assert_helper exists because the contents of assert are implementation defined when NDEBUG is true, so we cannot embed it into an expression (it could be a statement, not an expression). It also guarantees that a failed constexpr_assert fails to be constexpr even if assert is constexpr (say, when NDEBUG is false).

    A downside to all of this is that your assert fires not at the line where the problem occurs, but 2 calls deeper.