7

In C++17, consider a case where S is a struct with a deleted default constructor and a float member, when S is initialized with empty braces, is the float member guaranteed by the standard to be zero-initialized?

struct A {
  int x{};
};

struct S
{
  S() = delete;
 
  A a;
  float b;
};

int main()
{
  auto s = S{}; // Is s.b guaranteed to be zero?
}

In my opinion, cppreference.com is not clear, saying both that:

If the number of initializer clauses is less than the number of members and basesor initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) copy-initialized from empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.

(from here), which implies that b is guaranteed to be zero

In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.

(from here)

which implies that b is not guaranteed to be zero.

There is also a discussion that seems to imply that while not guaranteed, all known compiler zero-initialize anyway:

The standard specifies that zero-initialization is not performed when the class has a user-provided or deleted default constructor, even if that default constructor is not selected by overload resolution. All known compilers performs additional zero-initialization if a non-deleted defaulted default constructor is selected.

Related to Why does aggregate initialization not work anymore since C++20 if a constructor is explicitly defaulted or deleted?

fouronnes
  • 3,838
  • 23
  • 41
  • I don't know... Is the delete not supposed to prevent default construction? Or is it still the case that a stupid C++ rule allows side-stepping the deletion? – Johannes Schaub - litb Oct 18 '21 at 08:39
  • The provided example compiles. It does not call the default construtor but does aggregate initialization – fouronnes Oct 18 '21 at 08:42
  • 2
    C++20 fixes that stupid rule – StoryTeller - Unslander Monica Oct 18 '21 at 08:42
  • BTW: your code does not compile with the c++20 standard. – Jabberwocky Oct 18 '21 at 08:43
  • That's great news! My question is explicitly for C++17 though. – fouronnes Oct 18 '21 at 08:43
  • 1
    @StoryTeller-UnslanderMonica Do you have a link about the C++20 fix? – fouronnes Oct 18 '21 at 08:46
  • 3
    This is the paper that was voted into C++20 https://wg21.link/P1008R1 – StoryTeller - Unslander Monica Oct 18 '21 at 08:48
  • The second quote comes from the value-initialization page, which says that your structure will be aggregate-initialized. So that means the first quote is the one that details how the structure will be initialized. And that quote says that the members will be [list-initialized](https://en.cppreference.com/w/cpp/language/list_initialization). And since no list is provided, the members will be individually value-initialized (or aggregate-initialized, depending on type). So `b` will be value-initialized and zero. – Some programmer dude Oct 18 '21 at 08:55
  • 1
    @StoryTeller-UnslanderMonica thanks for the link. I've discovered the `explicit` trick and answered myself thanks to you. Technically I'm still looking for the answer about whether b will be zero initialized or not, but practically the explicit trick solves my real problem! – fouronnes Oct 18 '21 at 08:56
  • 1
    Happy to help. I feel I should point out that this obscure quirk did have one upside. It allowed you to write aggregates that did not permit indeterminate values. Consider https://wandbox.org/permlink/aq1t3bMV9ORogvMy - And notice how only the default (and problematic) initialization is disabled. I still agree that the rule was too arcane and weird to stay, but it wasn't all bad. – StoryTeller - Unslander Monica Oct 18 '21 at 08:59
  • Oh I see. That's actually pretty nice. In C++20 you'd need to manually provide the trivial constructor that forwards the arguments from the initializer list to mimick aggretate initialization. I see. – fouronnes Oct 18 '21 at 09:14
  • @fouronnes [The fickle aggregate](https://dfrib.github.io/the-fickle-aggregate/). – dfrib Oct 18 '21 at 12:31

2 Answers2

6

This is a quirk of C++ that is fixed in C++20. In the meantime you can add explicit to the deleted default constructor to force the struct to become non-aggregate, and make your code a guaranteed compile error:

struct A {
  int x{};
};

struct S
{
  explicit S() = delete;
 
  const A a;
  const float b;
};

int main()
{
  auto s = S{}; // error: call to deleted constructor of 'S'
}
fouronnes
  • 3,838
  • 23
  • 41
5

Because S is an aggregate, S{} will perform aggregate initialization. The rule in the standard about how members are initialized when there are no initializers in the list is basically what you cited:

  • If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
  • Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).

So for b, that's the equivalent of float b = {};. Per the rules of list initialization, we have to get all the way down to 3.10:

Otherwise, if the initializer list has no elements, the object is value-initialized.

And value initialization will initialize a float to 0.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982