Object Initialization Syntax in C++ ( T obj = {...} vs T obj{...} ) - c++

What is the difference between the two forms of initialization, T obj = {…} and T obj{…}?
I initially thought T obj = {…} was shorthand for T obj = T{…} where a temporary object is copied into our new object. This, although doesn't execute a copy constructor (copy elision), requires its existence and access to it. But when I blocked copy constructor access in this particular class by making the constructor private, there was no error.
This means that there is no copy mechanism involved. So what's the function of the '=' symbol?
I have referred to the following question but was dissatisfied because of the absence of an explanation:
Is C++11 Uniform Initialization a replacement for the old style syntax?
EDIT: On a similar note, is there a difference between int arr[]{…} and int arr[] = {…}? I am asking this to see if I can bring out the contrast between uniform initialization and list initialization.

These have almost exactly the same effect:
T x = { 1, 2, 3 };
T x { 1, 2, 3 };
Technically the version with = is called copy-list-initialization and the other version is direct-list-initialization but the behaviour of both of those forms is specified by the list-initialization behaviour.
The differences are:
If copy-list-initialization selects an explicit constructor then the code is ill-formed.
If T is auto, then:
copy-list-initialization deduces std::initializer_list<Type_of_element>
direct-list-initialization only allows a single element in the list, and deduces Type_of_element.
More information: Why does the standard differentiate between direct-list-initialization and copy-list-initialization?
If T is an array type then the above still applies; since array list initialization is always aggregate initialization, there is never a constructor selected and so the two versions are the same in all cases.
T obj = T{...} (excluding auto) is exactly the same as T obj{...}, since C++17, i.e. direct-list-initialization of obj. Prior to C++17 there was direct-list-initialization of a temporary, and then copy-initialization of obj from the temporary.

I think that comparing these two syntaxes is not your real question.
It seems to me that you are expecting C++17 elision to behave as did the pre-C++17 "optimisation" permitted by the standard and performed by many implementations.
In that "optimisation", though a copy constructor invocation could be elided, it had to be valid and accessible.
That is not the case with C++17 elision.
This is a true elision, whereby just writing T{} doesn't really create a T, but instead says "I want a T", and an actual temporary is "materialised" only if/when it needs to be.
Redundant utterances of this fact are effectively collapsed into one, so despite screaming "I want a T! I want a T! I want a T! I want a T!" the child still only gets one T in the end. 😉
So, in C++17, T obj = T{...} is literally equivalent to T obj{...}.
That explains the results you're seeing, and your confusion.
You may read more about this feature on cppreference.com; here's a snippet from the top of the page:
Mandatory elision of copy/move operations
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:
[..]
In the initialization of an object, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type [..]

Related

C++ object construction -- direct initialization versus using the '=' operator, are they equivalent?

