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
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.
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.
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.
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.
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.
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 ); }
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".
We propose the following changes to the working draft.
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]
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]:
- ...
- an invocation of a function other than a constexpr function or a constexpr constructor [Note: overload resolution (13.3) is applied as usual —end note]
- a direct or indirect invocation of an undefined constexpr function or a undefined constexpr constructor outside the definition of a constexpr function or a constexpr constructor
- a result that is not mathematically defined or not in the range of representable values for its type
- ...
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 afunction,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 theconstexpr
specifier, then all its declarations shall contain theconstexpr
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 theconstexpr
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]
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.
Edit paragraph 2 as follows.
- ...
- Nested external specifications [1 024].
- Recursive constexpr function invocations [512].
- Template arguments in a template declaration [1 024].
- Recursively nested template instantiations [17].
- Recursively nested implicit concept map definitions [1 024].
- ...