Issues with Constexpr

ISO/IEC JTC1 SC22 WG21 N2826 = 09-0016 - 2009-02-05

Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
William M. Miller, Edison Design Group, wmm@edg.com

Problems

Two issues have been raised with respect to the declaration, definition, and use of constexpr functions. This paper addresses those issues based on concensus of the core working group in September 2008.

Core issue 695: Compile-time calculation errors in constexpr functions

Section: 5 [expr] Status: drafting Submitter: Mike Miller Date: 9 June, 2008

Evaluating an expression like 1/0 is intended to produce undefined behavior during the execution of a program but be ill-formed at compile time. The wording for this is in 5 [expr] paragraph 4:

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression appears where an integral constant expression is required (5.19 [expr.const]), in which case the program is ill-formed.

The formulation "appears where an integral constant expression is required" is intended as an acceptable Standardese circumlocution for "evaluated at compile time," a concept that is not directly defined by the Standard. It is not clear that this formulation adequately covers constexpr functions.

Notes from the September, 2008 meeting:

The CWG felt that the concept of "compile-time evaluation" needs to be defined for use in discussing constexpr functions. There is a tension between wanting to diagnose errors at compile time versus not diagnosing errors that will not actually occur at runtime. In this context, a constexpr function might never be invoked, either in a constant expression context or at runtime, although the requirement that the expression in a constexpr function be a potential constant expression could be interpreted as triggering the provisions of 5 [expr] paragraph 4.

There are also contexts in which it is not known in advance whether an expression must be constant or not, notably in the initializer of a const integer variable, where the nature of the initializer determines whether the variable can be used in constant expressions or not. In such a case, it is not clear whether an erroneous expression should be considered ill-formed or simply non-constant (and thus subject to runtime undefined behavior, if it is ever evaluated). The consensus of the CWG was that an expression like 1/0 should simply be considered non-constant; any diagnostic would result from the use of the expression in a context requiring a constant expression.

Core issue 699: Must constexpr member functions be defined in the class member-specification?

Section: 7.1.5 [dcl.constexpr] Status: drafting Submitter: Mike Miller Date: 26 June, 2008

According to 7.1.5 [dcl.constexpr] paragraph 1,

The constexpr specifier shall be applied only to the definition of an object, function, or function template, or to the declaration of a static data member of a literal type (3.9 [basic.types]).

As a result, a constexpr member function cannot be simply declared in the class member-specification and defined later; it must be defined in its initial declaration.

This restriction was not part of the initial proposal but was added during the CWG review. However, the original intent is still visible in some of the wording in 7.1.5 [dcl.constexpr]. For example, paragraph 2 refers to applying the constexpr specifier to the "declaration" and not the "definition" of a function or constructor. Although that is formally correct, as definitions are also declarations, it could be confusing. Also, the example in paragraph 6 reads,


class debug_flag {
  public:
    explicit debug_flag(bool);
    constexpr bool is_on();    // error: debug_flag not literal type
    ...

when the proximate error is that is_on is only declared, not defined. There are also many occurrences of the constexpr specifier in the library clauses where the member function is only declared, not defined.

It's not clear how much simplification is gained by this restriction. There are reasons for defining ordinary inline functions outside the class member-specification (reducing the size and complexity of the class definition, separating interface from implementation, making the editing task easier if program evolution results in an inline function being made non-inline, etc.) that would presumably apply to constexpr member functions as well. It seems feasible to allow separate declaration and definition of a constexpr function; it would simply not be permitted to use it in a constant expression before the definition is seen (although it could presumably still be used in non-constant expressions in that region, like an ordinary inline function).

If the prohibition were relaxed to allow separate declaration and definition of constexpr member functions, some questions would need to be answered, such as whether the constexpr specifier must appear on both declaration and definition (the inline specifier need not). If it can be omitted in one or the other, there's a usability issue regarding the fact that constexpr implies const; the const qualifier would need to be specified explicitly in the declaration in which constexpr was omitted.

If the current restriction is kept, the library clauses should state in an introduction that a non-defining declaration of a constexpr member function should be considered "for exposition only" and not literal code.

Notes from the September, 2008 meeting:

In addition to the original issues described above, the question has arisen whether recursive constexpr functions are or should be permitted. Although they were originally desired by the proposers of the feature, they were prohibited out of an abundance of caution. However, the wording that specified the prohibition was changed during the review process, inadvertently permitting them.

The CWG felt that there are sufficient use cases for recursion that it should not be forbidden (although a new minimum for recursion depth should be added to Annex B [implimits]). If mutual recursion is to be allowed, forward declaration of constexpr functions must also be permitted (answering the original question in this issue). A call to an undefined constexpr function in the body of a constexpr function should be diagnosed when the outer constexpr function is invoked in a context requiring a constant expression; in all other contexts, a call to an undefined constexpr function should be treated as a normal runtime function call, just as if it had been invoked with non-constant arguments.

Message c++std-core-13310: Constexpr Recursion?

Submitter: Lawrence Crowl Date: 8 September, 2008

The working draft does not indicate whether or not constexpr recursion is permitted. Is it intended to be permitted? My reading of the draft indicates that it is permitted, but such a powerful implication probably needs a note.


constexpr unsigned int factorial( unsigned int n )
{ return n==0 ? 1 : n * factorial( n-1 ); }

Comments

The text to say "evaluated at translation time" does not seem necessary given the proposed resolution of making undefined results be "not a constant expression".

Proposed Wording

We propose the following changes to the working draft.

5 Expressions [expr]

Edit paragraph 2 as follows.

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined, unless such an expression appears where an integral constant expression is required (5.19), in which case the program is ill-formed. [Note: most existing implementations of C++ ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all floating point exceptions vary among machines, and is usually adjustable by a library function. —end note]

5.19 Constant expressions [expr.const]

Edit paragraph 2 as follows.

A conditional-expression is a constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional (5.16) operations that are not evaluated are not considered [Note: an overloaded operator invokes a function. —end note]:

7.1.5 The constexpr specifier [dcl.constexpr]

Edit paragraph 1 as follows.

The constexpr specifier shall be applied only to the definition of an object, the declaration of a function, function or function template, or to the declaration of a static data member of an effective literal type (3.9). If any declaration of a function or function template has the constexpr specifier, then all its declarations shall contain the constexpr specifier. [Note: An explicit specialization of a function template is not a declaration of that template and can thus differ from the template declaration with respect to the constexpr specifier. —end note] [Note: function parameters cannot be declared constexpr. —end note] [Example:


constexpr int square(int x); // OK, declaration
constexpr int square(int x) { // OK
  return x * x;
}
constexpr int bufsz = 1024; // OK, definition
constexpr struct pixel { // error: pixel is a type
  int x;
  int y;
  constexpr pixel(int); // OK, declaration
};
constexpr pixel::pixel(int a)
  : x(square(a)), y(square(a)) { } // OK, definition
constexpr pixel small(2); // error: square not defined,
  // so small(2) not constant (5.19), so constexpr not satisfied
constexpr int square(int x) { // OK, definition
  return x * x;
}
constexpr pixel large(4); // OK, square defined
int next(constexpr int x) { // error: not for parameters
  return x + 1;
}
extern constexpr int memsz; // error: not a definition

end example]

17.6.5.5++ constexpr function declarations [constexpr.dcl]

After section 17.6.5.5 Member functions [member.functions], add the following section.

Implementations shall provide definitions for any non-defining declarations of constexpr functions and constructors within the associated header files.

Annex B (informative) Implementation quantities [implimits]

Edit paragraph 2 as follows.