In C++, are these two styles of initializing a class-object functionally equivalent, or are there some situations where they might have differing semantics and generate different code?
SomeClass foo(1,2,3);
vs
auto foo = SomeClass(1,2,3);
The 1st one is direct initialization.
The 2nd one is copy initialization, in concept foo is copy-initialized from the direct-initialized temporary SomeClass. (BTW it has nothing to do with operator=; it's initialization but not assignment.)
Because of mandatory copy elision (since C++17) they have the same effect exactly, the object is initialized by the appropriate constructor directly.
In the initialization of an object, when the initializer expression is
a prvalue of the same class type (ignoring cv-qualification) as the
variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x
Before C++17 copy elision is an optimization; even the copy/move construction might be omitted the appropriate copy/move constructor has to be usable; if not (e.g. the constructor is marked as explicit) the 2nd style won't work while the 1st is fine.
This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:

Difference between direct-list-initialization and copy-list-initialization? [duplicate]

We know that T v(x); is called direct-initialization, while T v = x; is called copy-initialization, meaning that it will construct a temporary T from x that will get copied / moved into v (which is most likely elided).
For list-initialization, the standard differentiates between two forms, depending on the context. T v{x}; is called direct-list-initialization while T v = {x}; is called copy-list-initialization:
§8.5.4 [dcl.init.list] p1
[...] List-initialization can occur in direct-initialization or copy-initialization
contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization. [...]
However, there are only two more references each in the whole standard. For direct-list-initialization, it's mentioned when creating temporaries like T{x} (§5.2.3/3). For copy-list-initialization, it's for the expression in return statements like return {x}; (§6.6.3/2).
Now, what about the following snippet?
#include <initializer_list>
struct X{
X(X const&) = delete; // no copy
X(X&&) = delete; // no move
X(std::initializer_list<int>){} // only list-init from 'int's
};
int main(){
X x = {42};
}
Normally, from the X x = expr; pattern, we expect the code to fail to compile, because the move constructor of X is defined as deleted. However, the latest versions of Clang and GCC compile the above code just fine, and after digging a bit (and finding the above quote), that seems to be correct behaviour. The standard only ever defines the behaviour for the whole of list-initialization, and doesn't differentiate between the two forms at all except for the above mentioned points. Well, atleast as far as I can see, anyways.
So, to summarize my question again:
What is the use of splitting list-initialization into its two forms if they (apparently) do the exact same thing?
Because they don't do the exact same thing. As stated in 13.3.1.7 [over.match.list]:
In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.
In short, you can only use implicit conversion in copy-list-initialization contexts.
This was explicitly added to make uniform initialization not, um, uniform. Yeah, I know how stupid that sounds, but bear with me.
In 2008, N2640 was published (PDF), taking a look at the current state of uniform initialization. It looked specifically at the difference between direct initialization (T{...}) and copy-initialization (T = {...}).
To summarize, the concern was that explicit constructors would effectively become pointless. If I have some type T that I want to be able to be constructed from an integer, but I don't want implicit conversion, I label the constructor explicit.
Then somebody does this:
T func()
{
return {1};
}
Without the current wording, this will call my explicit constructor. So what good is it to make the constructor explicit if it doesn't change much?
With the current wording, you need to at least use the name directly:
T func()
{
return T{1};
}

Why does the standard differentiate between direct-list-initialization and copy-list-initialization?

We know that T v(x); is called direct-initialization, while T v = x; is called copy-initialization, meaning that it will construct a temporary T from x that will get copied / moved into v (which is most likely elided).
For list-initialization, the standard differentiates between two forms, depending on the context. T v{x}; is called direct-list-initialization while T v = {x}; is called copy-list-initialization:
§8.5.4 [dcl.init.list] p1
[...] List-initialization can occur in direct-initialization or copy-initialization
contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization. [...]
However, there are only two more references each in the whole standard. For direct-list-initialization, it's mentioned when creating temporaries like T{x} (§5.2.3/3). For copy-list-initialization, it's for the expression in return statements like return {x}; (§6.6.3/2).
Now, what about the following snippet?
#include <initializer_list>
struct X{
X(X const&) = delete; // no copy
X(X&&) = delete; // no move
X(std::initializer_list<int>){} // only list-init from 'int's
};
int main(){
X x = {42};
}
Normally, from the X x = expr; pattern, we expect the code to fail to compile, because the move constructor of X is defined as deleted. However, the latest versions of Clang and GCC compile the above code just fine, and after digging a bit (and finding the above quote), that seems to be correct behaviour. The standard only ever defines the behaviour for the whole of list-initialization, and doesn't differentiate between the two forms at all except for the above mentioned points. Well, atleast as far as I can see, anyways.
So, to summarize my question again:
What is the use of splitting list-initialization into its two forms if they (apparently) do the exact same thing?
Because they don't do the exact same thing. As stated in 13.3.1.7 [over.match.list]:
In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.
In short, you can only use implicit conversion in copy-list-initialization contexts.
This was explicitly added to make uniform initialization not, um, uniform. Yeah, I know how stupid that sounds, but bear with me.
In 2008, N2640 was published (PDF), taking a look at the current state of uniform initialization. It looked specifically at the difference between direct initialization (T{...}) and copy-initialization (T = {...}).
To summarize, the concern was that explicit constructors would effectively become pointless. If I have some type T that I want to be able to be constructed from an integer, but I don't want implicit conversion, I label the constructor explicit.
Then somebody does this:
T func()
{
return {1};
}
Without the current wording, this will call my explicit constructor. So what good is it to make the constructor explicit if it doesn't change much?
With the current wording, you need to at least use the name directly:
T func()
{
return T{1};
}

initializing a non-copyable member (or other object) in-place from a factory function

A class must have a valid copy or move constructor for any of this syntax to be legal:
C x = factory();
C y( factory() );
C z{ factory() };
In C++03 it was fairly common to rely on copy elision to prevent the compiler from touching the copy constructor. Every class has a valid copy constructor signature regardless of whether a definition exists.
In C++11 a non-copyable type should define C( C const & ) = delete;, rendering any reference to the function invalid regardless of use (same for non-moveable). (C++11 §8.4.3/2). GCC, for one, will complain when trying to return such an object by value. Copy elision ceases to help.
Fortunately, we also have new syntax to express intent instead of relying on a loophole. The factory function can return a braced-init-list to construct the result temporary in-place:
C factory() {
return { arg1, 2, "arg3" }; // calls C::C( whatever ), no copy
}
Edit: If there's any doubt, this return statement is parsed as follows:
6.6.3/2: "A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list."
8.5.4/1: "list-initialization in a copy-initialization context is called copy-list-initialization." ¶3: "if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7)."
Do not be misled by the name copy-list-initialization. 8.5:
13: The form of initialization (using parentheses or =) is generally insignificant, but does matter when the
initializer or the entity being initialized has a class type; see below. If the entity being initialized does not
have class type, the expression-list in a parenthesized initializer shall be a single expression.
14: The initialization that occurs in the form
T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.
Both copy-initialization and its alternative, direct-initialization, always defer to list-initialization when the initializer is a braced-init-list. There is no semantic effect in adding the =, which is one reason list-initialization is informally called uniform initialization.
There are differences: direct-initialization may invoke an explicit constructor, unlike copy-initialization. Copy-initialization initializes a temporary and copies it to initialize the object, when converting.
The specification of copy-list-initialization for return { list } statements merely specifies the exact equivalent syntax to be temp T = { list };, where = denotes copy-initialization. It does not immediately imply that a copy constructor is invoked.
-- End edit.
The function result can then be received into an rvalue reference to prevent copying the temporary to a local:
C && x = factory(); // applies to other initialization syntax
The question is, how to initialize a nonstatic member from a factory function returning non-copyable, non-moveable type? The reference trick doesn't work because a reference member doesn't extend the lifetime of a temporary.
Note, I'm not considering aggregate-initialization. This is about defining a constructor.
On your main question:
The question is, how to initialize a nonstatic member from a factory function returning non-copyable, non-moveable type?
You don't.
Your problem is that you are trying to conflate two things: how the return value is generated and how the return value is used at the call site. These two things don't connect to each other. Remember: the definition of a function cannot affect how it is used (in terms of language), since that definition is not necessarily available to the compiler. Therefore, C++ does not allow the way the return value was generated to affect anything (outside of elision, which is an optimization, not a language requirement).
To put it another way, this:
C c = {...};
Is different from this:
C c = [&]() -> C {return {...};}()
You have a function which returns a type by value. It is returning a prvalue expression of type C. If you want to store this value, thus giving it a name, you have exactly two options:
Store it as a const& or &&. This will extend the lifetime of the temporary to the lifetime of the control block. You can't do that with member variables; it can only be done with automatic variables in functions.
Copy/move it into a value. You can do this with a member variable, but it obviously requires the type to be copyable or moveable.
These are the only options C++ makes available to you if you want to store a prvalue expression. So you can either make the type moveable or return a freshly allocated pointer to memory and store that instead of a value.
This limitation is a big part of the reason why moving was created in the first place: to be able to pass things by value and avoid expensive copies. The language couldn't be changed to force elision of return values. So instead, they reduced the cost in many cases.
Issues like this were among the prime motivations for the change in C++17 to allow these initializations (and exclude the copies from the language, not merely as an optimization).

Do these two C++ initializer syntaxes ever differ in semantics?

Assume that the following code is legal code that compiles properly, that T is a type name, and that x is the name of a variable.
Syntax one:
T a(x);
Syntax two:
T a = x;
Do the exact semantics of these two expressions ever differ? If so, under what circumstances?
If these two expressions ever do have different semantics I'm also really curious about which part of the standard talks about this.
Also, if there is a special case when T is the name of a scalar type (aka, int, long, double, etc...), what are the differences when T is a scalar type vs. a non-scalar type?
Yes. If the type of x is not T, then the second example expands to T a = T(x). This requires that T(T const&) is public. The first example doesn't invoke the copy constructor.
After the accessibility has been checked, the copy can be eliminated (as Tony pointed out). However, it cannot be eliminated before checking accessibility.
The difference here is between implicit and explicit construction, and there can be difference.
Imagine having a type Array with the constructor Array(size_t length), and that somewhere else, you have a function count_elements(const Array& array). The purpose of these are easily understandable, and the code seems readable enough, until you realise it will allow you to call count_elements(2000). This is not only ugly code, but will also allocate an array 2000 elements long in memory for no reason.
In addition, you may have other types that are implicitly castable to an integer, allowing you to run count_elements() on those too, giving you completely useless results at a high cost to efficiency.
What you want to do here, is declare the Array(size_t length) an explicit constructor. This will disable the implicit conversions, and Array a = 2000 will no longer be legal syntax.
This was only one example. Once you realise what the explicit keyword does, it is easy to dream up others.
From 8.5.14 (emphasis mine):
The function selected is called with the initializer expression as its argument; if the function is a constructor, the call initializes a temporary of the destination type. The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see class.temporary, class.copy.
So, whether they're equivalent is left to the implementation.
8.5.11 is also relevant, but only in confirming that there can be a difference:
-11- The form of initialization (using parentheses or =) is generally insignificant, but does matter when the entity being initialized has a class type; see below. A parenthesized initializer can be a list of expressions only when the entity being initialized has a class type.
T a(x) is direct initialization and T a = x is copy initialization.
From the standard:
8.5.11 The form of initialization (using parentheses or =) is generally insignificant, but does matter when the entity being initialized has a class type; see below. A parenthesized initializer can be a list of expressions only when the entity being initialized has a class type.
8.5.12 The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form
T x = a;
The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization and is equivalent to the form
T x(a);
The difference is that copy initialization creates a temporary object which is then used to direct-initialize. The compiler is allowed to avoid creating the temporary object:
8.5.14 ... The result of the call (which is the temporary for the constructor case) is then used to direct-initialize, according to the rules above, the object that is the destination of the copy-initialization. In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized; see 12.2, 12.8.
Copy initialization requires a non-explicit constructor and a copy constructor to be available.
In C++, when you write this:
class A {
public:
A() { ... }
};
The compiler actually generates this, depending on what your code uses:
class A {
public:
A() { ... }
~A() { ... }
A(const A& other) {...}
A& operator=(const A& other) { ... }
};
So now you can see the different semantics of the various constructors.
A a1; // default constructor
A a2(a1); // copy constructor
a2 = a1; // copy assignment operator
The copy constructors basically copy all the non-static data. They are only generated if the resulting code is legal and sane: if the compiler sees types inside the class that he doesn't know how to copy (per normal assignment rules), then the copy constructor won't get generated. This means that if the T type doesn't support constructors, or if one of the public fields of the class is const or a reference type, for instance, the generator won't create them - and the code won't build. Templates are expanded at build time, so if the resulting code isn't buildable, it'll fail. And sometimes it fails loudly and very cryptically.
If you define a constructor (or destructor) in a class, the generator won't generate a default one. This means you can override the default generated constructors. You can make them private (they're public by default), you can override them so they do nothing (useful for saving memory and avoiding side-effects), etc.