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

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};
}

Related

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

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 [..]

c++11 initialization T{p, ...} vs T = {p, ...} [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};
}

C++11: in-class initializaton with "= {}" doesn't work with explicit constructor

In C++11 we can do in-class initialization using a "brace-or-equal-initializer" (words from the standard) like this:
struct Foo
{
/*explicit*/ Foo(int) {}
};
struct Bar
{
Foo foo = { 42 };
};
But if we un-comment explicit, it no longer compiles. GCC 4.7 and 4.9 say this:
error: converting to ‘Foo’ from initializer list would use explicit constructor ‘Foo::Foo(int)’
I found this surprising. Is it really the intention of the C++11 standard that this code doesn't compile?
Removing the = fixes it: Foo foo { 42 }; but I personally find this harder to explain to people who have been used to the form with = for decades, and since the standard refers to a "brace-or-equal-initializer" it's not obvious why the good old way doesn't work in this scenario.
I can't explain the rationale behind this, but I can repeat the obvious.
I found this surprising. Is it really the intention of the C++11
standard that this code doesn't compile?
§13.3.1.7
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed.
Removing the = fixes it: Foo foo { 42 }; but I personally find this
harder to explain to people who have been used to the form with = for
decades, and since the standard refers to a
"brace-or-equal-initializer" it's not obvious why the good old way
doesn't work in this scenario.
Foo foo { 42 } is direct initialization, whereas the equal sign (with braces) makes it copy-list-initialization. Another answer reasons that because compilation fails for copy-initialization (equal sign without braces), then it shouldn't be surprising that it also fails for copy-list-initialization, but the two fail for different reasons.
cppreference:
Direct-initialization is more permissive than copy-initialization:
copy-initialization only considers non-explicit constructors and
user-defined conversion functions, while direct-initialization
considers all constructors and implicit conversion sequences.
And their page on the explicit specifier:
Specifies constructors and (since C++11) conversion
operators that don't allow implicit conversions or
copy-initialization.
On the other hand, for copy-list-initialization:
T object = {arg1, arg2, ...}; (10)
10) on the right-hand-side of the equals sign (similar to copy-initialization)
Otherwise, the constructors of T are considered, in two phases:
If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that
consists of the elements of the braced-init-list, with the restriction
that only non-narrowing conversions are allowed. If this stage
produces an explicit constructor as the best match for a
copy-list-initialization, compilation fails (note, in simple
copy-initialization, explicit constructors are not considered at all)
As discussed in What could go wrong if copy-list-initialization allowed explicit constructors?, the compilation fails because the explicit constructor is selected but is not allowed to be used.
If Foo(int) is explicit, then this won't compile also:
Foo foo = 42;
So for "people who have been used to the form with = for decades" it won't be a surprise that the form with {} doesn't compile either.
widget w = {x};
This is called “copy list initialization.” It means the same as widget w{x}; except that explicit constructors cannot be used. It’s guaranteed that only a single constructor is called.
From http://herbsutter.com/2013/05/09/gotw-1-solution/
See the rest of the article for a more detailed discussion on the various ways you can initialise an object.

List initialization and copy elision

Consider the following example:
#include <cstdlib>
struct A
{
A(int, char*){};
A(const A&){ printf("copy-ctor\n"); }
};
int main()
{
A x = A(5, nullptr);
}
According to 8.5.16 (of C++11 standard) the line
A x = A(5, nullptr);
is treated as
A x(A(5, nullptr));
(i.e. a temporary object of type A is created and passed to copy-ctor of type A to initialize an x). Then according to 12.8.31 compiler is allowed (but is not forced) to perform an optimization called "copy elision" to eliminate the creation of a temporary of type A which effectively makes that line of code to become
A x(5, nullptr);
(i.e. no temporaries created, no copy-ctors called).
Now, suppose I use a list-initialization in the example above like this:
A x = {5, nullptr}; // (1)
or
A x = A{5, nullptr}; // (2)
Can someone please quote the appropriate pharagraphs from C++11 standard that confirm or deny that (1) and/or (2) would always (i.e. not only when compiler can do "copy elision" optimization) be treated as
A x(5, nullptr);
(i.e. first constructor of A is being directly called, no temporaries created, no copying of objects of type A is performed).
This answer is apparently wrong, which surprised me to learn. See the comments. I think the first and fourth bullet points of [dcl.init.list]/3 are what mean (1) invokes a constructor (or performs aggregate init) directly, without a temporary.
There is nothing in the standard that guarantees that (1) and (2) avoid a temporary. They are both copy-initialization, (1) is copy-list-initialization as defined by [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.
In both cases it is copy-initialization, and [dcl.init] says that may involve a move (which can be elided).
8.5/14,15:
The initialization that occurs in the form
T x = a;
[...] is called copy-initialization.
The initialization that occurs in the forms
T x(a);
T x{a};
[...] is called direct-initialization.
If your compiler is not smart enough to always elide the temporary, then to ensure no temporary with list-initialization you can use direct-list-initialization, i.e.
A x{5, nullptr};

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};
}