50

What's the difference between

T a(b);

and

T a = b;

and

T a = T(b);

?

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • Related information on `T a = T(b)`: [What are copy elision and return value optimization?](https://stackoverflow.com/a/12953129) – jrh May 27 '20 at 12:35

2 Answers2

41
T a( b );

is direct initialization, unless it parses as a function declaration, in which case it's a function declaration.

T a = b;

is copy initialization, which means that it works as if a temporary object is constructed on the right hand side, and that a is then copy constructed or, in C++11 and later, possibly move constructed, from that temporary.

The compiler is free to elide (remove) the temporary+copying/moving whenever it can, but a copy or move constructor, whichever would be logically used, must still be accessible and not explicit.

For example, in C++03 you cannot copy-initialize a std::ostringstream, because it doesn't have a copy constructor. In C++11 you can copy-initialize an ostringstream if the initializer is a temporary, which then results in a logical move construction (which however will usually be elided, optimized away). For example, this copy initialization declaration,

ostringstream s = ostringstream( "blah" );

… doesn't compile as C++03, because in C++03 the copy initialization invokes the class' copy constructor, which doesn't exist. It does however compile as C++11, because in C++11 the copy initialization invokes the move constructor. And while (to maintain its illusion of being a stream) a std::ostringstream can't be directly copied, it can be moved.

Another such difference: in C++03 only the copy initialization syntax supports curly braces initializer, which in C++03 you can use when T is an aggregate type such as a raw array. In C++11 the curly braces notation has been extended and generalized as a uniform initialization syntax, so it can be used also with direct initialization. And so the following direct initialization declaration,

int v[]{ 3, 1, 4, 1, 5, 9, 2, 6, 5, 4 };

… does not compile as C++03, but does compile as C++11 and later.

The = copy initialization syntax is the original initialization syntax from C.

And in C++11 and later, due to move semantics, it can be used in a much wider range of cases than in C++03, such as with a std::ostringstream.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • FredOverflow pointed out that when `b` is of type `T` then the compiler is not just free to elide the temporary+copying, it must do so. An accessible copy constructor is still required though. And then there's still a difference since the copy initialization requires an accessible copy constructor that is not `explicit`. – Cheers and hth. - Alf Dec 17 '10 at 15:07
  • I know this is old question but you mentioned in case of `T a = b` temporary is constructed from `b` on the right hand then `a` is copy constructed from that temporary .... I did test I created class with all types of constructors and when calling `T a = b` it does only one copy construction ... so what am I missing here ? – Laith Jul 10 '16 at 19:31
  • 1
    @Laith: Well, you missed “as if” in the sentence you read, and “The compiler is free to elide (remove) the temporary+copying whenever it can” in the next sentence. ;-) Note that the compiler is free to elide the copy construction even when the copy constructor has side effects. Essentially, the compiler is allowed to blindly *assume* that a copy constructor copies, and does nothing else, regardless of obvious reality. Also, since this answer was written, in 2010, we've had C++11, which changed things by introducing move semantics. A C++ copy initialization can move or copy, depending. – Cheers and hth. - Alf Jul 10 '16 at 19:47
  • Thank you, that cleared my confusion – Laith Jul 10 '16 at 19:55
  • 1
    Well, thank *you* for bring this old answer to my attention. I've now updated it. Hopefully without bringing in any gross error. :) – Cheers and hth. - Alf Jul 10 '16 at 20:19
  • Since C++17, copy-initialisation from a prvalue of the same type does not require an accessible copy or move constructor, as elision is mandatory. One effect of this is enabling the `auto instance = Type{args};` syntax for 'stick `auto`' that Herb Sutter and others have been recommending. I guess that is indeed better than `auto instance{ Type{args} };` for making the move to left-to-right declarations less cumbersome, and even without using an aesthetic argument, the copy/move was obviously unnecessary before anyway, but just required due to the broader rules in older versions of C++. – underscore_d Oct 14 '18 at 11:26
  • "The compiler is free to elide (remove) the temporary+copying/moving whenever it can, but a copy or move constructor, whichever would be logically used, must still be accessible and not explicit." -- Is there a specific explicit copy constructor that would be able to handle something like `T a = T(56)` or will this syntax only work if the copy constructor is not explicit? Why is the copy constructor's explicit status relevant in removing this copy? It seems like it doesn't call the move or copy constructor here. – jrh May 27 '20 at 12:20
  • The other thing I find a bit confusing is, pre C++11 the () initializer was the only thing available, is `T a = T(something)` acceptable as a workaround in cases where the compiler would interpret `T a(something)` as a function declaration? Or was the only workaround pre-C++11 to split up the constructor so that there's no possible ambiguity? – jrh May 27 '20 at 12:23
24
T a(b);

Calls a constructor of a that accepts b. (If b is of the same type, then the copy constructor is called).

T a = b;

a temporary object of type T is created to be constructed by b. Then the copy constructor is called(= is not an assignment in this case and the next case!).

T a = T(b);

Same as above! except that we explicitly constructed a temporary object.

Note that the standard allows total elimination of temporary copies in the second and third case. Also, if b is not of type T, then in the first case T doesn't have to have a copy constructor. In the second and third cases, even though the implementation is free to optimize the whole thing, it still requires an accessible copy constructor. IIRC the standard calls this: copy elision.

Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234
  • 3
    The practical difference between the second and third is that in the third case the relevant non-copy constructor (if used, depending on the type of `b`) is permitted to be explicit. In the second case it is not. Not sure whether that's what you meant by "except that we explicitly constructed a temporary object", or not. – Steve Jessop Dec 17 '10 at 13:07
  • Good point. Maybe I should have clarified as you pointed out, that the second doesn't work in case of `explicit` constructor. – Khaled Alshaya Dec 17 '10 at 13:11
  • in `T a(b);` suppose `b`'s type doesn't hits any constructor which accepts `b`'s type but there is some conversion operator is present in `b`'s type then that conversion is considered? – Mr.Anubis Jul 29 '12 at 15:12
  • 1
    Typo in the 1st line: `T a(b);` calls a constructor of `T`, not `a`. – starriet Aug 06 '22 at 14:41