Experimenting with constexpr

Since seeing Scott Schurr at C++Now in Aspen and hearing his talks about constexpr, it’s been on my list of things to try out, and recently I got around to it. With the release of Visual Studio 2015, Microsoft’s compiler now finally supports C++11 style constexpr (modulo some minor issues), so it’s a good time to jump in.

Doing things the hard way

Now, I have no expectation that MS will provide for C++14 constexpr any time soon, since (as I understand it) it requires a fairly dramatic rework of the compiler front end. So although C++14’s relaxed constexpr is much easier to use than C++11’s relatively draconian version, I decided to stick as much as possible to C++11 to see what I could do. This basically means:

  • Functions are limited to a single return statement
  • Constructor bodies are empty – everything must be done in the initialization list

But I can do conditionals with the ternary operator, and I can call functions. That means in theory I can do anything! I like functional programming!

Scott’s talk covers three broad application areas of constexpr: floating-point computations, parsing and containers. So I started at the beginning.

I decided to put everything in the cx namespace, and make use of a trick I learned from Scott. One of the snags with constexpr is that it can be at the compiler’s discretion. Let’s say you write a constexpr function, and call it. Unless you use the result in a context that is required at compile time (such as a non-type template argument, array size, or assigned to a constexpr variable), the compiler is free not to do the work at compile time, but instead generate a normal runtime function. And in my experience, compilers aren’t aggressive about doing work at compile time.

From one point of view this is somewhat desirable – or at least, we want constexpr functions to be able to do double duty as compile-time and runtime functions. Well, that’s a stated goal, but as we shall see, writing C++11-friendly constexpr functions sometimes results in formulations that are very different from what we’d usually expect/want at runtime. But let’s assume that I write a nice constexpr function:

constexpr float abs(float x)
{
  return x >= 0 ? x : -x;
}

Now what if I make a mistake using this? What if I call this function and forget to use the result in a constexpr variable? The compiler’s going to be quite happy to emit the function, and I will end up with runtime computation that I don’t want.

Avoiding accidental runtime work

There’s no way I know of at compile time to insist that the compiler stay in constexpr-land. But we can at least turn a runtime problem into a link-time error with a little trick:

namespace err {
  namespace {
    extern const char* abs_runtime_error;
  }
}
 
constexpr float abs(float x)
{
  return x >= 0 ? x :
    x < 0 ? -x :
    throw err::abs_runtime_error;
}

This makes use of a couple of features of constexpr. First, throw is not allowed in constexpr functions (because what could it mean to throw an exception at compile time? Madness). But that doesn’t matter, because constexpr functions are effectively evaluated in a C++ interpreter, so as long as we never trigger the condition that causes evaluation of the throw, we’re OK. This compile-time interpreter also has a lot fewer features than the normal compiler that we’re used to, including (at least at time of writing) things like warnings for unreachable code and conditional expressions always being true, so we can even write things like:

constexpr float myFunc(float x)
{
  return true ? x :
    throw some_error;
}

And the compiler won’t make a sound.

The throw pattern

So what’s actually happening here? We’re getting two forms of protection. First, we can use this to enforce the domain, as we might with a square root function:

namespace err {
  namespace {
    extern const char* sqrt_error;
  }
}
 
constexpr float sqrt(float x)
{
  return x >= 0 ? sqrt_impl(x) :
    throw sqrt_error;
}

If we accidentally pass a negative number to sqrt here, it will try to evaluate throw in a constexpr function and we’ll get a compile error. So that’s nice.

The second, more insidious error that we avoid is the one where we accidentally turn a compile-time function into a runtime one. If we call sqrt without (for example) assigning the result to a constexpr variable, and the compiler emits sqrt as a runtime function, the linker will fail trying to find sqrt_error. A link error isn’t quite as good as a compile error, but it’s a lot better than a runtime problem that might not even be discovered!

The final oddity here arises when you consider that this code might be in a header. Since constexpr functions are (we hope) computed at compile time, they are implicitly inline, so that’s OK. But perhaps the stranger thing is that we have a symbol in an anonymous namespace in a header. This, by the way, is called out in the ISO C++ Core Guidelines as a no-no: “It is almost always a bug to mention an unnamed namespace in a header file.” But in this case, we don’t want the user to be able to define the symbol, and the potential for ODR-violations/multiple definitions isn’t there – the symbol is never supposed to be defined or used. Maybe we’ve found the one reason for that “almost”.

So those are some of the basics of building constexpr machinery. Next: my dive into floating-point maths at compile time.

Leave a Reply