The standard says about template constexpr functions/constructors in dcl.constexpr/6:
If the instantiated template specialization of a constexpr function template or member function of a class template would fail to satisfy the requirements for a constexpr function or constexpr constructor, that specialization is still a constexpr function or constexpr constructor, even though a call to such a function cannot appear in a constant expression. If no specialization of the template would satisfy the requirements for a constexpr function or constexpr constructor when considered as a non-template function or constructor, the template is ill-formed, no diagnostic required.
The interesting part is:
fail to satisfy the requirements for a ... constexpr constructor, that specialization is still a ... constexpr constructor
So, even if a constructor is marked with constexpr
, it may not be used in a constant expression.
Why does this rule exist? Why isn't constexpr
removed, when a function doesn't satisfy the requirements?
The current behavior is bad in two ways:
constexpr
silently removed.constexpr
constructor), will be dynamically initialized without any errors/warnings (because the constructor isn't "really" constexpr).Does this rule have some pros, which balances the cons of it?
A constructor that is declared with a constexpr specifier is a constexpr constructor. Previously, only expressions of built-in types could be valid constant expressions. With constexpr constructors, objects of user-defined types can be included in valid constant expressions.
Quick A: constexpr guarantees compile-time evaluation is possible if operating on a compile-time value, and that compile-time evaluation will happen if a compile-time result is needed.
constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.
constexpr functions will be evaluated at compile time when all its arguments are constant expressions and the result is used in a constant expression as well.
This rule allows you to write a templated constructor/function and mark it as constexpr
even when it's not always constexpr
(only at least sometimes).
For example, std::pair
has constexpr
constructors, but it is of course usable outside of constant expressions.
This is quite sensible, because otherwise you would have to duplicate all these functions (once with constexpr
and once without), even if the code is exactly the same. Let's not even consider ambiguity.
Since it is generally impossible to prove that a template cannot ever satisfy constexpr
, no diagnostic is required for it (but it's ill-formed so compilers can complain to you if they can prove this for a given case).
You are correct that this is not very useful if you want to specify "this function shall only be usable in constant expression", but that's not what this wording is aiming for.
Edit: To clarify, constexpr
for functions only means "legal to evaluate inside a constant expression" (more precise wording here), not "can only be evaluated at compile-time". By contrast, constexpr
variables must be initialized with a constant expression.
Another edit: We have exact wording to discuss, thanks to @JackAidley!
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function, the
constexpr
specifier is ignored and the specialization is not a constexpr function.
The problem with this is that "there is at least one set of arguments for which the function can be constant-evaluated" is part of the "requirements for a constexpr function". Therefore, compilers cannot implement this clause, since it is not possible to prove (in general) whether such a set exists for a given function (or a function template instantiation). You either have to muddy this requirement further or give up on this aspect. It seems the committee chose the latter.
In earlier versions of the suggestion for the change to the language, it operated as you suggest:
If the instantiated template specialization of a constexpr function template would fail to satisfy the requirements for a constexpr function,the
constexpr
specifier is ignored and the specialization is not a constexpr function.
But it was later changed. I was unable to track down any definitive answer to your question but I think that it is reasonable to believe that the answer is that constexpr
make other semantic changes to the code and these are retained even though the function is no longer usable in other constexpr
statements. If you look at defect report 1358 which includes the change to the current wording, you can see an intermediate form of words that includes the a note about retaining const
status regardless.
I also think that while the retention of constexpr
status is unintuitive, both of your arguments against it are wrong:
Catching the constexpr
when the template instantiation is made goes against how C++ templates usually work - you only get an error when you try and use the template in a way it cannot be used for that type; merely not being able to complete the entire signature is not an error. To introduce special case mechanics for constexpr
would be unnecessarily confusing and limit usefulness since you'd now need to write different templates for constexpr
able and un-constexpr
able types.
Because it maintains the constexpr
specifier the fallback isn't to general runtime dynamic initialisation but to dynamic initialisation at the time at which static
s are initialised. Which may cause problems because of the Static Initialisation Order Fiasco but does at least happen before the main()
function is entered.
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