These should probably be in different questions, but they're related so...
Why do we need to write constexpr
at all? Given a set of restrictions couldn't a compiler evaluate code to see if it satisfies the constexpr
requirements, and treat it as constexpr
if it does? As a purely documentation keyword I'm not sure it holds up because I can't think of a case where I (the user of someone else's constexpr
function) should really care if it's run time or not.
Here's my logic: If it's an expensive function I think as a matter of good practice I should treat it as such regardless of whether I give it compile-time constant input or not. That might mean calling it during load time and saving off the result, instead of calling it during a critical point in the execution. The reason is because constexpr
doesn't actually guarantee to me that it will not be executed in run time in the first place — so perhaps a new/different mechanism should do that.
The constexpr
restrictions seem to exclude many, if not most, functions from being compile-time evaluated which logically could be. I've read this is at least in part (or perhaps wholly?) to prevent infinite looping and hanging the compiler. But, if this is the reason, is it legitimate?
Shouldn't a compiler be able to compute if, for any given constexpr
function with the given inputs used, it loops infinitely? This is not solving the halting problem for any input. The input to a constexpr
function is compile time constant and finite, so the compiler only has to check for infinite looping for a finite set of input: the input actually used. It should be a regular compilation error if you write a compile-time infinite loop.
I asked a very similar question, Why do we need to mark functions as constexpr?
When I pressed Richard Smith, a Clang author, he explained:
The constexpr keyword does have utility.
It affects when a function template specialization is instantiated (constexpr function template specializations may need to be instantiated if they're called in unevaluated contexts; the same is not true for non-constexpr functions since a call to one can never be part of a constant expression). If we removed the meaning of the keyword, we'd have to instantiate a bunch more specializations early, just in case the call happens to be a constant expression.
It reduces compilation time, by limiting the set of function calls that implementations are required to try evaluating during translation. (This matters for contexts where implementations are required to try constant expression evaluation, but it's not an error if such evaluation fails -- in particular, the initializers of objects of static storage duration.)
This all didn't seem convincing at first, but if you work through the details, things do unravel without constexpr
. A function need not be instantiated until it is ODR-used, which essentially means used at runtime. What is special about constexpr
functions is that they can violate this rule and require instantiation anyway.
Function instantiation is a recursive procedure. Instantiating a function results in instantiation of the functions and classes it uses, regardless of the arguments to any particular call.
If something went wrong while instantiating this dependency tree (potentially at significant expense), it would be difficult to swallow the error. Furthermore, class template instantiation can have runtime side-effects.
Given an argument-dependent compile-time function call in a function signature, overload resolution may incur instantiation of function definitions merely auxiliary to the ones in the overload set, including the functions that don't even get called. Such instantiations may have side effects including ill-formedness and runtime behavior.
It's a corner case to be sure, but bad things can happen if you don't require people to opt-in to constexpr
functions.
As for constexpr
objects, certain types can produce core constant expressions which are usable in constant expression contexts without having been declared constexpr
. But you don't really want the compiler to try evaluating every single expression at compile time. That's what constant propagation is for. On the other hand it seems pretty essential to document when something needs to happen at compile time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With