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?
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.
As of C++14, this is no longer an issue; g++
with the -std=c++14
flag compiles and runs your code just fine.
There are three drawbacks:
- As noted in your question, this does not work in C++11.
- The
assert
will, of course, never by triggered at compile time. Even adding a static_assert
with the same condition won't work, since mMin
and mMax
are not considered constant expressions.
- Moreover, because the
assert
isn't triggered at compile time, but the function is constexpr
, if the condition is false but the expression is evaluated at compile time (e.g. constexpr auto foo = getClamped(1,2,0);
), the assert
will never fire--meaning that the incorrect function arguments will not be caught.
In a comment, user oliora links to an interesting blog post by Eric Niebler that describes multiple approaches that work in C++11 and can be triggered while compiling or at runtime as appropriate.
In short, the strategies are:
throw
an exception; to make it uncatchable (i.e. more like an assert
), mark the constexpr
function nothrow
- Niebler does not call this out in his post, but the
throw
expression must be wrapped in some kind of larger logical expression that is only evaluated if the condition being assert
ed is false
, such as a ternary expression (which is what Niebler uses in his example). A standalone if (condition) throw <exception>;
statement will not be permitted, even in C++14.
- Niebler also fails to note that, unlike
assert
, this approach does not depend on NDEBUG
; release builds will trigger failures and crash.
- Throw a custom expression type whose constructor invokes
std::quick_exit
. This eliminates the need for nothrow
.
- Again, this won't be compiled out for release builds (unless you wrap the
quick_exit
call in ifdef
's).
- Wrap an actual
assert
inside a lambda, which is passed to a struct that takes an arbitrary callable (as a template parameter) and invokes it and then calls std::quick_exit
, and then throw
that struct. This one seems like severe overkill, but of course it generates a true assertion-failure message at runtime, which is nice.
- This is the only approach that will not cause a release build to crash.
- oliora provides a variation of this approach without the
throw
and the quick_exit
. This seems much cleaner and saner.
constexpr calculates in compile time. Non static assert in run-time.
g++ is right. Per the standard, a non-static assert
is not permitted in a constexpr
statement.
... its function-body shall be a compound-statement that contains only:
null statements,
static_assert-declarations,
typedef declarations and alias-declarations that do not define classes or enumerations,
using-declarations,
using-directives,
and exactly one return statement.
-- 7.1.5/3