Skip to content

CWG2820 [dcl.init.general] Zero-initialization VS default constructors #40

Description

@frederick-vs-ja

Full name of submitter (unless configured in github; will be published with the issue): Jiang An

Reference (section label): [dcl.init.general]

Link to reflector thread (if any):

Issue description:

  1. A class may be default-initializable while having more than one default constructors. In such cases, implementations don't consider any unselected default constructors during value-initialization, and may perform zero-initialization.
    This strategy is slightly different from [dcl.init.general]/9 which doesn't exclude unselected defaulted constructors. Perhaps zero-initialization is desired and [dcl.init.general]/9 should be modified to match implementations.

  2. [dcl.init.general]/(9.1.2) seemly indicates that if the default constructor is defaulted (not user-provided) and non-trivial, then the object is zero-initialized and then default-initialized. The current wording might have some drawbacks and lead to implement divergence:

    • Some implementations (gcc, icc, and clang) only perform default-initialization in this case, while others (msvc) perform both operations.
    • When the default constructor is trivial, it is unobservable whether it is called after zero-initialization, so perhaps the default-initialization can be performed unconditionally.

Example:

struct Foo {
    int x;
    Foo() = default;
    template<class T>
    Foo(T = T()) {} // user-provided, also a default constructor
};

template<int N>
struct Bar {
    int y;
    Bar() requires (N >= 0) = default;
    Bar() requires (N < 0) {} // user-provided
};

static_assert(Foo().x == 0); // passes
static_assert(Bar<0>().y == 0); // passes if supported

struct Baz {
    int z;
    int w = 1;
    Baz() = default; // non-trivial
};

static_assert(Baz().z == 0); // passes on msvc, fails on gcc, icc, and clang

Suggested resolution:

There are two mutually exclusive suggested resolutions. Option A exclusively selects zero-initialization or default-initialization (matching gcc etc.), while Option B sometimes perform both forms (matching msvc, seemly the status quo).

Option A:

Change [dcl.init.general]/(9.1) as indicated:

if T is a (possibly cv-qualified) class type ([class]), the semantic constraints for default-initialization are checked, and then

  • if T has either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted there is no selected default constructor ([class.default.ctor]) for default-initialization, or the selected default constructor is non-trivial or deleted, then the object is default-initialized;
  • otherwise, 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;

Option B:

Change [dcl.init.general]/(9.1) as indicated:

if T is a (possibly cv-qualified) class type ([class]), the semantic constraints for default-initialization are checked, and then

  • if T has either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted there is no selected default constructor ([class.default.ctor]) for default-initialization, or the selected default constructor is user-provided or deleted, then the object is default-initialized;
  • otherwise, 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 then default-initialized;

