Question

I'm trying to nail down the differences between N3337 §8.5p7 (C++11) and N3797 §8.5p8 (post C++11) that deal with value-initialization.

N3337 §8.5p7:

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
  • if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
  • if T is an array type, then each element is value-initialized;
  • otherwise, the object is zero-initialized.

An object that is value-initialized is deemed to be constructed and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed,” etc., even if no constructor is invoked for the object’s initialization.

N3797 §8.5p8:

To value-initialize an object of type T means:

  • if T is a (possibly cv-qualified) class type (Clause 9) with either no default constructor (12.1) or a default constructor that is user-provided or deleted, then the object is default-initialized;
  • if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is zero-initialized and the semantic constraints for default-initialization are checked, and if T has a non-trivial default constructor, the object is default-initialized;
  • if T is an array type, then each element is value-initialized;
  • otherwise, the object is zero-initialized.

An object that is value-initialized is deemed to be constructed and thus subject to provisions of this International Standard applying to “constructed” objects, objects “for which the constructor has completed,” etc., even if no constructor is invoked for the object’s initialization.

Given these two rules the snippet below should give different results:

#include <iostream>
struct Base {
    int i;

    Base(int i):i(i) {}
    Base():i(10) {}
};

struct Derived : public Base {
    int j;

    Derived(int j):Base(j), j(j) {}
    Derived()=default;
};

int main() {
    Derived d{};
    std::cout << "d.i = " << d.i << "  " << "d.j = " << d.j << '\n';
}

as follows:

  1. According to N3337, the default constructor for Derived is called, as Derived has a user-provided constructor. The default constructor for Derived calls Base default constructor, which initializes Derived::i = 10, leaving Derived::j unitialized.
  2. From N3797, as Derived has no user-provided default constructor, nor a deleted default constructor, the second bullet point applies. That is, Derived is zero-initialized, i.e., both Derived::i and Derived::j are initialized with 0 and the object d is default-initialized, which leaves Derived::i = 10.

Although my knowledge of Unixes is minimum, I've been trying to replicate these two cases, using different flags for the compilers clang++ and g++, by trial and error, in Coliru, to no avail. The results so far, all printed d.i = 10 d.j = 0 without warnings.

Was it helpful?

Solution

The program in the OP cannot distinguish if d.j is being initialized to 0 or if it is uninitialized and coincidentally happens to be 0. This would be clear if the Derived object in question were to be created in memory that is already initialized to a known non-zero value, say with placement new:

 Derived d{42};        // d.i and d.j are both 42.
 ::new (&d) Derived{}; // d.i is 0, d.j is 0 per N3797 or 42 per N3337.

As dyp says in his comment, compilers typically track changes due to defects in the standard (as opposed to new features) and include them in their support for a particular standard revision. There is likely no compiler that compiles exactly the language as specified in any given standard document given that the standards are constantly in flux. When you tell, e.g., clang 3.4 to compile C++11 the language it actually implements is "the portion of C++11 plus pertinent defect resolutions that we had implemented (IIRC all of it for 3.4) in time for the 3.4 release."

The particular change to the value-initialization wording that the OP asks about happened in the resolution of Core Working Group (CWG) Defect Report (DR) number 1301 which also addressed DR1324 and DR1368. As a defect resolution, compilers would then have reason to implement the change.

Analysis with various compilers and versions (mostly performed by the OP) demonstrates:

In summary, there's no way to force a compiler to perform exactly as specified, but we can usually determine what's going on anyway with some careful analysis.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top