7
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?

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 2
    You can probably use `static_assert`... – Mats Petersson Sep 06 '13 at 00:35
  • 1
    For future reference: please provide a simple main function that triggers the error in question. – László Papp Sep 06 '13 at 00:36
  • 1
    @MatsPetersson: what if the `constexpr` fails and the function is executed at runtime? – Vittorio Romeo Sep 06 '13 at 00:37
  • Surely that is not "allowed" (that is, the compiler should not allow you to form a call to `getClamped` where the inputs aren't constants - so at least the `static_assert` should fire if the inputs are "wrong way around", even if it can't sort out the clamping at compile-time [although I don't see why it shouldn't be able to do that too]. – Mats Petersson Sep 06 '13 at 00:42
  • Very good question +1 because you have uncovered a compiler bug. Very badly written -1. Makes +0. @Vittorio, me and a some others like to confirm an example via copy&paste&compile. Additionally, imagine you where not using `#include ` but something else very fancy, it may make a difference. – Patrick Fromberg Sep 06 '13 at 01:28
  • @PatrickFromberg, should I make a minimal example and post it here? – Vittorio Romeo Sep 06 '13 at 08:55
  • 1
    @Vittorio, no but next time please. In your case the compile options where actually more important as others pointed out. After thinking about your idea I am tempted to undefine `constexpr` depending on NDEBUG set or not. – Patrick Fromberg Sep 06 '13 at 09:36
  • Alright, sorry for writing the question poorly. Glad I could help. – Vittorio Romeo Sep 06 '13 at 09:51

4 Answers4

13

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Equivalent to `test && assert( test )`, which I would find more idiomatic. – Potatoswatter Sep 06 '13 at 01:21
  • 2
    @Potatoswatter No, it isn't. When `NDEBUG` is undefined, `assert(test)` is `void(0)`, and your expression is illegal. When `NDEBUG` is defined, `assert(test)` is completely implementation defined. So `test && assert(test)` may or may not be valid code that may or may not work. – Yakk - Adam Nevraumont Sep 06 '13 at 01:30
  • Ah, sorry. Good deal :) – Potatoswatter Sep 06 '13 at 02:51
  • 2
    The separate function `assert_helper` is not needed: C++11 19.3/2 describes the header ``: "The contents are the same as the Standard C library header ``. See also: ISO C 7.2." N1570 (almost C11) 7.2.1.1/2 states: "The `assert` macro puts diagnostic tests into programs; it expands to a void expression." so the body of `getClamped` can be simply `return assert(mMin < mMax), (mValue < mMin ? mMin : (mValue > mMax ? mMax : mValue));`. – Casey Sep 06 '13 at 16:27
  • 1
    @Casey not quite: we have no guarantee from any of that that `assert(true)` is a valid part of a `constexpr`. So we have to evaluate `mMin < mMax`, and *only if false* can we call `assert`. Ie, we don't need `assert_helper`, but we still technically need `constexpr_assert` for full portability. – Yakk - Adam Nevraumont Sep 11 '13 at 14:41
  • Is there any way to modify this to create an assertion macro that can trigger at compile-time when a `constexpr` function is called with an improper argument, but which can also trigger at run-time when the `constexpr` function is called at run-time? Essentially something that behaves like `static_assert` when `constexpr` functions are actually evaluated during compilation but `assert` if/when the body of the function is actually linked? – Kyle Strand Aug 18 '15 at 23:39
  • @KyleStrand http://ericniebler.com/2014/09/27/assert-and-constexpr-in-cxx11/ this is probably what you need – oliora Oct 04 '16 at 22:34
  • @oliora Wow, that lambda trick is madness. Anyway, I didn't realize that `assert` actually *can* be used in a `constexpr` function in C++14, which seems like a crucial thing to know. In fact, I'll add that as an answer. – Kyle Strand Oct 04 '16 at 22:47
  • @oliora ...except, bizarrely, I can't get his `throw` trick to actually work for me with `g++`.... `error: expression '' is not a constant-expression` – Kyle Strand Oct 04 '16 at 22:55
  • 2
    @KyleStrand that's what I come up as a cross-compiler c++11 solution https://gist.github.com/oliora/928424f7675d58fadf49c70fdba70d2f – oliora Oct 04 '16 at 23:11
  • @oliora Actually, the `throw` trick works fine as long as it's part of a ternary expression; I must have screwed that up yesterday. – Kyle Strand Oct 06 '16 at 20:38
  • @KyleStrand using throw is a good solution if you ok with exception in release version. I didn't want it so I used assert. – oliora Oct 06 '16 at 20:44
  • 1
    @oliora Though of course as Niebler points out, using `noexcept` is essentially equivalent to having an `assert` that is not suppressed for release builds. – Kyle Strand Oct 06 '16 at 21:01
  • 1
    @oliora I've gone ahead and written up an answer, crediting you and Niebler. – Kyle Strand Oct 06 '16 at 21:04
  • 1
    @oliora FYI I've now written [an answer to another question](http://stackoverflow.com/a/42659084/1858225) again relying heavily on your code and the link you've provided. – Kyle Strand Mar 07 '17 at 22:00
5

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 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.
    • 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.
Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
1

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

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • Ah yes, abort is also a return in a way. And clang may also be correct depending on the compiler options because assert may be just defined as noop. – Patrick Fromberg Sep 06 '13 at 02:58
1

constexpr calculates in compile time. Non static assert in run-time.