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.