8

In G++, various builtin math functions are constexpr under certain conditions. For example, the following compiles:

static constexpr double A = __builtin_sqrt(16.0);
static constexpr double B = __builtin_pow(A, 2.0);

They are not always constexpr though, it depends on the argument. For example, __builtin_sqrt(NAN) results in a compile error when used as a constant expression.

But I'm hitting a strange case where it seems to me that it should be constexpr, but it is not:

static constexpr double value () { return 1.23; }
static constexpr double result = __builtin_round(__builtin_sqrt(value()));

This produces:

a.cpp:2:73: error: ‘__builtin_round(1.1090536506409416e+0)’ is not a constant expression
 static constexpr double result = __builtin_round(__builtin_sqrt(value()));
                                                                         ^

I've tried variations of the above code, and I've found that:

  • The __builtin_round has some special role in the problem. Replacing it with some other builtin math function, such as sqrt or pow resolves the error. So would appear that __builtin_round simply lacks constexpr support. But...
  • If value() is replaced by a literal 1.23, that too removes the error.
  • Removing __builtin_sqrt, leaving only __builtin_round(value()), removes the error too.

I'd like to know why the round is behaving in this way, and if there is any workaround.

NOTE. I am aware that the builtin math functions, with their constexpr-ness, are a non-standard compiler-specific feature. Please don't lecture me on how I should not be using this, or how I shouldn't be trying to do compile time math. In my case, having constexpr math is an important feature, and I'm fine with depending on G++.

Ambroz Bizjak
  • 7,399
  • 1
  • 34
  • 44
  • Doesn't rounding depend on the current floating point rounding method, which is known only at run time? – Alan Stokes May 03 '14 at 18:04
  • @AlanStokes I taught so too, but gcc assumes the rounding mode is known at compile time anyway. See `-fno-rounding-math` option in gcc. Plus, if it was due to that, at least in theory pretty much all FP operators would not be constexpr (which, at least in G++, are constexpr, similar to most math functions). – Ambroz Bizjak May 03 '14 at 18:09

3 Answers3

4

I have an idea of another workaround.

There is: use a helper function pass_through

template<typename T>
constexpr T&& pass_through (T&& t) { return static_cast<T&&>(t); }

use it like this:

static constexpr double value () { return 1.23; }
static constexpr double result = __builtin_round(pass_through(__builtin_sqrt(value())));

This code is compiled without error in the G++.

I also agree, the opinion that this issue should be reported to the GCC.

user1942027
  • 6,884
  • 6
  • 32
  • 44
2

I can only answer part of your question, which is whether there's a workaround.

There is: use a helper constexpr variable.

Although __builtin_round(__builtin_sqrt(value())) is rejected as a constant expression, helper = value() is accepted, and after that, so is result = __builtin_round(__builtin_sqrt(helper)). Alternatively, helper = __builtin_sqrt(value()) and then result = __builtin_round(helper).

This inconsistency shows that GCC is clearly capable of evaluating the expression at compile-time and is willing to treat it as constexpr. Although it may not find support in the standard, it may be worth reporting this as an enhancement request on GCC's bug tracker.

As for an actual reason, I would guess that GCC first performs simple constant folding, then checks if constexpr requires additional constant folding, and if so, checks whether the expression matches the constexpr requirements, and if so, calculates the result. The check would fail, since the expression is technically not valid, but the extra helper variable would improve the simple constant folding so that by the time the validity is checked, the invalid builtin functions are no longer there. But like I said, it's a guess.

1

This seems to work on g++ 4.8.2:

static constexpr double value () { return 1.23; }
static constexpr double root(double x) { return sqrt(x);}
static constexpr double result = roundl(root(value()));

Interestingly, if I replace roundl with round the compiler complains:

error: ‘round(1.1090536506409416e+0)’ is not a constant expression

This also works:

static constexpr double value () { return 1.23; }
static constexpr double roundroot(double x) { return roundl(sqrt(x));}
static constexpr double result = roundroot(value());

But again, only with roundl and not with round. All of this is also true when using the corresponding __builtin_ versions (which these simply wrap).

I'm downloading the gcc source now to take a look but don't have an answer to "why" yet.

Edward
  • 6,278
  • 1
  • 25
  • 50