ISO/IEC JTC1 SC22 WG21
N3652
Richard Smith
[email protected]
2013-04-18

Relaxing constraints on constexpr functions

constexpr member functions and implicit const

This paper describes the subset of N3597 selected for inclusion in C++14, relaxing a number of restrictions on constexpr functions. These changes all received overwhelmingly strong or unopposed support under review of the Evolution Working Group. It also incorporates Option 2 of N3598.

Accepted Changes

The changes selected by the Evolution Working Group were:

In addition, in discussion of N3598, Option 2 was selected, which removes the rule that a constexpr 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 constexpr constructors 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: 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:

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

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 satisfied

constexpr intvoid 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 

Change in [dcl.constexpr] (7.1.5)/3:

The definition of a constexpr function shall satisfy the following constraints:

Change in [dcl.constexpr] (7.1.5)/4:

The definition of a constexpr constructor shall satisfy the following constraints:

[ 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: 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. For or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that after function invocation substitution, every constructor call and full-expression in the mem-initializers would be a an invocation of the function or constructor could be an evaluated subexpression of a core constant 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 or constexpr 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 a constexpr 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 no other effect on the function type of a constexpr function or a constexpr constructor. — end note ] The keyword const 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 which that a 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

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.