Jens Maurer

N3218: Core Issue 1125: Unclear definition of "potential constant expression" (DE 8, GB 26)

This paper presents the detailed changes to the C++ Working Draft N3126 to address core issue 1125, i.e. FCD comment DE 8. The definition of "function invocation substitution" in the changes below also resolves core issue 1127, i.e. FCD comment GB 26.

Change in 3.6.2 basic.start.init paragraph 2 as indicated:

Constant initialization is performed:
Change in 5.19 expr.const paragraph 2:
Remove 5.19 expr.const paragraph 6 entirely:
An expression is a potential constant expression if it is a constant expression when all occurrences of function parameters are replaced as follows:
Change in 7.1.5 dcl.constexpr paragraph 3 as indicated:
[ Example: ... ]
Change 7.1.5 dcl.constexpr paragraph 4 as indicated:
A trivial copy/move constructor is also a constexpr constexpr constructor. [ Example: ... ]
In 7.1.5 dcl.constexpr, add a new paragraph after the current paragraph 4:
Function invocation substitution for a call of a constexpr function or of a constexpr constructor means to convert implicitly each argument expression to the corresponding parameter type as if by copy-initialization [ Footnote: The resulting converted value will include an lvalue-to-rvalue conversion (4.1 conv.lval) if the corresponding copy-initialization requires one. --end footnote], to substitute that converted expression for each use of the corresponding parameter in the function-body, and, for constexpr functions, to convert implicitly the resulting expression to the return type of the function. Such substitution does not change the meaning. [ Example:
constexpr int f(void *) { return 0; }
constexpr int f(...) { return 1; }
constexpr int g1() { return f(0); }         // calls f(void *)
constexpr int g2(int n) { return f(n); }    // calls f(...) even for n == 0
constexpr int g3(int n) { return f(n*0); }  // calls f(...)

namespace N {
  constexpr int c = 5;
  constexpr int h() { return c; }
constexpr int c = 0;
constexpr int g4() { return N::h(); }  // value is 5, "c" is not looked up again after the substitution
] For a constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19 expr.const), the program is ill-formed; no diagnostic required. For a 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 constant expression (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() { throw 0; }         // 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 7.1.5 dcl.constexpr paragraph 8 as indicated:
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, the constructor shall be a constexpr constructor and every argument to the constructor shall be a constant expression that call shall be a constant expression (5.19 expr.const). 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 expr.const). [ Example:
struct pixel {
  int x, y;
constexpr pixel ur = { 1294, 1024 };// OK
constexpr pixel origin;             // error: initializer missing