constexpr and floating point rounding behaviour

Tags: programming

Published on
« Previous post: Preventing hotlinking from certain … — Next post: A brief critique of the singleton … »

One of the new features in C++11 is the ability to declare variables (or even functions) to be a constant expression that can be evaluated at compile time. Not only does this make the code easier to read, it also offers increased performance while still being very maintainable.

I was thus very surprised to find out that, according to one of our group members, constexpr stopped working—the code had apparently compiled on a personal machine but it would not compile in our production environment at university. We used the following test program to trace down the culprit:

#include <cmath>

int main(int, char**)
{
  constexpr double a = 2.0;
  constexpr double b = std::sqrt(a);

  return 0;
}

And indeed, using our CMake build environment, gcc would prove unwilling to compile the code, responding with a very terse error message:

constexpr.cc: In function ‘int main(int, char**)’:
constexpr.cc:6:35: error: ‘sqrt(2.0e+0)’ is not a constant expression
   constexpr double b = std::sqrt(a);

After the initial bafflement about this seemingly incorrect compiler error message, I happened to take a closer look at the compiler flags of our build environment. The culprit then turned out to be -frounding-math. This flag instructs the compiler that floating point rounding behaviour might change at runtime, so naturally, an expression such as std::sqrt(2.0) is not a constant expression any more. Thus, I learned that:

$ g++ -std=c++11 constexpr.cc
$ ./a.out # Yay
$ g++ -std=c++11 -frounding-math constexpr.cc
constexpr.cc: In function ‘int main(int, char**)’:
constexpr.cc:6:35: error: ‘sqrt(2.0e+0)’ is not a constant expression
   constexpr double b = std::sqrt(a);

Having lost a sizeable chunk of my daily sanity on this bug, I was of course interested in tracing done its cause. The culprit turned out to be CGAL. For reasons that are not completely clear to my addled mind, the CGAL module for CMake decides to set the CXX_COMPILE_FLAGS globally, regardless of whether a file actually uses CGAL or not. The personal computer where the code was tested first did not have a working installation of CGAL, so of course the code compiled correctly.

Lessons learned: Check the compile flags and be wary around CMake modules (though I have to point out that it is the implementation of the module that caused the problems, not CMake per se; quite the opposite—I am a very fond user of CMake).