Activity

  1. jensmaurer commented on May 25, 2022

    @jensmaurer
    Member

    See CWG1324 and CWG1368 (resolved by CWG1301).
    The latest edit to that paragraph was via CWG1507.

    @frederick-vs-ja , please do me a favor and double-check that the proposed edits don't revert or otherwise negatively affect those existing (and resolved) core issues, i.e. that we're not introducing a regression.

  2. xmh0511 commented on May 25, 2022

    @xmh0511

    I'm not sure whether the second initialization(i.e. default-initialization creates an object o2) will end the lifetime of the object o1 that is begun by the first initialization(i.e. zero-initialization), which is regarded as a reuse of the storage occupied by o1. Anyway, [class.ctor.general] p3 says

    An object created in this way is unnamed.

    [dcl.init.general] p20

    An object whose initialization has completed is deemed to be constructed, even if the object is of non-class type or no constructor of the object's class is invoked for the initialization.

    We do not like the double-initialization and the subsequent confusion caused by such initialization

    maybe CWG wishes to somehow remove the double-initialization to make this clear

    See cplusplus/draft#4802 and the linked questions in that issue.

  3. frederick-vs-ja commented on May 25, 2022

    @frederick-vs-ja
    Author

    @jensmaurer I'm sure that the suggested resolutions only change the conditions under which zero-initialization is performed. So existing resolutions won't be broken. Anyway, the semantic constraints for default-initialization are always needed to be checked.

    @xmh0511 I think the wording can "magically" ensure that there is only o1 and no o2, so reuse doesn't happen.

    [class.ctor.general] p3 is talking about functional notation type conversion, although it was accidently made dangling by P1787.

    Perhaps we should resolve double-initialization uniformly elsewhere, and discussion for delegating constructors may be needed.

  4. xmh0511 commented on May 25, 2022

    @xmh0511

    I think the wording can "magically" ensure that there is only o1 and no o2, so reuse doesn't happen.

    AFAIK, each initialization will construct an object. An object cannot be initialized twice. If it were, it wouldn't make sense or conform to intuitive. Again, if the initializations of the object in this issue are the zero-initialization followed by the default-initialization, which of them starts the lifetime of the object?

  5. frederick-vs-ja commented on May 27, 2022

    @frederick-vs-ja
    Author

    AFAIK, each initialization will construct an object. An object cannot be initialized twice.

    Perhaps we shouldn't apply this model to objects of trivially default constructible (and/or implicit-lifetime?) types, in order to start their lifetime as quickly as possible. Implementations can (and probably do) support zero-initialization followed by dynamic initialization, and there're probably a lot of programs depending on this.

    Again, if the initializations of the object in this issue are the zero-initialization followed by the default-initialization, which of them starts the lifetime of the object?

    IMO for objects (including subobjects) of trivially default constructible types, their lifetime should be started by zero-initialization; for other objects, zero-initialization is not sufficient to start their lifetime.

    Anyway, I think regularization or removal of double-initailziation should be handled as another issue.

  6. frederick-vs-ja commented on Nov 1, 2023

    @frederick-vs-ja
    Author

    CWG2820 partially addresses this (a weak form of Option B is proposed).

  7. changed the title [-][dcl.init.general] Zero-initialization VS default constructors[/-] [+]CWG2820 [dcl.init.general] Zero-initialization VS default constructors[/+] on Nov 1, 2023
  8. SRNissen commented on Nov 20, 2023

    @SRNissen

    I think the wording can "magically" ensure that there is only o1 and no o2, so reuse doesn't happen.

    AFAIK, each initialization will construct an object. An object cannot be initialized twice. If it were, it wouldn't make sense or conform to intuitive. Again, if the initializations of the object in this issue are the zero-initialization followed by the default-initialization, which of them starts the lifetime of the object?

    How sure are you of that?

    I only encountered the issue of default-after-zero initialization this weekend and haven't spent more than, at most, ten hours looking, but I cannot find any statement that an object cannot be initialized after it has been initialized.

    RE https://cplusplus.github.io/CWG/issues/2820.html

    The suggested change is

    otherwise, 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 then default-initialized

    Which leaves my pet scenario with a double-initialization:

    struct T
    {
        Nontrivial nt{20};
    };
    
    int main()
    {
        T t{};
    }

    The order, if I got the Standard right, is:

    • t is list-initialized
    • List initialization of a class using an empty list is value-initialization
    • T's implicitly defaulted ctor is non-trivial because of nt{20};
    • So t is zero-initialized, then default-initialized.

    And so, again, t.nt is initialized twice - first zero-initialized during the zero-initialization of t, then initialized again with the brace-or-equal-initializer during the default construction of t.

    EDIT

    I can tell you what resolution I would prefer here:

    Value initializing an object of class type will recursively value-initialize every base class and sub-object, picking their initializers in this prioritized order:

    • as specified in the member-initializer-list if available,
    • else the default-member-initializer if available,
    • else the objects default ctor if available,
    • else, if the subobject is of scalar type, integer literal zero,
    • else, the program is ill formed (diagnostic required)

    Clear. Obvious. Probably politically impossible.

    My secondary preference would be some intricate wording to indicate that value initialization does zero-initialization as before but only for sub-objects that are not mentioned member initializer lists and which do not have Default Member Initializers.

    My tertiary preference is to redefine "zero-initialization" of subobjects to set values to integer literal zero unless the sub-object is present in a member-initializer-list or a default member initializer is present, in which case zero initialization picks that value instead of zero.

    (Two and three have the same actual effect, it's just whether you say "don't zero-initialize sub-objects with other initializers" or "do zero-initialize sub-objecst with other initializers, but we'll redefine what zero-initialize means."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions