Where in the C++11 standard does it specify when a

2019-02-04 08:42发布

问题:

Just because a function (or constructor)...

  • is declared constexpr and
  • the function definition meets the constexpr requirements

...doesn't mean that the compiler will evaluate the constexpr function during translation. I've been looking through the C++11 FDIS (N3242, available at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/) to try and determine two things:

  • When is the compiler obligated to evaluate a constexpr function during translation?
  • When is the compiler allowed to evaluate a constexpr function during translation?

Section 5.19 Paragraph 1 says that constant expressions can be evaluated during translation. As far as I can comprehend, the remainder of Section 5.19 sets forth the rules for what is valid in the definition of a constexpr function.

I understand that I can force constexpr evaluation during translation by declaring the result of the constexpr function as constexpr. Like this:

// Declaration
constexpr double eulers_num() { return 2.718281828459045235360287471; }

// Forced evaluation during translation
constexpr double twoEulers = eulers_num() * 2.0;
static_assert(twoEulers > 5.0, "Yipes!");

So far I've been unable to find the paragraphs in the FDIS that:

  • Force twoEulers to be evaluated during translation or
  • Specify other situations when the compiler may or must evaluate a constexpr function during translation.

What I'm particularly interested in discovering is whether constexpr evaluation during translation is triggered by:

  1. When all parameters passed to the constexpr function are literals, or
  2. The implied object argument during overload resolution (Section 13.3.1 Paragraph 3) is either constexpr or requires a literal (such as for array dimensions), or
  3. Something else entirely.

Please, if possible, in your responses cite sections of the FDIS that I can look up or key phrases I can search in the FDIS. The English in the standard is somewhat obtuse, so I may have been reading the relevant paragraphs and have entirely missed their meaning or intent.

回答1:

It is "allowed" to evaluate the constexpr call at compile time whenever it is actually possible to do so. Remember that the specification operates under the "as if" rule. So if you can't tell the difference, the compiler can do whatever it wants.

The compiler is required to evaluate constexpr calls at compile time when it actually needs the answer at compile time. For example:

constexpr int foo() {return 5;}

std::array<float, foo()> arr;

The compiler needs to know the array size at compile time. Therefore, it must evaluate the constant expression at compile time. If the constexpr function cannot be executed at compile time, you get a compile-time error.



回答2:

Nicol Bolas is 100% correct, but there is one other interesting aspect: whether the expression is evaluated at translation-time and whether it is evaluated at run-time are completely independent questions. Since the constant expression cannot have side-effects, it can be evaluated an arbitrary number of times, and nothing stops it from being evaluated at both translation-time and run-time.

Suppose the constant expression were a large array (not a std::array, just an array), which is entirely legal, and the program does not indicate that it has static storage. Suppose also that only element 7 of the array is used in a context in which compile-time computation is necessary. It is quite reasonable for the compiler to compute the entire array, use element 7, discard it, and insert code to compute it at run-time in the scope in which it is used, rather than bloating the binary with the whole computed array. I believe this is not a theoretical issue; I've observed it with various compilers in various contexts. constexpr does not imply static.

Of course, if the compiler can determine that the array is not used at runtime, it might not even insert code to compute it, but that's yet another issue.

If you do use such an object at run-time, and you want to indicate to the compiler that it would be worth keeping it around for the duration of the program, you should declare it as static.



回答3:

By combing the FDIS I have found three places that specify where a constexpr expression must be evaluated during translation.

Section 3.6.2 Initialization of non-local variables, paragraph 2 says if an object with static or thread local storage duration is initialized with a constexpr constructor then the constructor is evaluated during translation:

Constant initialization is performed:

  • if an object with static or thread storage duration is initialized by a constructor call, if the constructor is a constexpr constructor, if all constructor arguments are constant expressions (including conversions), and if, after function invocation substitution (7.1.5), every constructor call and full-expression in the mem-initializers is a constant expression;

Section 7.1.5 The constexpr specifier, paragraph 9 says if an object declaration includes the constexpr specifier, that object is evaluated during translation (i.e., is a literal):

A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.19). Otherwise, every full-expression that appears in its initializer shall be a constant expression. Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization shall be one of those allowed in a constant expression (5.19).

I’ve heard people argue that this paragraph leaves room for an implementation to postpone the initialization until runtime unless the effect can be detected during translation due to, say, a static_assert. That is probably not an accurate view because whether a value is initialized during translation is, under some circumstances, observable. This view is reinforced by Section 5.19 Constant expressions paragraph 4:

[ Note: Although in some contexts constant expressions must be evaluated during program translation, others may be evaluated during program execution. Since this International Standard imposes no restrictions on the accuracy of floating-point operations, it is unspecified whether the evaluation of a floating-point expression during translation yields the same result as the evaluation of the same expression (or the same operations on the same values) during program execution... — end note ]

Section 9.4.2 Static data members, paragraph 3 says if a const static data member of literal type is initialized by a constexpr function or constructor, then that function or constructor must be evaluated during translation:

If a static data member is of const literal type, its declaration in the class definition can specify a brace-orequal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. — end note ]

Interestingly, I did not find anything in the FDIS that required a constexpr expression to be evaluated if its result is used as an array dimension. I'm quite sure the standard committee expects that to be the case. But I also could have missed that in my search.

Outside of those circumstances the C++11 standard allows computations in constexpr functions and constructors to be performed during translation. But it does not require it. The computations could occur at runtime. Which computations the compiler performs during translation are, to a certain extent, a quality of implementation question.

In all three of the situations I located, the trigger for translation-time evaluation is based on the requirements of the target using the result of the constexpr call. Whether or not the arguments to the constexpr function are literal is never considered (although it is a pre-requisite for valid evaluation).

So, to get to the real point of this, it appears that constexpr evaluation during translation is triggered by:

  • The implied object argument during overload resolution (Section 13.3.1 Paragraph 3) is either constexpr or requires a literal.

I hope that's helpful to someone besides me. Thanks to everyone who contributed.



回答4:

I don't think it's forced anywhere. I had a look too, it's tricky because there's not one paper on constexpr in that list; they all seem to add/remove from the previous collection of papers.

I think the general idea is when the inputs to the constexpr function are constexpr themselves, it'll all be done at compile time; and by extension non-function constexpr statements, which are literal anyway will be run at compile time if you're using a half intelligent compiler.

If a constexpr function or constructor is called with arguments which aren't constant expressions, the call behaves as if the function were not constexpr, and the resulting value is not a constant expression.

from wikipedia

which in seem to get the info from this pdf:

constexpr functions: A constexpr function is one which is “suf- ficiently simple” so that it delivers a constant expression when called with arguments that are constant values (see §2.1).