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.
Related
In C++17, consider a case where S is a struct with a deleted default constructor and a float member, when S is initialized with empty braces, is the float member guaranteed by the standard to be zero-initialized?
struct A {
int x{};
};
struct S
{
S() = delete;
A a;
float b;
};
int main()
{
auto s = S{}; // Is s.b guaranteed to be zero?
}
In my opinion, cppreference.com is not clear, saying both that:
If the number of initializer clauses is less than the number of members and basesor initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default member initializers, if provided in the class definition, and otherwise (since C++14) copy-initialized from empty lists, in accordance with the usual list-initialization rules (which performs value-initialization for non-class types and non-aggregate classes with default constructors, and aggregate initialization for aggregates). If a member of a reference type is one of these remaining members, the program is ill-formed.
(from here), which implies that b is guaranteed to be zero
In all cases, if the empty pair of braces {} is used and T is an aggregate type, aggregate-initialization is performed instead of value-initialization.
(from here)
which implies that b is not guaranteed to be zero.
There is also a discussion that seems to imply that while not guaranteed, all known compiler zero-initialize anyway:
The standard specifies that zero-initialization is not performed when the class has a user-provided or deleted default constructor, even if that default constructor is not selected by overload resolution. All known compilers performs additional zero-initialization if a non-deleted defaulted default constructor is selected.
Related to Why does aggregate initialization not work anymore since C++20 if a constructor is explicitly defaulted or deleted?
This is a quirk of C++ that is fixed in C++20. In the meantime you can add explicit to the deleted default constructor to force the struct to become non-aggregate, and make your code a guaranteed compile error:
struct A {
int x{};
};
struct S
{
explicit S() = delete;
const A a;
const float b;
};
int main()
{
auto s = S{}; // error: call to deleted constructor of 'S'
}
Because S is an aggregate, S{} will perform aggregate initialization. The rule in the standard about how members are initialized when there are no initializers in the list is basically what you cited:
If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
So for b, that's the equivalent of float b = {};. Per the rules of list initialization, we have to get all the way down to 3.10:
Otherwise, if the initializer list has no elements, the object is value-initialized.
And value initialization will initialize a float to 0.
I need to initilise an member array of a class with a non-default constructor and without using the copy constructor.
I have the following two classes:
class MemberClass
{
public:
MemberClass(int id) { /* Do stuff */ }; // Define non-default ctor
MemberClass(const MemberClass& other) = delete; // Delete copy ctor
~MemberClass() { /* Do stuff */ }; // Overide default dtor
};
class ContainerClass
{
private:
MemberClass mem[2];
public:
ContainerClass(int id)
: mem { {id} , {id} }
{}
};
which upon compiling gives the following error:
error: use of deleted function ‘MemberClass::MemberClass(const MemberClass&)’
: mem { {id} , {id} }
but I cannot figure out a way to initialise the mem array without defining a copy constructor. I've found answers from here and here explaining that copy-elision is occuring and a copy-ctor is needed to compile but should be removed by the compiler. The MemberClass should never be copied, so defining a copy-ctor just for this initialisation seems very awkward and prone to more difficult debugging elsewhere.
If the MemberClass only has a default constructor then there is no issue given by the compiler. Nor is there any issue given if mem is not an array and is just a single MemberClass object. My only issue is with initialising this array with the non-default ctor, and without using copy-ctor.
Weirdly, if I do not define a destructor I don't get any compilation error, which seems like a clue.
Is there a "correct" way to do this sort of initialisation?
I think this is a bug in gcc.
As specified by C++17 [dcl.init.aggr]/3 (this text was substantially the same in C++14):
When an aggregate is initialized by an initializer list as specified in 11.6.4, the elements of the initializer list are taken as initializers for the elements of the aggregate, in order. Each element is copy-initialized from the corresponding initializer-clause. [...] If an initializer-clause is itself an initializer list, the member is list-initialized,
I'll analyze three different cases here:
Case 1: mem { id, id }
Case 2: mem { MemberClass{id}, MemberClass{id} }
Case 3: mem { {id}, {id} }
In Case 1, mem[0] is copy-initialized by the expression id. This is the same sort of initialization as MemberClass x = id;. It is covered by dcl.init/17.6.2 (copy-initialization from expression of a different type).
The behaviour is that the initializer is converted to a prvalue (i.e. MemberClass{id}) which then direct-initializes the target. In C++14 this was ill-formed: although it is a copy elision context, a valid copy constructor must still exist. In C++17 it is well-formed: initializing an object from a prvalue of the same type is the same as initializing the object using the constructor specified by the prvalue (so-called "guaranteed copy elision").
Case 2 is similar to case 1: it uses 17.6.1 (initialization from expression of the same type) and the same analysis for Case 1 applies.
However, Case 3 is different. As per the last bold quote from dcl.init.aggr/3, mem[0] is list-initialized by {id}, i.e. the code should behave the same as MemberClass z {id};. There is no temporary or copy operation even in C++14.
So the correct behaviour is:
C++14 - Case 3 is correct, Case 1 and 2 ill-formed.
C++17 - All cases correct.
The error messages emitted by gcc suggest that it in C++14 mode is treating Case 3, your code, the same as the other two cases. And in C++17 mode it never got the memo about guaranteed copy elision.
Let's say that I want to disable the construction of class, then I can do the following (as per Best style for deleting all constructors (or other function)?
):
// This results in Example being CopyConstructible:
struct Example {
Example() = delete;
};
or
struct Example {
template <typename... Ts>
Example(Ts&&...) = delete;
};
or
struct Example {
Example(const Example&) = delete;
};
where the first example can still be copied if constructed (which the intention is to disable), but the second two will disable almost all methods for creating Example. If I default construct any of the above using an empty braced initializer list, then the instance is successfully constructed. In Meyer's Effective Modern C++ he gives the example (bottom of page 51):
Widget w3{}; // calls Widget ctor with no args
which I would thus expect to fail for the above Example classes i.e:
Example e{};
should not construct since it should call the deleted default constructor. However, it does, is usable, and if defined as the first case above, is also copyable. See live demo. My question is: Is this correct, and if so, why? Also, if this is correct, how do I completely disable the destruction of the class?
From ground up
We will first clarify what it means to initialize an object and how/when/if a constructor is invoked.
The following is my laymen's interpretation of the standard, for simplicity's sake some irrelevant details have been omitted or mangled.
Initializer
An initializer is one of the following
() // parentheses
// nothing
{} // braced initializer list
= expr // assignment expression
The parentheses and braced initializer list may contain further expressions.
They are used like this, given struct S
new S() // empty parentheses
S s(1, 2) // parentheses with expression list as (1, 2)
S s // nothing
S s{} // empty braced initializer list
S s{{1}, {2}} // braced initializer list with sublists
S s = 1 // assignment
S s = {1, 2} // assignment with braced initializer list
Note that we have not yet mentioned constructors
Initialization
Initialization is performed according to what initializers are used.
new S() // value-initialize
S s(1, 2) // direct-initialize
S s // default-initialize
S s{} // list-initialize
S s{{1}, {2}} // list-initialize
S s = 1 // copy-initialize
S s = {1, 2} // list-initialize
Once initialization is performed, the object is considered initialized.
Note that, again, constructors have not been mentioned
List initialize
We will be primarily explaining what it means to list initialize something, as this is the question at hand.
When list initialization occurs, the following is considered in order
If the object is an aggregate type and the list has a single element that is the object's type or is derived from the object's type, the object is initialized with that element
If the object is an aggregate type, the object is aggregate initialized
If the list is empty, and the object has a default constructor, the object is value-initialized (ends up calling default constructor)
If the object is a class type, the constructors are considered, performing overload resolution with the elements of the list
Aggregate
An aggregate type is defined as [dcl.init.aggr]
An aggregate is an array or a class with
-- no user-provided, explicit, or inherited constructors
-- no private or protected non-static data members
-- no virtual functions, and no virtual, private, or protected base classes
Having a deleted constructor does not count towards providing a constructor.
Elements of an aggregate is defined as
The elements of an aggregate are:
-- for an array, the array elements in increasing subscript order, or
-- for a class, the direct base classes in declaration order, followed by the direct non-static data members that are not members of an anonymous union, in declaration order.
Aggregate-initialization is defined as
[...] the elements of the initializer list are taken as initializers for the elements of the aggregate, in order.
Example e{}
Following the rules above the question why Example e{} is legal is because
the initializer is a braced initializer list
uses list initialization
since Example is an aggregate type
uses aggregate initialization
and therefore does not invoke any constructor
When you write Example e{}, it is not default constructed. It is aggregate initialized. So, yes it is perfectly fine.
In fact, the following compiles
struct S
{
S() = delete;
S(const S&) = delete;
S(S&&) = delete;
S& operator=(const S&) = delete;
};
S s{}; //perfectly legal
Turn off construction
Make sure that Example is not an aggregate type to stop aggregate initialization and delete its constructors.
This is often trivial as most classes have private or protected data members. As such, it is often forgotten that aggregate initialization exists in C++.
The simplest way to make a class non-aggregate would be
struct S
{
explicit S() = delete;
};
S s{}; //illegal, calls deleted default constructor
However, as of 2017 May 30, only gcc 6.1 and above and clang 4.0.0 will reject this, all versions of CL and icc will incorrectly accept this.
Other initializations
This is one of the craziest corners in C++, and it was hellish informative to look through the standard to understand what exactly happened. There have been lots of references already written and I will not attempt to explain them.
This question already has answers here:
What is this weird colon-member (" : ") syntax in the constructor?
(14 answers)
Why should I prefer to use member initialization lists?
(9 answers)
Closed 8 years ago.
I am moving from structural C to OOP C++ and I frequently found a special use of ":" symbol as an operator when declaring/defining constructors in C++. I roughly understood the use of this style but somebody explain me the exact programming technique with this constructor definition.
e.g.: 1
class time_stamp
{
public:
time_stamp(time &t_time)
: m_time(t_time)
{}
~time_stamp()
{
m_time.update(); // as soon as I'm destroyed, update the time
}
private:
time &m_time;
};
e.g.: 2
class threaded_class
{
public:
threaded_class()
: m_stoprequested(false), m_running(false)
{
pthread_mutex_init(&m_mutex);
}
~threaded_class()
{
pthread_mutex_destroy(&m_mutex);
}
/** Some other member declarations */
}
Please explain me use of ":" in below lines of codes from above 2 examples
time_stamp(time &t_time) : m_time(t_time){} and
threaded_class(): m_stoprequested(false), m_running(false)
{
pthread_mutex_init(&m_mutex);
}
The colon character : is used to denote the constructor member initializer list. This is the place where you can initiailze members of a class or call a base class constructor.
C++ Standard n3337 12.6.2 § 3:
A mem-initializer-list can initialize a base class using any
class-or-decltype that denotes that base class type.
C++ Standard n3337 12.6.2 § 7:
The expression-list or braced-init-list in a mem-initializer is used
to initialize the designated subobject (or, in the case of a
delegating constructor, the complete class object) according to the
initialization rules of 8.5 for direct-initialization.
Example:
class Foo {
int a;
};
If you would like integer a to have determined value after call to constructor is made you have to give a this value in constructor. There are two options:
in constructor body
Foo::Foo() {
a = 70;
}
in it's member initializer list
Foo::Foo() : a( 70) {
}
Initialization via a member initilization list should be preferred
It is always legal, is never less efficient than assignment inside the body of the constructor, and is often more efficient. The very important thing about initialization list is that it allows to direct initialize class member omitting a default construction of a member being subject to such a process.
As Scott Myers pointed out in his "Effective C++", if you fail to specify an initialization argument for class member, it's default constructor will be called. When you later perform an assignment to it inside your class constructor, you will call operator= on member variable. That will total two calls to member functions: one for the default constructor and one more for the assignment. You can omit a first call by specifying an initializer. Also as Scott Myers pointed out in his "Effective C++" : "from a purely pragmatic point of view, there are times when the initialization list must be used. In particular, const and reference members may only be initialized, never assigned".
A trap
(At least) Equally important thing is that members are not initialized in order of their appearance in initialization list but in order of declaration in class. Remember this to avoid errors like
/* trying to allocate very large block of memory
as a result of initializing a vector with
uninitialized integer: std::vector<int> v( N)
*/
class SearchEngine {
std::vector<int> v;
int N;
explicit SearchEngine( std::vector<int> const& keys)
: N( keys.size()), v( N), {
C++ Standard n3337 8.5.4 § 1:
List-initialization is initialization of an object or reference from a
braced-init-list. Such an initializer is called an initializer list,
and the comma-separated initializer-clauses of the list are called the
elements of the initializer list. An initializer list may be empty.
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. [ Note: List-initialization can be used — as
the initializer in a variable definition (8.5)
— as the initializer in
a new expression (5.3.4)
— in a return statement (6.6.3)
— as a
function argument (5.2.2)
— as a subscript (5.2.1)
— as an argument to
a constructor invocation (8.5, 5.2.3)
— as an initializer for a
non-static data member (9.2)
— in a mem-initializer (12.6.2)
— on the
right-hand side of an assignment (5.17)
[ Example:
int a = {1};
std::complex z{1,2};
new std::vector{"once",
"upon", "a", "time"}; // 4 string elements
f( {"Nicholas","Annemarie"}
); // pass list of two elements
return { "Norah" }; // return list of
one element
int* e {}; // initialization to zero / null pointer
x =
double{1}; // explicitly construct a double
std::map
anim = { {"bear",4}, {"cassowary",2}, {"tiger",7} };
— end example ] — end note ]
It's for member initialisation. This is the only place you can init members without them being default initialised.
If you do it within curly braces, the default constructor for members has already been invoked and your assigning new value to it. With the colon syntax, you decide how members are initialised (in terms of value fortrivial types and in terms of constructor with non trivial ones).
Initialization list. It is useful when you want to initialize the member objects right after construction. And you have to use it when a member object has a not a default constuctor.
And it's not just another way to initialize members, sometimes you have to use it, and most of the time you should use it to keep the code consistence.
Here is an example for a situation that you have to use it:
struct A
{
const X x; // X has not default constructor
A() : x(some_value) {}
};
Even if a member object has a default constructor, you should initialize it by initialization-list to avoid redundant construction.
struct A
{
string x;
A() : x("Hello") {}
};
In the above case, if you assign "Hello" inside the constructor's body, then you made an unnecessary call to string::string() and then x = "Hello";, which it can be replaced by just one call to string::string("Hello").
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.