template constexpr inline
T getClamped(const T& mValue, const T& mMin, const T& mMax)
{
assert(mMin < mMax); // remove this
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
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:
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.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
throw expression must be wrapped in some kind of larger logical expression that is only evaluated if the condition being asserted 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.assert, this approach does not depend on NDEBUG; release builds will trigger failures and crash.std::quick_exit. This eliminates the need for nothrow.
quick_exit call in ifdef's).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.
throw and the quick_exit. This seems much cleaner and saner.constexpr calculates in compile time. Non static assert in run-time.
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.