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?
g++ is right. Per the standard, a non-static
assert
is not permitted in aconstexpr
statement.GCC is right. However, there is a relatively simple workaround:
where we abuse the comma operator, twice.
The first time because we want to have an
assert
that, whentrue
, can be called from aconstexpr
function. The second, so we can chain two functions into a singleconstexpr
function.As a side benefit, if the
constexpr_assert
expression cannot be verified to betrue
at compile time, then thegetClamped
function is notconstexpr
.The
assert_helper
exists because the contents ofassert
are implementation defined whenNDEBUG
is true, so we cannot embed it into an expression (it could be a statement, not an expression). It also guarantees that a failedconstexpr_assert
fails to beconstexpr
even ifassert
isconstexpr
(say, whenNDEBUG
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.
constexpr calculates in compile time. Non static assert in run-time.
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 astatic_assert
with the same condition won't work, sincemMin
andmMax
are not considered constant expressions.assert
isn't triggered at compile time, but the function isconstexpr
, if the condition is false but the expression is evaluated at compile time (e.g.constexpr auto foo = getClamped(1,2,0);
), theassert
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 anassert
), mark theconstexpr
functionnothrow
throw
expression must be wrapped in some kind of larger logical expression that is only evaluated if the condition beingassert
ed isfalse
, such as a ternary expression (which is what Niebler uses in his example). A standaloneif (condition) throw <exception>;
statement will not be permitted, even in C++14.assert
, this approach does not depend onNDEBUG
; release builds will trigger failures and crash.std::quick_exit
. This eliminates the need fornothrow
.quick_exit
call inifdef
'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 callsstd::quick_exit
, and thenthrow
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 thequick_exit
. This seems much cleaner and saner.