[PATCH] C99 partial re-initialization behavior (DR-253)

Richard Smith richard at metafoo.co.uk
Mon Jan 12 13:46:33 PST 2015

Sorry for the delay; I've been thinking about this problem for a while and I think I now have a reasonable design for it. Consider a case like this:

  struct Q { int a, b, c; };
  Q *f();
  void g() {
    struct A { Q q; } a = { *f(), .q.b = 3 };

Here, we can't model the initialization of `a.q` as a partial merge (because we have no way to represent initializing `a.q.a` and `a.q.c` from the //same call// to `*f()`, but initializing `a.q.b` from `3`). I don't like the design of extending `InitListExpr` with a `PrevInitExpr` because it makes all users of that type need to cope with a weird special case; I'd prefer a more uniform representation.

So here's what I propose: add a `DesignatedInitUpdateExpr` that represents updating an initializer with another initializer by way of a designated initializer. This would have three members: the "base" initializer, the "update" initializer representing an initializer for a subobject of the "base", and a list of designators referring to the field of the base that is updated.

The evaluation semantics for a DIUE would be to first initialize the object using the "base" initializer, and then re-initialize the designated subobject using the "update" initializer. If the updated subobject is of a type that needs destruction, we'll need to pick between either running the destructor for the updated subobject, or rejecting designated initializations that would re-initialize a subobject with a non-trivial destructor (we may also need to take care if the updated subobject is a union member, because the initialized subobject might be a different one).

So in the above case, the initializer for `a` would be:

- An `InitListExpr` for `a`, with type `A`
- The init for field 0 (`q`) is a `DesignatedInitUpdateExpr`, with type `Q` and designator `b`
- The "base" for the DIUE is the expression `*f()`
- The "update" for the DIUE is the expression `3`

We could still use something like your `partialMergeFrom` functionality where possible, and only create `DesignatedInitUpdateExpr`s when necessary, but there are implications here: if an initializer has side-effects DIUE can preserve those side-effects even if the initializer is completely overridden, whereas partialMergeFrom will always lose the side-effects. The C standard allows either choice here, but I think we should be as consistent as we reasonably can. I suggest that we use `partialMergeFrom` wherever possible, and put the DIUE at the 'lowest' place possible within the initializer, so that we (attempt to) discard side-effects from all expressions that don't contribute to the final result. If we discard a side-effecting initializer in this way, we should issue a warning.

One other thing: `partialMergeFrom` should be a static function in `SemaInit.cpp`, not a member of `InitListExpr` (and maybe it might make more sense to integrate its functionality directly into `InitListChecker`). It's too high-level to belong on the AST.



More information about the cfe-commits mailing list