Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constexpr constructor fails to satisfy the requirements, but still constexpr. Why?

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:

  • the non-constexpr-ness isn't caught at the closest possible location, but at the actual constexpr expression, where it is used. So we have to find the offending part, where constexpr silently removed.
  • an object, which is intended to be statically initialized (because it has a 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?

like image 499
geza Avatar asked Dec 05 '18 12:12

geza


People also ask

Can constructor be constexpr?

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.

Is constexpr guaranteed?

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.

What is the point of constexpr?

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.

Is constexpr always evaluated at compile time?

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.


2 Answers

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.

like image 95
Max Langhof Avatar answered Oct 04 '22 02:10

Max Langhof


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 constexprspecifier 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:

  1. 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 constexprable and un-constexprable types.

  2. Because it maintains the constexpr specifier the fallback isn't to general runtime dynamic initialisation but to dynamic initialisation at the time at which statics are initialised. Which may cause problems because of the Static Initialisation Order Fiasco but does at least happen before the main() function is entered.

like image 31
Jack Aidley Avatar answered Oct 04 '22 02:10

Jack Aidley