Difference between S() vs S{}?
Asked Answered
E

1

7

In the code below, is there any difference between the two assignments to value? In both cases, would value.v be default constructed, and x be initialized to 42?

struct S
{
   std::vector<int> v;
   int x = 42;
};
S value;

void foo()
{
   value = S();
   value = { };
}
Ellery answered 23/1, 2020 at 21:33 Comment(6)
Why not try it?Quarterback
I have. In MSVC I get the same behavior. However, in my target system, an embedded platform running an RTOS, I get a system crash when I use the { } syntax. It had been a challenge to track down where the specific crash was occurring because it was during system startup, before I could attach the remote debugger to the processor board. However, after changing the syntax of some of my initialization code to the older "constructor style" my board boots without incident. This has thus led me to question whether I was properly understanding the behavior of the two statements.Ellery
Steve does this help? cppinsights.io/s/bee58611Daron
Seems to confirm was I expected, which is that the result should be equivalent? That link is quite useful however, thanks!Ellery
See Initialization - cppreference.comDredger
@Quarterback trying it will not help with knowing if the behaviour is standard or compiler-dependent or undefinedAkmolinsk
S
13

S() and S{} mean the same thing in nearly all cases. But not all cases.

  • If S is not a class type, same thing: value initialization.
  • If S is a class type that is not an aggregate, still mostly means the same thing: value initialization. Except in cases like:

    struct X { X(std::initializer_list<int>); };
    auto x1 = X(); // ill-formed
    auto x2 = X{}; // ok, calls constructor
    
  • if S is an aggregate, then S() is value-initialization but S{} s aggregate-initialization. Even that means the same thing a lot of the time. But not all of the time.

Example 1: explicit default constructor makes aggregate-initialization ill-formed

struct A { explicit A(int = 0); };
struct B { A a; };

B b;        // OK
B b2 = B(); // OK
B b3{};     // error through trying to copy-list-initialize a = {}

Example 2: value initialization in some contexts does zero-initialization first

struct C { C() {} int i; };
struct D { C a; }; 

D d1{};     // d1.a.i is indeterminate
D d2 = D(); // d2.a.i is zero

In the OP example though, while S is an aggregate with an implicitly-defined default constructor - this is the interesting case. But here, there's no change in semantics with the extra zero-initialization, we're initializing x to 42 and default-constructing v either way.


Note that also while in OP, this invokes (and is intended to invoke) the move assignment operator from S{}:

value = { };

it's also possible that this could invoke a different operator entirely, since {} could end up binding "better" to some different parameter in a different assignment operator overload. std::optional has to jump through some hooks to ensure that opt = {} actually invokes the move assignment operator.

Spurlock answered 24/1, 2020 at 1:58 Comment(5)
@DavidC.Rankin Yes. Yes it is. :-(Spurlock
"If S is a class type that not an aggregate, still means the same thing" - is this true even if S has an initializer list constructor?Akmolinsk
@Akmolinsk Yes, the value-initialize bullet preceeds the std::initializer_list bullet.Spurlock
You missed the case where S has no default constructor but does have an initializer-list constructor.Mysticism
It is interesting that you mention the point regarding std::optional, because in my real world case I had several std::optional members in S!Ellery

© 2022 - 2024 — McMap. All rights reserved.