Relaxing constraints on constexpr functions (original) (raw)
ISO/IEC JTC1 SC22 WG21
N3652
Richard Smith
richard@metafoo.co.uk
2013-04-18
constexpr member functions and implicit const
This paper describes the subset of N3597selected for inclusion in C++14, relaxing a number of restrictions on constexprfunctions. These changes all received overwhelmingly strong or unopposed support under review of the Evolution Working Group. It also incorporates Option 2 ofN3598.
Accepted Changes
The changes selected by the Evolution Working Group were:
- Allow declarations within constexpr functions, other than:
- static or thread_local variables
- uninitialized variables
- Allow if and switch statements (but not goto)
- Allow all looping statements: for (including range-based for), while, and do-while
- Allow mutation of objects whose lifetime began within the constant expression evaluation.
In addition, in discussion of N3598, Option 2 was selected, which removes the rule that aconstexpr non-static member function is implicitly const.
The proposed wording also resolves core issue 1361.
Proposed wording
Change in [basic.start.init] (3.6.2)/2:
Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place.A constant initializer for an object o is an expression that is a constant expression, except that it may also invoke constexprconstructors for o and its subobjects even if those objects are of non-literal class types [ Note: such a class may have a non-trivial destructor ]. Constant initialization is performed:
- if each full-expression (including implicit conversions) that appears in the initializer of a reference with static or thread storage duration is a constant expression (5.19) and the reference is bound to an lvalue designating an object with static storage duration or to a temporary (see 12.2);
- 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 and in the brace-or-equal-initializers for non-static data members is a constant expressionand if the initialization full-expression is a constant initializer for the object;- if an object with static or thread storage duration is not initialized by a constructor call and if every full-expression that appears in its initializer is a constant expression. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. ...
Change in [basic.types] (3.9)/10:
A type is a literal type if it is:
- void; or
- a scalar type; or
- a reference type; or
- an array of literal type; or
- a class type (Clause 9) that has all of the following properties:
- it has a trivial destructor,
every constructor call and full-expression in the brace-or-equal-initializers for non-static data members (if any) is a constant expression (5.19),- it is an aggregate type (8.5.1) or has at least one constexpr constructor or constructor template that is not a copy or move constructor, and
- all of its non-static data members and base classes are of non-volatile literal types.
Change in [expr.const] (5.19)/2:
A conditional-expression e is a core 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. ]the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:
- this (5.1.1), except in a constexprfunction or a constexpr constructor that is being evaluated as part of e
[ Note: when evaluating a constant expression, function invocation substitution (7.1.5) replaces each occurrence ofthis in a constexpr member function with a pointer to the class object. ];- an invocation of a function other than a constexpr constructor for a literal class,
ora constexpr function, or an implicit invocation of a trivial destructor (12.4)[ Note Overload resolution (13.3) is applied as usual ];- an invocation of an undefined constexpr function or an undefined constexpr constructor;
an invocation of a constexpr function with arguments that, when substituted by function invocation substitution (7.1.5), do not produce a core constant expression; [ Example:constexpr const int* addr(const int& ir) { return &ir; } // OKstatic const int x = 5;constexpr const int* xp = addr(x); // OK: (const int*)&(const int&)x is an// address constant expressionconstexpr const int* tp = addr(5); // error, initializer for constexpr variable not a constant// expression; (const int*)&(const int&)5 is not a constant// expression because it takes the address of a temporary]an invocation of a constexpr constructor with arguments that, when substituted by function invocation substitution (7.1.5), do not produce all core constant expressions for the constructor calls and full-expressions in the_mem-initializer_{s} (including conversions); [ Example:int x; // not constantstruct A {constexpr A(bool b) : m(b?42:x) { }int m;};constexpr int v = A(true).m; // OK: constructor call initializes// m with the value 42 after substitutionconstexpr int w = A(false).m; // error: initializer for m is// x, which is non-constant]an invocation of a constexpr function or a constexprconstructor that would exceed the implementation-defined recursion limits (see Annex B);- an expression that would exceed the implementation-defined limits (see Annex B);
- an operation that would have undefined behavior [ Note: including, for example, signed integer overflow (Clause 5), certain pointer arithmetic (5.7), division by zero (5.6), or certain shift operations (5.8) ];
- a lambda-expression (5.1.2);
- an lvalue-to-rvalue conversion (4.1) unless it is applied to
- a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression [ Note: a string literal (2.14.5) corresponds to an array of such objects. ], or
- a non-volatile glvalue
of literal typethat refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or- a non-volatile glvalue of literal type that refers to a non-volatile
temporaryobject whose lifetimebegan within the evalution of ehas not ended, initialized with a core constant expression;- an lvalue-to-rvalue conversion (4.1)or modification (5.17, 5.2.6, 5.3.2)that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;
- an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
- it is initialized with a constant expression or
- it is a non-static data member of an
temporaryobject whose lifetimebegan within the evaluation of ehas not ended and is initialized with a core constant expression;- a conversion from type cv void * to a pointer-to-object type;
- a dynamic cast (5.2.7);
- a reinterpret_cast (5.2.10);
- a pseudo-destructor call (5.2.4);
increment or decrement operations (5.2.6, 5.3.2);- modification of an object (5.17, 5.2.6, 5.3.2) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evalution of e;
- a typeid expression (5.2.8) whose operand is of a polymorphic class type;
- a new-expression (5.3.4);
- a delete-expression (5.3.5);
- a relational (5.9) or equality (5.10) operator where the result is unspecified; or
an assignment or a compound assignment (5.17); or- a throw-expression (15.1). [ Example:
int x; // not constant struct A { constexpr A(bool b) : m(b?42:x) { } int m; }; constexpr int v = A(true).m; // OK: constructor call initializes // m with the value 42 constexpr int w = A(false).m; // error: initializer for m is // x, which is non-constant
constexpr int f1(int k) { constexpr int x = k; // error: x is not initialized by a // constant expression because lifetime of k // began outside the initializer of x return x; } constexpr int f2(int k) { int x = k; // OK: not required to be a constant expression // because x is not constexpr return x; }
constexpr int incr(int &n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // error: incr(k) is not a core constant // expression because lifetime of k // began outside the expression incr(k) return x; } constexpr int h(int k) { int x = incr(k); // OK: incr(k) is not required to be a core // constant expression return x; } constexpr int y = h(1); // OK: initializes y with the value 2 // h(1) is a core constant expression because // the lifetime of k begins inside h(1)
]
Change in [expr.const] (5.19)/4:
A literal constant expression is a prvalue core constant expression of literal type, but not pointer type (after conversions as required by the context). For a literal constant expression of array or class type, each subobject of its value shall have been initialized by a constant expression. A reference constant expression is an lvalue core constant expression that designates an object with static storage duration or a function. An address constant expression is a prvalue core constant expression (after conversions as required by the context) of type std::nullptr_t or of pointer type that evaluates to the address of an object with static storage duration, to the address of a function, or to a null pointer value. Collectively, literal constant expressions, reference constant expressions, and address constant expressions are called constant expressions.A constant expression is either a glvalue core constant expression whose value refers to an object with static storage duration or to a function, or a prvalue core constant expression whose value is an object where, for that object and each of its subobjects:
- each non-static data member of reference type refers to an object with static storage duration or to a function, and
- if the object or subobject is of pointer type, it contains the address of an object with static storage duration, the address past the end of such an object (5.7), the address of a function, or a null pointer value.
Change example in [dcl.constexpr] (7.1.5)/1:
constexpr
intvoid square(int &x); // OK: declaration 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)x) // OK: definition { square(x); } constexpr pixel small(2); // error: square not defined, so small(2) // not constant (5.19) so constexpr not satisfiedconstexpr
intvoid square(int &x) { // OK: definitionreturnx *= 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
Change in [dcl.constexpr] (7.1.5)/3:
The definition of a constexpr function shall satisfy the following constraints:
- it shall not be virtual (10.3);
- its return type shall be a literal type;
- each of its parameter types shall be a literal type;
- its function-body shall be = delete, = default, or a _compound-statement_that does not contain
s only* null statements,* _static_assert-declaration_s* typedef declarations and _alias-declaration_s that do not define classes or enumerations,* _using-declaration_s,* _using-directive_s,* and exactly one return statement.
- an asm-definition,
- a goto statement,
- a try-block, or
- a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed.
[ Example:
constexpr int square(int x)
{ return x * x; } // OK
constexpr long long_max()
{ return 2147483647; } // OKconstexpr int abs(int x){ return x < 0 ? -x : x; } // OKconstexpr void f(int x) // error: return type is void{ /* ... */ }
constexpr int abs(int x) {
if (x < 0)
x = -x;
return x; // OK
}
constexpr int first(int n) {
static int value = n; // error: variable has static storage duration
return value;
}
constexpr int uninit() {
int a; // error: variable is uninitialized
return a;
}
constexpr int prev(int x)
{ return --x; } // OKerror: use of increment
constexpr int g(int x, int n) { // OKerror: body not just "return expr"
int r = 1;
while (--n > 0) r *= x;
return r;
}
]
Change in [dcl.constexpr] (7.1.5)/4:
The definition of a constexpr constructor shall satisfy the following constraints:
- the class shall not have any virtual base classes;
- each of the parameter types shall be a literal type;
- its function-body shall not be a function-try-block; In addition, either its function-body shall be = delete, or it shall satisfy the following constraints:
- either its function-body shall be = default, or the compound-statement of its function-body shallsatisfy the constraints for a function-body of a constexpr function;
contain only* null statements,* _static_assert-declaration_s* typedef declarations and _alias-declaration_s that do not define classes or enumerations,* _using-declaration_s,* and _using-directive_s;- every non-variant non-static data member and base class sub-object shall be initialized (12.6.2);
- if the class is a non-empty union, or for each non-empty anonymous union member of a non-union class, exactly one non-static data member shall be initialized;
- every constructor involved in initializing non-static data members and base class sub-objects shall be a constexpr constructor
;.every assignment-expression that is an initializer-clause appearing directly or indirectly within a_brace-or-equal-initializer_ for a non-static data member that is not named by a mem-initializer-id shall be a constant expression.[ Example:
struct Length { explicit constexpr Length(int i = 0) : val(i) { } private: int val; };
]
Change in [dcl.constexpr] (7.1.5)/5:
Drafting note: these changes assume the resolution of core issue 1358 has been incorporated into the draft.
Function invocation substitution for a call of a constexpr function or of a constexpr constructor means:* implicitly converting each argument to the corresponding parameter type as if by copy-initialization, [ Footnote: The resulting converted value will include an lvalue-to-rvalue conversion (4.1) if the corresponding copy-initialization requires one. ]* substituting that converted expression for each use of the corresponding parameter in the function-body,* in a member function, substituting for each use of this (9.3.2) a prvalue pointer whose value is the address of the object for which the member function is called, and* in a constexpr function, implicitly converting the resulting returned expression or braced-init-list to the return type of the function as if by copy-initialization. Such substitution does not change the meaning. [ Example: ... ]For a non-template, non-defaulted constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19), the program is ill-formed; no diagnostic required. Foror a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such thatafter function invocation substitution, every constructor call and full-expression in the mem-initializers would be aan invocation of the function or constructor could be an evaluated subexpression of a coreconstant expression(5.19)(including conversions), the program is ill-formed; no diagnostic required.[ Example:
constexpr int f(bool b) { return b ? throw 0 : 0; } // OK constexpr int f() { return f(true); } // ill-formed, no diagnostic required
struct B { constexpr B(int x) : i(0) { } // x is unused int i; };
int global;
struct D : B { constexpr D() : B(global) { } // ill-formed, no diagnostic required // lvalue-to-rvalue conversion on non-constant global };
]
Change in [dcl.constexpr] (7.1.5)/6:
Drafting note: these changes assume the resolution of core issue 1358 has been incorporated into the draft.
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 orconstexpr constructor, even though a call to such a function cannot appear in a constant expression.
[ Note: If the function is a member function it will still be const as described below. — end note ]If no specialization of the template would satisfy the requirements for aconstexpr function or constexpr constructor when considered as a non-template function or constructor, the template is ill-formed; no diagnostic required.
Change in [dcl.constexpr] (7.1.5)/8:
A constexpr specifier for a non-static member function that is not a constructor declares that member function to be const (9.3.1). [ Note:The constexpr specifier has noothereffect on thefunctiontype of a constexpr function or a constexpr constructor.— end note ] The keywordconst is ignored if it appears in the cv-qualifier-seq of the function declarator of the declaration of such a member function.The class of whichthata constexpr function is a member shall be a literal type (3.9). [ Example:class debug_flag { public: explicit debug_flag(bool); constexpr bool is_on() const; // error: debug_flag not // literal type private: bool flag; }; constexpr int bar(int x, int y) // OK { return x + y + x*y; } // ... int bar(int x, int y) // error: redefinition of bar { return x * 2 + 3 * y; }
]
Change in [dcl.constexpr] (7.1.5)/9:
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, or if a constexpr specifier is used in a reference declaration, every full-expression that appears in its initializer shall be a constant expression.[ Note: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).is part of such a full-expression ][ Example:struct pixel { int x, y; }; constexpr pixel ur = { 1294, 1024 }; // OK constexpr pixel origin; // error: initializer missing
]
Change in [class.copy] 12.8/26:
A copy/move assignment operator that is defaulted and not defined as deleted is implicitly defined when it is odr-used (3.2) (e.g., when it is selected by overload resolution to assign to an object of its class type) or when it is explicitly defaulted after its first declaration.The implicitly-defined copy/move assignment operator is constexpr if
- X is a literal type, and
- the assignment operator selected to copy/move each direct base class is aconstexpr function, and
- for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is aconstexpr function.
Add new item to Clause B:
Full-expressions evaluated within a core constant expression [1,048,576].
Add new subclause after C.2 to Clause C:
C++ and ISO C++ 2011
This subclause lists the differences between C++ and ISO C++ 2011 (ISO/IEC 14882:2011, Programming Languages -- C++), by the chapters of this document.
Add a new subclause to the newly-added subclause:
Clause 7: declarations
7.1.5
Change: constexpr non-static member functions are not implicitly const member functions.
Rationale: Necessary to allow constexpr member functions to mutate the object.
Effect on original feature: Valid C++ 2011 may fail to compile in this International Standard. For example, the following code is valid in C++ 2011 but invalid in this International Standard because it declares the same member function twice with different return types:struct S { constexpr const int &f(); int &f(); };
Acknowledgements
Thanks to Jens Maurer for assistance in preparing this wording, and to Bjarne Stroustrup and Gabriel Dos Reis for guidance and encouragement.