What are the differences between the following three initializations with std::initializer_lists?
std::vector<int> a{ 2, 3, 5, 7};
std::vector<int> b( { 2, 3, 5, 7} );
std::vector<int> c = { 2, 3, 5, 7};
In the above example, std::vector is just a placeholder, but I am interested in a general answer.
Let's abstract away from std::vector. And call it T.
T t{a, b, c};
T t = { a, b, c };
T t({a, b, c});
The first two forms are list initialization (and the only difference between them is that if T is a class, for the second explicit constructors are forbidden to be called. If one is called, the program becomes ill-formed). The last form is just ordinary direct initialization as we know it from C++03:
T t(arg);
That there appears a {a, b, c} as arg means that the argument for the constructor call is a brace initializer list. This third form does not have the special handling that list initialization has. T must be a class type there, even if the braced init list has only 1 argument. I'm glad that we put clear rules before releasing C++11 in this case.
As in terms of what constructors are called for the third, let's assume
struct T {
T(int);
T(std::initializer_list<int>);
};
T t({1});
Since a direct initialization is just a call to the overloaded constructors, we can transform this to
void ctor(int);
void ctor(std::initializer_list<int>);
void ctor(T const&);
void ctor(T &&);
We can use both trailing functions, but we would need a user defined conversion if we picked these functions. To initialize the T ref parameter, list initialization will be used because this is not a direct initialization with parens (so the parameter initialization is equivalent to T ref t = { 1 }). The first two functions are exact matches. However, the Standard says that in such a case, when one function converts to std::initializer_list<T> and the other does not, then the former function wins. Therefor in this scenario, the second ctor would be used. Note that in this scenario, we will not do two-phase overload resolution with first only initializer list ctors - only list initialization will do that.
For the first two, we will use list-initialization, and it will do context dependent things. If T is an array, it will initialize an array. Take this example for a class
struct T {
T(long);
T(std::initializer_list<int>);
};
T t = { 1L };
In this case, we do two-phase overload resolution. We first only consider initializer list constructors and see if one matches, as argument we take the whole braced init list. The second ctor matches, so we pick it. We will ignore the first constructor. If we have no initializer list ctor or if none matches, we take all ctors and the elements of the initializer list
struct T {
T(long);
template<typename A = std::initializer_list<int>>
T(A);
};
T t = { 1L };
In this case we pick the first constructor, because 1L cannot be converted to std::initializer_list<int>.
In the above example, std::vector is just a placeholder, I am interested in a general answer.
How "general" of an answer do you want? Because what that means really depends on what the type you're initializing is and what constructors they have.
For example:
T a{ 2, 3, 5, 7};
T b( { 2, 3, 5, 7} );
These may be two different things. Or they may not. It depends on what constructors T has. If T has a constructor that takes a single initializer_list<int> (or some other initializer_list<U>, where U is an integral type), then both of these will call that constructor.
However, if it doesn't have that, then these two will do different things. The first, will attempt to call a constructor that takes 4 arguments that can be generated by integer literals. The second will attempt to call a constructor that takes one argument, which it will try to initialize with {2, 3, 5, 7}. This means that it will go through each one-argument constructor, figure out what the type for that argument is, and attempt to construct it with R{2, 3, 5, 7} If none of those work, then it will attempt to pass it as an initializer_list<int>. And if that doesn't work, then it fails.
initializer_list constructors always have priority.
Note that the initializer_list constructors are only in play because {2, 3, 5, 7} is a braced-init-list where every element has the same type. If you had {2, 3, 5.3, 7.9}, then it wouldn't check initializer_list constructors.
T c = { 2, 3, 5, 7};
This will behave like a, save for what kinds of conversions it will do. Since this is copy-list-initialization, it will attempt to call an initializer_list constructor. If no such constructor is available, it will attempt to call a 4-argument constructor, but it will only allow implicit conversions of its for arguments into the type parameters.
That's the only difference. It doesn't require copy/move constructors or anything (the specification only mentions copy-list-initialization in 3 places. None of them forbid it when copy/move construction is unavailable). It is almost exactly equivalent to a except for the kind of conversion it allows on its arguments.
This is why it's commonly called "uniform initialization": because it works almost the same way everywhere.
Traditionally (C++98/03), initialization like T x(T()); invoked direct initialization, and initialization like T x = T(); invoked copy initialization. When you used copy initialization, the copy ctor was required to be present available, even though it might not (i.e., usually wasn't) used.
Initializer lists kind of change that. Looking at §8.5/14 and §8.5/15 shows that the terms direct-initialization and copy-initialization still apply -- but looking at §8.5/16, we find that for a braced init list, this is a distinction without a difference, at least for your first and third examples:
— If the initializer is a (non-parenthesized) braced-init-list, the object or reference is list-initialized (8.5.4).
As such, the actual initialization for your first and third examples is done identically, and neither requires a copy ctor (or move ctor). In both cases, we're dealing with the fourth bullet in §8.5.4/3:
— 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.
... so both use std::vector's ctor that takes an std::initializer_list<T> as its argument.
As noted in the quote above, however, that only deals with a "(non-parenthesized) braced-init-list". For your second example with a parenthesized braced-init-list, we get to the first sub-bullet of the sixth bullet (geeze -- really need to talk to somebody about adding numbers for those) of §8.5/16:
— If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered. The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3). The constructor so selected is called to initialize the object, with the initializer expression or expression-list as its argument(s). If no constructor applies, or the overload resolution is ambiguous, the initialization is ill-formed.
Since this uses the syntax for direct initialization, and the expression inside the parentheses is a braced-initializer-list, and std::vector has a ctor that takes an initializer list, that's the overload that's selected.
Bottom line: although the routes through the standard to get there are different, all three end up using std::vector's constructor overload for std::initializer_list<T>. From any practical viewpoint, there's no difference between the three. All three will invoke vector::vector(std::initializer_list<T>, with no copies or other conversions happening (not even ones that are likely to be elided and really happen only in theory).
I believe with slightly different values, however, there is (or at least may be) one minor difference. The prohibition against narrowing conversions is in §8.5.4/3, so your second example (which doesn't go through §8.5.4/3, so to speak) should probably allow narrowing conversions, where the other two clearly do not. Even if I were an inveterate gambler, however, I wouldn't bet a thing on a compiler actually recognizing this distinction and allowing the narrowing conversion in the one case but not the others (I find it a little surprising, and rather doubt that it's intended to be allowed).
I played a bit on gcc 4.7.2 with a custom class taking std::initializer_list in a constructor. I tried all those scenarios and more. It seems there is really no difference in observable results on that compiler for those 3 statements.
EDIT: This is exact code I used for testing:
#include <iostream>
#include <initializer_list>
class A {
public:
A() { std::cout << "A::ctr\n"; }
A(const A&) { std::cout << "A::ctr_copy\n"; }
A(A&&) { std::cout << "A::ctr_move\n"; }
A &operator=(const A&) { std::cout << "A::=_copy\n"; return *this; }
A &operator=(A&&) { std::cout << "A::=_move\n"; return *this; }
~A() { std::cout << "A::dstr\n"; }
};
class B {
B(const B&) { std::cout << "B::ctr_copy\n"; }
B(B&&) { std::cout << "B::ctr_move\n"; }
B &operator=(const B&) { std::cout << "B::=copy\n"; return *this; }
B &operator=(B&&) { std::cout << "B::=move\n"; return *this; }
public:
B(std::initializer_list<A> init) { std::cout << "B::ctr_ user\n"; }
~B() { std::cout << "B::dstr\n"; }
};
int main()
{
B a1{ {}, {}, {} };
B a2({ {}, {}, {} });
B a3 = { {}, {}, {} };
// B a4 = B{ {}, {}, {} }; // does not compile on gcc 4.7.2, gcc 4.8 and clang (top version)
std::cout << "--------------------\n";
}
a1, a2 and a3 compiles fine on gcc 4.7.2, gcc 4.8 and the latest clang. I also do not see any observable results between the number of operations done on list members for all 3 cases. The last case (not from question) does not compile if I make B copy/move constructor private/deleted.
Related
Currently trying to wrap my head around C++11's uniform initialization. I came upon this ambiguous case: consider a class which can either be constructed from either a two-argument constructor or an initializer list of any length:
class Foo {
public:
Foo(int a, int b) {
std::cout << "constructor 1" << std::endl;
}
Foo(std::initializer_list<int>) {
std::cout << "constructor 2" << std::endl;
}
};
Following uniform initialization convention, I'd expect the following to work:
Foo a (1, 2) prints constructor 1 (duh)
Foo b {1, 2} prints constructor 1
Foo c = {1, 2} prints constructor 2
However, it seems like the compiler interprets Foo b {1, 2} as a list initialization, and calls constructor 2. Is the () syntax the only way to force the compiler to consider other kinds of constructors when an initializer-list constructor is present?
it seems like the compiler interprets Foo b {1, 2} as a list
initialization, and calls constructor 2. Is the () syntax the only way
to force the compiler to consider other kinds of constructors when an
initializer-list constructor is present?
Quotes from standard draft explains this well:
9.4.5.2 [dcl.init.list] (emphasis mine):
A constructor is an initializer-list constructor if its first
parameter is of type std::initializer_list or reference to cv
std::initializer_list for some type E, and either there are no
other parameters or else all other parameters have default arguments
([dcl.fct.default]).
[Note 2: Initializer-list constructors are
favored over other constructors in list-initialization
([over.match.list]). Passing an initializer list as the argument to
the constructor template template C(T) of a class C does not
create an initializer-list constructor, because an initializer list
argument causes the corresponding parameter to be a non-deduced
context ([temp.deduct.call]). — end note]
and 12.4.2.8 [over.match.list]:
When objects of non-aggregate class type T are list-initialized such
that [dcl.init.list] specifies that overload resolution is performed
according to the rules in this subclause or when forming a
list-initialization sequence according to [over.ics.list], overload
resolution selects the constructor in two phases:
If the initializer list is not empty or T has no default constructor,
overload resolution is first performed where the candidate functions
are the initializer-list constructors ([dcl.init.list]) of the class T
and the argument list consists of the initializer list as a single
argument.
Otherwise, or 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.
You can add an extra ignored argument to your constructor to specify a particular overload at callsite, like they do in STL:
#include <iostream>
struct non_init_list_t {};
inline constexpr non_init_list_t non_init_list;
struct Class {
Class(int a, int b, non_init_list_t = non_init_list) { std::clog << "()\n"; }
Class(std::initializer_list<int> list) { std::clog << "{}\n"; }
};
Class a{12, 42, non_init_list}; // ()
Class b{12, 42}; // {}
Class c(12, 42); // ()
If the constructor has the initializer_list version, the compiler will first interpret it as initializer_list, if there is no initializer_list version, the compiler will interpret it as another overloaded version.
If the compiler interprets it as another version, and you want to call a constructor that uses the initializer_list version, it happens that the number and type of arguments are the same as other ctors, then your will cause a bug. Then the compiler chooses the initializer_list version or other version? So using bracket notation is definitely not the initializer_list version. If you don't have an initializer_list version in your constructor, don't worry about this issue.
BTW, if you use auto to infer type automatically, DO NOT use uniform initialization. It must interpret type to initializer_list.
Suppose I have this function:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
In each grouping, are these statements identical? Or is there an extra (possibly optimizable) copy in some of the initializations?
I have seen people say both things. Please cite text as proof. Also add other cases please.
C++17 Update
In C++17, the meaning of A_factory_func() changed from creating a temporary object (C++<=14) to just specifying the initialization of whatever object this expression is initialized to (loosely speaking) in C++17. These objects (called "result objects") are the variables created by a declaration (like a1), artificial objects created when the initialization ends up being discarded, or if an object is needed for reference binding (like, in A_factory_func();. In the last case, an object is artificially created, called "temporary materialization", because A_factory_func() doesn't have a variable or reference that otherwise would require an object to exist).
As examples in our case, in the case of a1 and a2 special rules say that in such declarations, the result object of a prvalue initializer of the same type as a1 is variable a1, and therefore A_factory_func() directly initializes the object a1. Any intermediary functional-style cast would not have any effect, because A_factory_func(another-prvalue) just "passes through" the result object of the outer prvalue to be also the result object of the inner prvalue.
A a1 = A_factory_func();
A a2(A_factory_func());
Depends on what type A_factory_func() returns. I assume it returns an A - then it's doing the same - except that when the copy constructor is explicit, then the first one will fail. Read 8.6/14
double b1 = 0.5;
double b2(0.5);
This is doing the same because it's a built-in type (this means not a class type here). Read 8.6/14.
A c1;
A c2 = A();
A c3(A());
This is not doing the same. The first default-initializes if A is a non-POD, and doesn't do any initialization for a POD (Read 8.6/9). The second copy initializes: Value-initializes a temporary and then copies that value into c2 (Read 5.2.3/2 and 8.6/14). This of course will require a non-explicit copy constructor (Read 8.6/14 and 12.3.1/3 and 13.3.1.3/1 ). The third creates a function declaration for a function c3 that returns an A and that takes a function pointer to a function returning a A (Read 8.2).
Delving into Initializations Direct and Copy initialization
While they look identical and are supposed to do the same, these two forms are remarkably different in certain cases. The two forms of initialization are direct and copy initialization:
T t(x);
T t = x;
There is behavior we can attribute to each of them:
Direct initialization behaves like a function call to an overloaded function: The functions, in this case, are the constructors of T (including explicit ones), and the argument is x. Overload resolution will find the best matching constructor, and when needed will do any implicit conversion required.
Copy initialization constructs an implicit conversion sequence: It tries to convert x to an object of type T. (It then may copy over that object into the to-initialized object, so a copy constructor is needed too - but this is not important below)
As you see, copy initialization is in some way a part of direct initialization with regard to possible implicit conversions: While direct initialization has all constructors available to call, and in addition can do any implicit conversion it needs to match up argument types, copy initialization can just set up one implicit conversion sequence.
I tried hard and got the following code to output different text for each of those forms, without using the "obvious" through explicit constructors.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
How does it work, and why does it output that result?
Direct initialization
It first doesn't know anything about conversion. It will just try to call a constructor. In this case, the following constructor is available and is an exact match:
B(A const&)
There is no conversion, much less a user defined conversion, needed to call that constructor (note that no const qualification conversion happens here either). And so direct initialization will call it.
Copy initialization
As said above, copy initialization will construct a conversion sequence when a has not type B or derived from it (which is clearly the case here). So it will look for ways to do the conversion, and will find the following candidates
B(A const&)
operator B(A&);
Notice how I rewrote the conversion function: The parameter type reflects the type of the this pointer, which in a non-const member function is to non-const. Now, we call these candidates with x as argument. The winner is the conversion function: Because if we have two candidate functions both accepting a reference to the same type, then the less const version wins (this is, by the way, also the mechanism that prefers non-const member function calls for non-const objects).
Note that if we change the conversion function to be a const member function, then the conversion is ambiguous (because both have a parameter type of A const& then): The Comeau compiler rejects it properly, but GCC accepts it in non-pedantic mode. Switching to -pedantic makes it output the proper ambiguity warning too, though.
Assignment is different from initialization.
Both of the following lines do initialization. A single constructor call is done:
A a1 = A_factory_func(); // calls copy constructor
A a1(A_factory_func()); // calls copy constructor
but it's not equivalent to:
A a1; // calls default constructor
a1 = A_factory_func(); // (assignment) calls operator =
I don't have a text at the moment to prove this but it's very easy to experiment:
#include <iostream>
using namespace std;
class A {
public:
A() {
cout << "default constructor" << endl;
}
A(const A& x) {
cout << "copy constructor" << endl;
}
const A& operator = (const A& x) {
cout << "operator =" << endl;
return *this;
}
};
int main() {
A a; // default constructor
A b(a); // copy constructor
A c = a; // copy constructor
c = b; // operator =
return 0;
}
double b1 = 0.5; is implicit call of constructor.
double b2(0.5); is explicit call.
Look at the following code to see the difference:
#include <iostream>
class sss {
public:
explicit sss( int )
{
std::cout << "int" << std::endl;
};
sss( double )
{
std::cout << "double" << std::endl;
};
};
int main()
{
sss ddd( 7 ); // calls int constructor
sss xxx = 7; // calls double constructor
return 0;
}
If your class has no explicit constuctors than explicit and implicit calls are identical.
You can see its difference in explicit and implicit constructor types when you initialize an object :
Classes :
class A
{
A(int) { } // converting constructor
A(int, int) { } // converting constructor (C++11)
};
class B
{
explicit B(int) { }
explicit B(int, int) { }
};
And in the main function :
int main()
{
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast
// B b1 = 1; // error: copy-initialization does not consider B::B(int)
B b2(2); // OK: direct-initialization selects B::B(int)
B b3 {4, 5}; // OK: direct-list-initialization selects B::B(int, int)
// B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
B b5 = (B)1; // OK: explicit cast performs static_cast
}
By default, a constructor is as implicit so you have two way to initialize it :
A a1 = 1; // this is copy initialization
A a2(2); // this is direct initialization
And by defining a structure as explicit just you have one way as direct :
B b2(2); // this is direct initialization
B b5 = (B)1; // not problem if you either use of assign to initialize and cast it as static_cast
Of note:
[12.2/1] Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
I.e., for copy-initialization.
[12.8/15] When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
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 other words, copy-initialization is just like direct-initialization in most cases <opinion> where understandable code has been written. Since direct-initialization potentially causes arbitrary (and therefore probably unknown) conversions, I prefer to always use copy-initialization when possible. (With the bonus that it actually looks like initialization.)</opinion>
Technical goriness:
[12.2/1 cont from above] Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
Glad I'm not writing a C++ compiler.
First grouping: it depends on what A_factory_func returns. The first line is an example of copy initialization, the second line is direct initialization. If A_factory_func returns an A object then they are equivalent, they both call the copy constructor for A, otherwise the first version creates an rvalue of type A from an available conversion operators for the return type of A_factory_func or appropriate A constructors, and then calls the copy constructor to construct a1 from this temporary. The second version attempts to find a suitable constructor that takes whatever A_factory_func returns, or that takes something that the return value can be implicitly converted to.
Second grouping: exactly the same logic holds, except that built in types don't have any exotic constructors so they are, in practice, identical.
Third grouping: c1 is default initialized, c2 is copy-initialized from a value initialized temporary. Any members of c1 that have pod-type (or members of members, etc., etc.) may not be initialized if the user supplied default constructors (if any) do not explicitly initialize them. For c2, it depends on whether there is a user supplied copy constructor and whether that appropriately initializes those members, but the members of the temporary will all be initialized (zero-initialized if not otherwise explicitly initialized). As litb spotted, c3 is a trap. It's actually a function declaration.
Answering with respect to this part:
A c2 = A(); A c3(A());
Since most of the answers are pre-c++11 I am adding what c++11 has to say about this:
A simple-type-specifier (7.1.6.2) or typename-specifier (14.6)
followed by a parenthesized expression-list constructs a value of the
specified type given the expression list. If the expression list is a
single expression, the type conversion expression is equivalent (in
definedness, and if defined in meaning) to the corresponding cast
expression (5.4). If the type specified is a class type, the class
type shall be complete. If the expression list specifies more than a
single value, the type shall be a class with a suitably declared
constructor (8.5, 12.1), and the expression T(x1, x2, ...) is
equivalent in effect to the declaration T t(x1, x2, ...); for some
invented temporary variable t, with the result being the value of t as
a prvalue.
So optimization or not they are equivalent as per the standard.
Note that this is in accordance with what other answers have mentioned. Just quoting what the standard has to say for sake of correctness.
This is from C++ Programming Language by Bjarne Stroustrup:
An initialization with an = is considered a copy initialization. In principle, a copy of the initializer (the object we are copying from) is placed into the initialized object. However, such a copy may be optimized away (elided), and a move operation (based on move semantics) may be used if the initializer is an rvalue. Leaving out the = makes the initialization explicit. Explicit initialization is known as direct initialization.
A lot of these cases are subject to an object's implementation so it's hard to give you a concrete answer.
Consider the case
A a = 5;
A a(5);
In this case assuming a proper assignment operator & initializing constructor which accept a single integer argument, how I implement said methods affects the behavior of each line. It is common practice however for one of those to call the other in the implementation as to eliminate duplicate code (although in a case as simple as this there would be no real purpose.)
Edit: As mentioned in other responses, the first line will in fact call the copy constructor. Consider the comments relating to the assignment operator as behavior pertaining to a stand alone assignment.
That said, how the compiler optimizes the code will then have it's own impact. If I have the initializing constructor calling the "=" operator - if the compiler makes no optimizations, the top line would then perform 2 jumps as opposed to one in the bottom line.
Now, for the most common situations, your compiler will optimize through these cases and eliminate this type of inefficiencies. So effectively all the different situations you describe will turn out the same. If you want to see exactly what is being done, you can look at the object code or an assembly output of your compiler.
Suppose I have this function:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
In each grouping, are these statements identical? Or is there an extra (possibly optimizable) copy in some of the initializations?
I have seen people say both things. Please cite text as proof. Also add other cases please.
C++17 Update
In C++17, the meaning of A_factory_func() changed from creating a temporary object (C++<=14) to just specifying the initialization of whatever object this expression is initialized to (loosely speaking) in C++17. These objects (called "result objects") are the variables created by a declaration (like a1), artificial objects created when the initialization ends up being discarded, or if an object is needed for reference binding (like, in A_factory_func();. In the last case, an object is artificially created, called "temporary materialization", because A_factory_func() doesn't have a variable or reference that otherwise would require an object to exist).
As examples in our case, in the case of a1 and a2 special rules say that in such declarations, the result object of a prvalue initializer of the same type as a1 is variable a1, and therefore A_factory_func() directly initializes the object a1. Any intermediary functional-style cast would not have any effect, because A_factory_func(another-prvalue) just "passes through" the result object of the outer prvalue to be also the result object of the inner prvalue.
A a1 = A_factory_func();
A a2(A_factory_func());
Depends on what type A_factory_func() returns. I assume it returns an A - then it's doing the same - except that when the copy constructor is explicit, then the first one will fail. Read 8.6/14
double b1 = 0.5;
double b2(0.5);
This is doing the same because it's a built-in type (this means not a class type here). Read 8.6/14.
A c1;
A c2 = A();
A c3(A());
This is not doing the same. The first default-initializes if A is a non-POD, and doesn't do any initialization for a POD (Read 8.6/9). The second copy initializes: Value-initializes a temporary and then copies that value into c2 (Read 5.2.3/2 and 8.6/14). This of course will require a non-explicit copy constructor (Read 8.6/14 and 12.3.1/3 and 13.3.1.3/1 ). The third creates a function declaration for a function c3 that returns an A and that takes a function pointer to a function returning a A (Read 8.2).
Delving into Initializations Direct and Copy initialization
While they look identical and are supposed to do the same, these two forms are remarkably different in certain cases. The two forms of initialization are direct and copy initialization:
T t(x);
T t = x;
There is behavior we can attribute to each of them:
Direct initialization behaves like a function call to an overloaded function: The functions, in this case, are the constructors of T (including explicit ones), and the argument is x. Overload resolution will find the best matching constructor, and when needed will do any implicit conversion required.
Copy initialization constructs an implicit conversion sequence: It tries to convert x to an object of type T. (It then may copy over that object into the to-initialized object, so a copy constructor is needed too - but this is not important below)
As you see, copy initialization is in some way a part of direct initialization with regard to possible implicit conversions: While direct initialization has all constructors available to call, and in addition can do any implicit conversion it needs to match up argument types, copy initialization can just set up one implicit conversion sequence.
I tried hard and got the following code to output different text for each of those forms, without using the "obvious" through explicit constructors.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
How does it work, and why does it output that result?
Direct initialization
It first doesn't know anything about conversion. It will just try to call a constructor. In this case, the following constructor is available and is an exact match:
B(A const&)
There is no conversion, much less a user defined conversion, needed to call that constructor (note that no const qualification conversion happens here either). And so direct initialization will call it.
Copy initialization
As said above, copy initialization will construct a conversion sequence when a has not type B or derived from it (which is clearly the case here). So it will look for ways to do the conversion, and will find the following candidates
B(A const&)
operator B(A&);
Notice how I rewrote the conversion function: The parameter type reflects the type of the this pointer, which in a non-const member function is to non-const. Now, we call these candidates with x as argument. The winner is the conversion function: Because if we have two candidate functions both accepting a reference to the same type, then the less const version wins (this is, by the way, also the mechanism that prefers non-const member function calls for non-const objects).
Note that if we change the conversion function to be a const member function, then the conversion is ambiguous (because both have a parameter type of A const& then): The Comeau compiler rejects it properly, but GCC accepts it in non-pedantic mode. Switching to -pedantic makes it output the proper ambiguity warning too, though.
Assignment is different from initialization.
Both of the following lines do initialization. A single constructor call is done:
A a1 = A_factory_func(); // calls copy constructor
A a1(A_factory_func()); // calls copy constructor
but it's not equivalent to:
A a1; // calls default constructor
a1 = A_factory_func(); // (assignment) calls operator =
I don't have a text at the moment to prove this but it's very easy to experiment:
#include <iostream>
using namespace std;
class A {
public:
A() {
cout << "default constructor" << endl;
}
A(const A& x) {
cout << "copy constructor" << endl;
}
const A& operator = (const A& x) {
cout << "operator =" << endl;
return *this;
}
};
int main() {
A a; // default constructor
A b(a); // copy constructor
A c = a; // copy constructor
c = b; // operator =
return 0;
}
double b1 = 0.5; is implicit call of constructor.
double b2(0.5); is explicit call.
Look at the following code to see the difference:
#include <iostream>
class sss {
public:
explicit sss( int )
{
std::cout << "int" << std::endl;
};
sss( double )
{
std::cout << "double" << std::endl;
};
};
int main()
{
sss ddd( 7 ); // calls int constructor
sss xxx = 7; // calls double constructor
return 0;
}
If your class has no explicit constuctors than explicit and implicit calls are identical.
You can see its difference in explicit and implicit constructor types when you initialize an object :
Classes :
class A
{
A(int) { } // converting constructor
A(int, int) { } // converting constructor (C++11)
};
class B
{
explicit B(int) { }
explicit B(int, int) { }
};
And in the main function :
int main()
{
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast
// B b1 = 1; // error: copy-initialization does not consider B::B(int)
B b2(2); // OK: direct-initialization selects B::B(int)
B b3 {4, 5}; // OK: direct-list-initialization selects B::B(int, int)
// B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
B b5 = (B)1; // OK: explicit cast performs static_cast
}
By default, a constructor is as implicit so you have two way to initialize it :
A a1 = 1; // this is copy initialization
A a2(2); // this is direct initialization
And by defining a structure as explicit just you have one way as direct :
B b2(2); // this is direct initialization
B b5 = (B)1; // not problem if you either use of assign to initialize and cast it as static_cast
Of note:
[12.2/1] Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
I.e., for copy-initialization.
[12.8/15] When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
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 other words, copy-initialization is just like direct-initialization in most cases <opinion> where understandable code has been written. Since direct-initialization potentially causes arbitrary (and therefore probably unknown) conversions, I prefer to always use copy-initialization when possible. (With the bonus that it actually looks like initialization.)</opinion>
Technical goriness:
[12.2/1 cont from above] Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
Glad I'm not writing a C++ compiler.
First grouping: it depends on what A_factory_func returns. The first line is an example of copy initialization, the second line is direct initialization. If A_factory_func returns an A object then they are equivalent, they both call the copy constructor for A, otherwise the first version creates an rvalue of type A from an available conversion operators for the return type of A_factory_func or appropriate A constructors, and then calls the copy constructor to construct a1 from this temporary. The second version attempts to find a suitable constructor that takes whatever A_factory_func returns, or that takes something that the return value can be implicitly converted to.
Second grouping: exactly the same logic holds, except that built in types don't have any exotic constructors so they are, in practice, identical.
Third grouping: c1 is default initialized, c2 is copy-initialized from a value initialized temporary. Any members of c1 that have pod-type (or members of members, etc., etc.) may not be initialized if the user supplied default constructors (if any) do not explicitly initialize them. For c2, it depends on whether there is a user supplied copy constructor and whether that appropriately initializes those members, but the members of the temporary will all be initialized (zero-initialized if not otherwise explicitly initialized). As litb spotted, c3 is a trap. It's actually a function declaration.
Answering with respect to this part:
A c2 = A(); A c3(A());
Since most of the answers are pre-c++11 I am adding what c++11 has to say about this:
A simple-type-specifier (7.1.6.2) or typename-specifier (14.6)
followed by a parenthesized expression-list constructs a value of the
specified type given the expression list. If the expression list is a
single expression, the type conversion expression is equivalent (in
definedness, and if defined in meaning) to the corresponding cast
expression (5.4). If the type specified is a class type, the class
type shall be complete. If the expression list specifies more than a
single value, the type shall be a class with a suitably declared
constructor (8.5, 12.1), and the expression T(x1, x2, ...) is
equivalent in effect to the declaration T t(x1, x2, ...); for some
invented temporary variable t, with the result being the value of t as
a prvalue.
So optimization or not they are equivalent as per the standard.
Note that this is in accordance with what other answers have mentioned. Just quoting what the standard has to say for sake of correctness.
This is from C++ Programming Language by Bjarne Stroustrup:
An initialization with an = is considered a copy initialization. In principle, a copy of the initializer (the object we are copying from) is placed into the initialized object. However, such a copy may be optimized away (elided), and a move operation (based on move semantics) may be used if the initializer is an rvalue. Leaving out the = makes the initialization explicit. Explicit initialization is known as direct initialization.
A lot of these cases are subject to an object's implementation so it's hard to give you a concrete answer.
Consider the case
A a = 5;
A a(5);
In this case assuming a proper assignment operator & initializing constructor which accept a single integer argument, how I implement said methods affects the behavior of each line. It is common practice however for one of those to call the other in the implementation as to eliminate duplicate code (although in a case as simple as this there would be no real purpose.)
Edit: As mentioned in other responses, the first line will in fact call the copy constructor. Consider the comments relating to the assignment operator as behavior pertaining to a stand alone assignment.
That said, how the compiler optimizes the code will then have it's own impact. If I have the initializing constructor calling the "=" operator - if the compiler makes no optimizations, the top line would then perform 2 jumps as opposed to one in the bottom line.
Now, for the most common situations, your compiler will optimize through these cases and eliminate this type of inefficiencies. So effectively all the different situations you describe will turn out the same. If you want to see exactly what is being done, you can look at the object code or an assembly output of your compiler.
How can I initialize an array without copy or move-constructing temporary elements? When the element has an explicitly deleted copy or move constructor, I can initialize the array only if the element has a default ctor or a ctor with all default arguments and I do one of the following: (a) plainly declare the array, (b) direct initialize and zero initialize the array, or (c) copy initialize and zero initialize the array. Neither direct (but not zero) initialization nor copy (but not zero) initialization compiles.
struct Foo
{
Foo(int n = 5) : num(n) {}
Foo(const Foo&) = delete;
//Foo(Foo&&) = delete; // <-- gives same effect
int num;
};
int main()
{
// Resultant arrays for 'a1', 'a2', and 'a3' are two
// 'Foo' elements each with 'num' values of '5':
Foo a1[2]; // plain declaration
Foo a2[2] {}; // direct initialization and zero initialization
Foo a3[2] = {}; // copy initialization and zero initialization
Foo a4[2] {5, 5}; // direct initialization -> ERROR
Foo a5[2] = {5, 5}; // copy initialization -> ERROR
}
Are those 3 ways the only ways to initialize arrays without copying/moving temporary elements?
Do a1, a2, and a3 count as initializations? e.g. a1 is a declaration, but its elements get initial, albeit default, values.
Are any of them bugs? I did this GCC 6.3.0 with C++14 flag.
Why does copy initialization combined with zero initialization work if it is still under the category of copy initialization?
In general, are all array initializations with curly braces just construction of temporary elements (unless elided when there is no deletion of copy or move constructors (or does elision not apply to arrays?)) followed by per-element copy, move, or mix of copy and move construction?
The code declaration Foo a2[2]; declares an array. The only way to initialize an array is via list-initialization (i.e. a brace-enclosed list of zero or more elements), and the behaviour is described by the section of the Standard titled aggregate initialization. (The term aggregate refers to arrays, and classes that meet certain criteria).
In aggregate initialization, the presence of = makes no difference. The basic definition of it is in C++14 [dcl.init.aggr]/2:
When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.
Also, /7:
If there are fewer initializer-clauses in the list than there are members in the aggregate, then each member not explicitly initialized shall be initialized from its brace-or-equal-initializer or, if there is no brace-or-equal-
initializer, from an empty initializer list (8.5.4).
You can see from this that copy-initialization is always used for each provided initializer. Therefore, when the initializer is an expression, an accessible copy/move-constructor must exist for the class.
However (as suggested by Anty) you can make the initializer be another list. Copy-initialization using a list is called copy-list-initialization:
Foo a6[2] = {{6}, {6}};
When a single Foo is list-initialized, it is not aggregate initialization (since Foo is not an aggregate). So the rules are different to those discussed above. Copy-list-initialization of a non-aggregate class comes under list-initialization, in [dcl.init.list]/3.4, which specifies for Foo that the initializers in the list are matched to constructor arguments using overload resolution. At this stage the Foo(int) constructor will be chosen, meaning the copy-constructor is not required.
For completeness I'll mention the nuclear option:
typename std::aligned_storage< sizeof(Foo), alignof(Foo) >::type buf[2];
::new ((void *)::std::addressof(buf[0])) Foo(5);
::new ((void *)::std::addressof(buf[1])) Foo(5);
Foo *a7 = reinterpret_cast<Foo *>(buf);
// ...
a7[0].~Foo();
a7[1].~Foo();
Obviously this is a last resort for when you can't achieve your goal by any other means.
Note 1: The above applies to C++14. In C++17 I believe the so-called "guaranteed copy elision" will change copy-initialization to not actually require a copy/move constructor. I will hopefully update this answer once the standard is published. There has also been some fiddling with aggregate initialization in the drafts.
In your case you still may use those constructs:
Foo a4[2] = {{4},{3}};
or
Foo a5[2] {{4},{3}};
You can also create a pointer with malloc and then use array syntax on it (if the class is a POD). Ex:
class A {
public:
int var1;
int var2;
public int add(int firstNum, int secondNum) {
return firstNum + secondNum;
}
}
A * p = 0;
while(!p) {
p = (A*)malloc(sizeof(A) * 2);
}
p[0] = {2, 3};
p[1] = {2, 5};
There is also a way to initialize an array as a temporary value, but I forgot how to do that.
You can directly initialize an array of objects if the class is a POD (plain old data). In order for a class to be a POD, it must have no constructors, destructors, or virtual methods. Everything in the class must also be declared public in order for it to be a POD. Basically, a POD class is just a c-style struct that can have methods in it.
I don't have the C++ standard at hand, and citing it would probably be the only way to prove my words. So to answer each of your questions I can only say:
No this is not all. I cannot provide you wit an exhaustive list of possiblities, but I definitely have used the following before:
xx
struct Foo
{
Foo(int n = 5) : num(n) {}
Foo(const Foo&) = delete;
Foo(Foo&&) = delete;
int num;
};
int main()
{
Foo a1[2]; // plain declaration
Foo a2[2] {}; // direct initialization
Foo a3[2] = {}; // also direct initialization
Foo a4[2] { {5}, {5} }; // also direct initialization
Foo a5[2] = { {5}, {5} }; // also direct initialization
}
Brace initalization is not declare-and-copy, it's separate language construct. It might very well just in-place construct the elements. The only situation where i am not sure if this applies is { Foo(5), Foo(5) } initialization, as it explicitly requests creation of temporaries. The { 5, 5} variant is just the same, because in order to initialize an array you need a brace-initalized list of Foo objects. Since you don't create any, it will use the constructor for temporaries to obtain { Foo(5), Foo(5) }. The { { 5 }, { 5 } } variant compiles because compiler knows that it can construct Foo object out of provided { 5 } initializer and therefore needs no temporaries - although I don't know exact standard wording that allows this.
No, I don't think any of these are bugs.
I remember a line in C++ standard that basically says that a compiler can always replace assignment initialization by direct initialization when creating a new variable.
xx
Foo x( 5 );
Foo x { 5 };
Foo x = { 5 }; // Same as above
As I already pointed out above: No, you can in-place initialize the array, you just need a proper element initializers. { 5 } will be intepreted as "an initializer for Foo object", whereas plain 5 will be understud as "a value that can be converted to a temporary Foo object". Initializer lists generally have to contain either a initializer lists for the elements, or items of the exact type of the elements. If something different is given, a temporary will be created.
Suppose I have this function:
void my_test()
{
A a1 = A_factory_func();
A a2(A_factory_func());
double b1 = 0.5;
double b2(0.5);
A c1;
A c2 = A();
A c3(A());
}
In each grouping, are these statements identical? Or is there an extra (possibly optimizable) copy in some of the initializations?
I have seen people say both things. Please cite text as proof. Also add other cases please.
C++17 Update
In C++17, the meaning of A_factory_func() changed from creating a temporary object (C++<=14) to just specifying the initialization of whatever object this expression is initialized to (loosely speaking) in C++17. These objects (called "result objects") are the variables created by a declaration (like a1), artificial objects created when the initialization ends up being discarded, or if an object is needed for reference binding (like, in A_factory_func();. In the last case, an object is artificially created, called "temporary materialization", because A_factory_func() doesn't have a variable or reference that otherwise would require an object to exist).
As examples in our case, in the case of a1 and a2 special rules say that in such declarations, the result object of a prvalue initializer of the same type as a1 is variable a1, and therefore A_factory_func() directly initializes the object a1. Any intermediary functional-style cast would not have any effect, because A_factory_func(another-prvalue) just "passes through" the result object of the outer prvalue to be also the result object of the inner prvalue.
A a1 = A_factory_func();
A a2(A_factory_func());
Depends on what type A_factory_func() returns. I assume it returns an A - then it's doing the same - except that when the copy constructor is explicit, then the first one will fail. Read 8.6/14
double b1 = 0.5;
double b2(0.5);
This is doing the same because it's a built-in type (this means not a class type here). Read 8.6/14.
A c1;
A c2 = A();
A c3(A());
This is not doing the same. The first default-initializes if A is a non-POD, and doesn't do any initialization for a POD (Read 8.6/9). The second copy initializes: Value-initializes a temporary and then copies that value into c2 (Read 5.2.3/2 and 8.6/14). This of course will require a non-explicit copy constructor (Read 8.6/14 and 12.3.1/3 and 13.3.1.3/1 ). The third creates a function declaration for a function c3 that returns an A and that takes a function pointer to a function returning a A (Read 8.2).
Delving into Initializations Direct and Copy initialization
While they look identical and are supposed to do the same, these two forms are remarkably different in certain cases. The two forms of initialization are direct and copy initialization:
T t(x);
T t = x;
There is behavior we can attribute to each of them:
Direct initialization behaves like a function call to an overloaded function: The functions, in this case, are the constructors of T (including explicit ones), and the argument is x. Overload resolution will find the best matching constructor, and when needed will do any implicit conversion required.
Copy initialization constructs an implicit conversion sequence: It tries to convert x to an object of type T. (It then may copy over that object into the to-initialized object, so a copy constructor is needed too - but this is not important below)
As you see, copy initialization is in some way a part of direct initialization with regard to possible implicit conversions: While direct initialization has all constructors available to call, and in addition can do any implicit conversion it needs to match up argument types, copy initialization can just set up one implicit conversion sequence.
I tried hard and got the following code to output different text for each of those forms, without using the "obvious" through explicit constructors.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
How does it work, and why does it output that result?
Direct initialization
It first doesn't know anything about conversion. It will just try to call a constructor. In this case, the following constructor is available and is an exact match:
B(A const&)
There is no conversion, much less a user defined conversion, needed to call that constructor (note that no const qualification conversion happens here either). And so direct initialization will call it.
Copy initialization
As said above, copy initialization will construct a conversion sequence when a has not type B or derived from it (which is clearly the case here). So it will look for ways to do the conversion, and will find the following candidates
B(A const&)
operator B(A&);
Notice how I rewrote the conversion function: The parameter type reflects the type of the this pointer, which in a non-const member function is to non-const. Now, we call these candidates with x as argument. The winner is the conversion function: Because if we have two candidate functions both accepting a reference to the same type, then the less const version wins (this is, by the way, also the mechanism that prefers non-const member function calls for non-const objects).
Note that if we change the conversion function to be a const member function, then the conversion is ambiguous (because both have a parameter type of A const& then): The Comeau compiler rejects it properly, but GCC accepts it in non-pedantic mode. Switching to -pedantic makes it output the proper ambiguity warning too, though.
Assignment is different from initialization.
Both of the following lines do initialization. A single constructor call is done:
A a1 = A_factory_func(); // calls copy constructor
A a1(A_factory_func()); // calls copy constructor
but it's not equivalent to:
A a1; // calls default constructor
a1 = A_factory_func(); // (assignment) calls operator =
I don't have a text at the moment to prove this but it's very easy to experiment:
#include <iostream>
using namespace std;
class A {
public:
A() {
cout << "default constructor" << endl;
}
A(const A& x) {
cout << "copy constructor" << endl;
}
const A& operator = (const A& x) {
cout << "operator =" << endl;
return *this;
}
};
int main() {
A a; // default constructor
A b(a); // copy constructor
A c = a; // copy constructor
c = b; // operator =
return 0;
}
double b1 = 0.5; is implicit call of constructor.
double b2(0.5); is explicit call.
Look at the following code to see the difference:
#include <iostream>
class sss {
public:
explicit sss( int )
{
std::cout << "int" << std::endl;
};
sss( double )
{
std::cout << "double" << std::endl;
};
};
int main()
{
sss ddd( 7 ); // calls int constructor
sss xxx = 7; // calls double constructor
return 0;
}
If your class has no explicit constuctors than explicit and implicit calls are identical.
You can see its difference in explicit and implicit constructor types when you initialize an object :
Classes :
class A
{
A(int) { } // converting constructor
A(int, int) { } // converting constructor (C++11)
};
class B
{
explicit B(int) { }
explicit B(int, int) { }
};
And in the main function :
int main()
{
A a1 = 1; // OK: copy-initialization selects A::A(int)
A a2(2); // OK: direct-initialization selects A::A(int)
A a3 {4, 5}; // OK: direct-list-initialization selects A::A(int, int)
A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
A a5 = (A)1; // OK: explicit cast performs static_cast
// B b1 = 1; // error: copy-initialization does not consider B::B(int)
B b2(2); // OK: direct-initialization selects B::B(int)
B b3 {4, 5}; // OK: direct-list-initialization selects B::B(int, int)
// B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
B b5 = (B)1; // OK: explicit cast performs static_cast
}
By default, a constructor is as implicit so you have two way to initialize it :
A a1 = 1; // this is copy initialization
A a2(2); // this is direct initialization
And by defining a structure as explicit just you have one way as direct :
B b2(2); // this is direct initialization
B b5 = (B)1; // not problem if you either use of assign to initialize and cast it as static_cast
Of note:
[12.2/1] Temporaries of class type are created in various contexts: ... and in some initializations (8.5).
I.e., for copy-initialization.
[12.8/15] When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...
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 other words, copy-initialization is just like direct-initialization in most cases <opinion> where understandable code has been written. Since direct-initialization potentially causes arbitrary (and therefore probably unknown) conversions, I prefer to always use copy-initialization when possible. (With the bonus that it actually looks like initialization.)</opinion>
Technical goriness:
[12.2/1 cont from above] Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.
Glad I'm not writing a C++ compiler.
First grouping: it depends on what A_factory_func returns. The first line is an example of copy initialization, the second line is direct initialization. If A_factory_func returns an A object then they are equivalent, they both call the copy constructor for A, otherwise the first version creates an rvalue of type A from an available conversion operators for the return type of A_factory_func or appropriate A constructors, and then calls the copy constructor to construct a1 from this temporary. The second version attempts to find a suitable constructor that takes whatever A_factory_func returns, or that takes something that the return value can be implicitly converted to.
Second grouping: exactly the same logic holds, except that built in types don't have any exotic constructors so they are, in practice, identical.
Third grouping: c1 is default initialized, c2 is copy-initialized from a value initialized temporary. Any members of c1 that have pod-type (or members of members, etc., etc.) may not be initialized if the user supplied default constructors (if any) do not explicitly initialize them. For c2, it depends on whether there is a user supplied copy constructor and whether that appropriately initializes those members, but the members of the temporary will all be initialized (zero-initialized if not otherwise explicitly initialized). As litb spotted, c3 is a trap. It's actually a function declaration.
Answering with respect to this part:
A c2 = A(); A c3(A());
Since most of the answers are pre-c++11 I am adding what c++11 has to say about this:
A simple-type-specifier (7.1.6.2) or typename-specifier (14.6)
followed by a parenthesized expression-list constructs a value of the
specified type given the expression list. If the expression list is a
single expression, the type conversion expression is equivalent (in
definedness, and if defined in meaning) to the corresponding cast
expression (5.4). If the type specified is a class type, the class
type shall be complete. If the expression list specifies more than a
single value, the type shall be a class with a suitably declared
constructor (8.5, 12.1), and the expression T(x1, x2, ...) is
equivalent in effect to the declaration T t(x1, x2, ...); for some
invented temporary variable t, with the result being the value of t as
a prvalue.
So optimization or not they are equivalent as per the standard.
Note that this is in accordance with what other answers have mentioned. Just quoting what the standard has to say for sake of correctness.
This is from C++ Programming Language by Bjarne Stroustrup:
An initialization with an = is considered a copy initialization. In principle, a copy of the initializer (the object we are copying from) is placed into the initialized object. However, such a copy may be optimized away (elided), and a move operation (based on move semantics) may be used if the initializer is an rvalue. Leaving out the = makes the initialization explicit. Explicit initialization is known as direct initialization.
A lot of these cases are subject to an object's implementation so it's hard to give you a concrete answer.
Consider the case
A a = 5;
A a(5);
In this case assuming a proper assignment operator & initializing constructor which accept a single integer argument, how I implement said methods affects the behavior of each line. It is common practice however for one of those to call the other in the implementation as to eliminate duplicate code (although in a case as simple as this there would be no real purpose.)
Edit: As mentioned in other responses, the first line will in fact call the copy constructor. Consider the comments relating to the assignment operator as behavior pertaining to a stand alone assignment.
That said, how the compiler optimizes the code will then have it's own impact. If I have the initializing constructor calling the "=" operator - if the compiler makes no optimizations, the top line would then perform 2 jumps as opposed to one in the bottom line.
Now, for the most common situations, your compiler will optimize through these cases and eliminate this type of inefficiencies. So effectively all the different situations you describe will turn out the same. If you want to see exactly what is being done, you can look at the object code or an assembly output of your compiler.