Before C++11, we can do copy initialization by writing something like A a = 1;
which is more or less equivalent to A a = A(1);
. That is, a temporary is first created and then a copy ctor is invoked. Regardless of copy elision, this must be so conceptually and the copy ctor must be accessible.
With list initialization in C++11, we can do a copy list initialization by writing A a = {1, 2};
. In my opinion, this should be more or less equivalent to A a = A(1, 2);
. However, on GCC and clang, A a = {1, 2}
compiles even when the copy and move ctor are inaccessible (by declaring as private). Still, A a = 1;
does not compile on GCC or clang if the corresponding copy/move ctor is inaccessible. So, A a = {1, 2};
seems more or less equivalent to A a{1, 2};
which is direct list initialization. The difference between this and the real direct list initialization is that A a = {1, 2};
does not compile if the ctor that takes two ints are explicit. In this aspect, A a = {1, 2};
resembles copy initialization.
So, my question is: what is the exact semantics of expressions like A a = {1, 2};
conceptually? By conceptually, copy elision do not stay in the way.
In other words, a good compiler will not create a copy for copy-initialization when it can be avoided; instead it will just call the constructor directly -- ie, just like for direct-initialization.
In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed. [ Note: This differs from other situations (13.3.1.3, 13.3.1.4), where only converting constructors are considered for copy initialization.
how many times a copy-constructor is called, according to me its 5 but answer is 7.
However, the copy constructor for an exception object still must not throw an exception because compilers are not required to elide the copy constructor call in all situations, and common implementations of std::exception_ptr will call a copy constructor even if it can be elided from a throw expression.
The standard describes it pretty well; [dcl.init.list]/3:
List-initialization of an object or reference of type
T
is defined as follows:
- [...]
- Otherwise, 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). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
[over.match.list] (emphasis mine):
When objects of non-aggregate class type
T
are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class
T
and the argument list consists of the initializer list as a single argument.If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class
T
and the argument list consists of the elements of the initializer list.If the initializer list has no elements and T has a default constructor, the first phase is omitted.
In copy-list-initialization, if anexplicit
constructor is chosen, the initialization is ill-formed.
Hence, if no initializer-list constructor is found (as in your case), the elements of the initializer list constitute the arguments for the constructor call.
In fact, the only difference of direct-list-initialization and copy-list-initialization is covered by the last, bolded sentence.
This is one of the advantages of list-initialization: It doesn't necessitate the presence of a special member function that is not gonna be used anyway.
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