Modification of objects in constant expression contexts

Viewed 62

Consider the following code:

struct S
{
  constexpr S(){};
  constexpr S(const S &r) { *this = r; };
  constexpr S &operator=(const S &) { return *this; };
};

int main()
{
  S s1{};
  constexpr S s2 = s1; // OK
}

The above program is well-formed but I'm expecting it to be ill-formed, because [expr.const]/(5.16) says:

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

  • [..]
  • (5.16) a modification of an object ([expr.ass], [expr.post.incr], [expr.pre.incr]) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of E;
  • [..]

Given the expression E is s1. The expression E evaluates a modification of the object *this. The modification is applied to the non-volatile lvalue *this which is of literal type, and this lvalue refers to a non-volatile object which is s1 but the lifetime of s1 does not begin within the evaluation of E: That's the lifetime of the object s1 began before the evaluation of the expression E.

So I'm expecting the program is ill-formed because the "unless" part does not satisfy which means, to me, that the expression E is not a core constant expression. So what I'm missing here? Am I misreading (5.16)?

1 Answers

As Language Lawyer points out, there are no modifications in your program, however even if the constructor did modify s2, this wouldn't necessarily make the program ill-formed, because:

In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression ([expr.const]).

-- [dcl.constexpr]/7

The constructor is allowed to modify s2 because the lifetime of s2 begins within the full-expression of the initialization, and only the full-expression of the initialization is required to be a constant expression (not the initializer in isolation).

Addendum: The only modifications that are relevant for [expr.const]/5.16 are modifications to scalar objects. This is because only modifications to scalar objects actually result in accesses to the memory of the abstract machine (see also defns.access). When I say the constructor is allowed to modify s2, I mean the constructor is allowed to modify scalar subobjects of s2.

Related