I am trying to understand the semantic difference between these two ways of initialization:
Foo foo{x};
Foo foo = {x};
I am interested to know the difference in the following cases:
x
is of type Foo
.Foo
has a constructor that takes an argument of the same type as that of x.x
is not of type Foo
but a converting constructor is available.x
is not of type Foo
but an explicit
converting constructor is available.By difference I mean, in each case:
Foo foo{x}; // 1
Foo foo = {x}; // 2
1 is direct-list-initialization. 2 is copy-list-initialization.
Assuming that Foo
is a class type, then in most cases they do exactly the same thing, conceptually or otherwise, except that if an explicit constructor is selected during overload resolution, then #2 is ill-formed. In particular, unlike copy-initialization, copy-list-initialization does not conceptually construct a temporary.
The one exception is when x
is of type Foo
or a type derived therefrom. In this case, #1 is equivalent to Foo foo(x);
(i.e., direct-initialization) and #2 is equivalent to Foo foo = x;
(i.e., copy-initialization). The subtle difference is that overload resolution for #2 in this case considers only non-explicit constructors, rather than considering all constructors and then becoming ill-formed if an explicit constructor is selected.* This exception is added by the resolution of CWG issue 1467, which was adopted last November.
* You'd have to write some pretty tortured code for this to matter. For example:
struct X
{
X() = default;
explicit X(X&);
X(const X&);
};
int main() {
X x1;
X x2 = {x1}; // #1
X x3 = x1; // #2
}
Pre-CWG1467, line #1 is ill-formed because overload resolution selects X(X&)
, which is explicit
. Post-CWG1467, the explicit
constructor X(X&)
isn't considered, so X(const X&)
is used. Note that line #2 is always well-formed and uses X(const X&)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With