This code compiles fine with GCC 5.X, MSVC, but GCC 6.X gives error:
"converting to 'a' from initializer list would use explicit
constructor 'a::a()'", clang "chosen constructor is explicit in
copy-initialization".
Removing explicit or changing to a c{} fixes the problem, but I`m curious why it works this way.
class a
{
public:
explicit a () {}
};
struct b
{
a c;
};
int main() {
b d{};
}
b is an aggregate. When you initialize it using an initializer list, the elements in the list will initialize the first n members of the aggregate, where n is the number of elements in the list. The remaining elements of the aggregate are copy-list-initialized.
So in your example, c will be copy-list-initialized, but that is ill-formed if the chosen constructor is explicit, hence the error.
The relevant standard quotes are
[dcl.init.aggr]/3
When an aggregate is initialized by an initializer list as specified in [dcl.init.list], the elements of the initializer list are taken as initializers for the elements of the aggregate.
The explicitly initialized elements of the aggregate are determined as follows:
...
— If the initializer list is an initializer-list, the explicitly initialized elements of the aggregate are the first n elements of the aggregate, where n is the number of elements in the initializer list.
— Otherwise, the initializer list must be {}, and there are no explicitly initialized elements.
[dcl.init.aggr]/5
For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
...
— Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
The effect of copy initializing c from an empty initializer list is described in
[dcl.init.list]/3
List-initialization of an object or reference of type T is defined as follows:
...
— Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
[dcl.init]/8
To value-initialize an object of type T means:
...
— if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
[dcl.init]/7
To default-initialize an object of type T means:
— If T is a (possibly cv-qualified) class type, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one for the initializer () is chosen through overload resolution. The constructor thus selected is called, with an empty argument list, to initialize the object.
[over.match.ctor]
... For copy-initialization, the candidate functions are all the converting constructors of that class.
[class.conv.ctor]/1
A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters (if any) to the type of its class. Such a constructor is called a converting constructor.
In the example above, a has no converting constructors, so overload resolution fails. The (non-normative) example in [class.conv.ctor]/2 even contains a very similar case
struct Z {
explicit Z();
explicit Z(int);
explicit Z(int, int);
};
Z c = {}; // error: copy-list-initialization
You can avoid the error by providing a default member initializer for c
struct b
{
a c{}; // direct-list-initialization, explicit ctor is OK
};
Related
I learnt about the explicit keyword in c++ and its uses. Then for practice I wrote the following program that works with MSVC but not with gcc and clang. Demo.
class Testing
{
public:
explicit Testing() = default;
};
int main()
{
Testing t[2] = {}; //works with msvc but not with gcc and clang
}
As you can see, the above works with msvc but not with clang and gcc. I am using C++20 and want to know which compiler is correct for this example according to the C++20 standard.
GCC produces the following error:
<source>:8:21: error: converting to 'Testing' from initializer list would use explicit constructor 'constexpr Testing::Testing()'
8 | Testing t[2] = {}; //works with msvc but not with gcc and clang
| ^
The program is ill-formed and gcc and clang are correct in rejecting the program because Testing t[2]={}; is copy-list-initialization and since t is an array it uses aggregate initialization which in turn result in copy initialization of t's member(s) using the empty initializer list {} which fails as the default ctor is explicit(and so Testing::Testing() cannot be used in copy initialization).
This is explained in detail below.
From list initialization:
3) List-initialization of an object or reference of type T is defined as follows:
3..4) Otherwise, if T is an aggregate, aggregate initialization is performed
This means that since T in our example is Testing[2] which is an array(and so an aggregate), aggregate initialization will be performed.
Next, from aggregate initialization:
5) For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
5.4) Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list.
This means that the array elements(which are not explicitly initialized btw) will be copy initialized from an empty initializer list {}. What this in turn means is that it is as if for each element of the array we're writing Testing array_element_nth = {}; which results in value-initialization as per dcl.init.list#3.5:
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
Next, from value initialization:
To value-initialize an object of type T means:
if T has either no default constructor ([class.default.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
This means that the array element will be default initialized.
So we move onto default initialization:
To default-initialize an object of type T means:
If T is a (possibly cv-qualified) class type ([class]), constructors are considered.
The applicable constructors are enumerated ([over.match.ctor]), and the best one for the initializer () is chosen through overload resolution ([over.match]).
The constructor thus selected is called, with an empty argument list, to initialize the object.
This means that that the constructors will be enumerated and best of them will be chosen with (). So we move onto over.match.ctor:
When objects of class type are direct-initialized, copy-initialized from an expression of the same or a derived class type ([dcl.init]), **or default-initialized, overload resolution selects the constructor.
For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized.
For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors ([class.conv.ctor]) of that class.
The argument list is the expression-list or assignment-expression of the initializer.
This means that the default ctor will be used but since this is in copy initialization context, the explicit ctor cannot be used.
Thus, msvc is wrong in accepting the program.
Here is the msvc bug:
MSVC compiles invalid program involving explicit constructor
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.
Generally speaking, parentheses and braces are very different. For minimal reproducible example:
#include <array>
#include <vector>
int main()
{
std::array<int, 2>{42, 42}; // OK
std::array<int, 2>(42, 42); // ill-formed
std::vector<int>{42, 42}; // two elements
std::vector<int>(42, 42); // 42 elements
}
However, since empty braces use value-initialization instead of std::initializer_list constructors, is there any different between empty parentheses and empty braces when used as initializers?
More formally, given a type T, is it possible that T() and T{} are different? (Either may be ill-formed.)
(This question and answer was originally created for C++20 standard compatible vector on Code Review Stack Exchange. It is intended that the answer covers all possible cases. Please inform me if I missed any.)
(The links in this answer point to N4659, the C++17 final draft. However, at the time of this writing, the situation is exactly the same for C++20.)
Yes, it's possible. There are two cases:
Case 1
T is a non-union aggregate for which zero-initialization, followed by default-initialization if the aggregate has a non-trivial constructor, differs from copy-initialization from {}.
We can use std::in_place_t to construct our example, because it has an explicit default constructor. Minimal reproducible example:
#include <utility>
struct A {
std::in_place_t x;
};
int main()
{
A(); // well-formed
A{}; // ill-formed
}
(live demo)
Case 1, variant
T is a union aggregate for whose first element default-initialization differs from copy-initialization from {}.
We can change struct to union in Case 1 to form a minimal reproducible example:
#include <utility>
union A {
std::in_place_t x;
};
int main()
{
A(); // well-formed
A{}; // ill-formed
}
(live demo)
Case 2
T is of the form const U& or U&& where U can be list-initialized from {}.
Minimal reproducible example:
int main()
{
using R = const int&;
R{}; // well-formed
R(); // ill-formed
}
(live demo)
Detailed explanation
T()
Per [dcl.init]/17:
The semantics of initializers are as follows. The destination type
is the type of the object or reference being initialized and the
source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the
source type is not defined.
If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is list-initialized.
If the destination type is a reference type, see [dcl.init.ref].
If the destination type is an array of characters, an array of char16_t, an array of char32_t, or an array of wchar_t, and
the initializer is a string literal, see [dcl.init.string].
If the initializer is (), the object is value-initialized.
[...]
We can conclude that T() always value-initializes the object.
T{}
Per [dcl.init]/17:
The semantics of initializers are as follows. The destination type
is the type of the object or reference being initialized and the
source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the
source type is not defined.
If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is
list-initialized.
[...]
That's enough for us to conclude that T{} always list-initializes the object.
Now let's go through [dcl.init.list]/3. I have highlighted the possible cases. The other cases are not possible because they require the initializer list to be non-empty.
List-initialization of an object or reference of type T is defined
as follows:
(3.1) If T is an aggregate class and the initializer list has a single element of type cv U, where U is T or a class derived from
T, the object is initialized from that element (by
copy-initialization for copy-list-initialization, or by
direct-initialization for direct-list-initialization).
(3.2) Otherwise, if T is a character array and the initializer list has a
single element that is an appropriately-typed string literal
([dcl.init.string]), initialization is performed as described in that
section.
(3.3) Otherwise, if T is an aggregate, aggregate initialization is
performed.
(3.4) Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is
value-initialized.
(3.5) Otherwise, if T is a specialization of
std::initializer_list<E>, the object is constructed as described
below.
(3.6) Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best
one is chosen through overload resolution ([over.match],
[over.match.list]). If a narrowing conversion (see below) is required
to convert any of the arguments, the program is ill-formed.
(3.7) Otherwise, if T is an enumeration with a fixed underlying type
([dcl.enum]), the initializer-list has a single element v, and the
initialization is direct-list-initialization, the object is
initialized with the value T(v) ([expr.type.conv]); if a narrowing
conversion is required to convert v to the underlying type of T,
the program is ill-formed.
(3.8) Otherwise, if the initializer list has a single element of type E
and either T is not a reference type or its referenced type is
reference-related to E, the object or reference is initialized from
that element (by copy-initialization for copy-list-initialization, or
by direct-initialization for direct-list-initialization); if a
narrowing conversion (see below) is required to convert the element to
T, the program is ill-formed.
(3.9) Otherwise, if T is a reference type, a prvalue of the type referenced by T is generated. The prvalue initializes its result
object by copy-list-initialization or direct-list-initialization,
depending on the kind of initialization for the reference. The prvalue
is then used to direct-initialize the reference.
(3.10) Otherwise, if the initializer list has no elements, the object is value-initialized.
(3.11) Otherwise, the program is ill-formed.
(Note: (3.6) is not possible in this case, for the following reason: (3.4) covers the case where a default constructor is present. In order for (3.6) to be considered, a non-default constructor has to be called, which is not possible with an empty initializer list. (3.11) is not possible because (3.10) covers all cases.)
Now let's analyze the cases:
(3.3)
For an aggregate, value-initialization first performs zero-initialization and then, if the element has a non-trivial default constructor, default-initialization, on the aggregate, per [dcl.init]/8:
To value-initialize an object of type T means:
[...]
if T is a (possibly cv-qualified) class type without a user-provided or deleted default constructor, then the object is
zero-initialized and the semantic constraints for
default-initialization are checked, and if T has a non-trivial default
constructor, the object is default-initialized;
[...]
Non-union aggregates
When copy-initializing a non-union aggregate from {}, elements that are not explicitly initialized with a default member initializer are copy-initialized from {} per [dcl.init.aggr]/8:
If there are fewer initializer-clauses in the list than there are
elements in a non-union aggregate, then each element not explicitly
initialized is initialized as follows:
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]).
Otherwise, the program is ill-formed.
[...]
See Case 1.
Union aggregates
If the aggregate is a union, and no member has a default member initializer, then copying-initializing the aggregate from {} copy-initializes the first element from {}: [dcl.init.aggr]/8:
[...]
If the aggregate is a union and the initializer list is empty, then
if any variant member has a default member initializer, that member is initialized from its default member initializer;
otherwise, the first member of the union (if any) is copy-initialized from an empty initializer list.
See Case 1, variant.
(3.4)
Value-initialized, so no difference.
(3.9)
T() isn't allowed if T is a reference per [dcl.init]/9:
A program that calls for default-initialization or
value-initialization of an entity of reference type is ill-formed.
See Case 2.
(3.10)
Similarly, value-initialized. No difference.
is there any different between empty parentheses and empty braces when used as initializers
There are cases where empty parentheses cannot be used as initialiser because it would be syntactically ambiguous with a function declaration:
T t(); // function declaration; not initialisation
T t{}; // value initialisation
More formally, given a type T, is it possible that T() and T{} are different?
The ambiguity described above has a case where T() is parsed as pointer to function, known as the Most Vexing Parse:
U t(T()); // function declaration; not initialisation
U t(T{}); // value initialisation, and direct initialisation
#include<cstddef>
template<typename T, std::size_t N>
struct A {
T m_a[N];
A() : m_a{} {}
};
struct S {
explicit S(int i=4) {}
};
int main() {
A<S, 3> an;
}
The above code compiles fine with MSVC (2017), but fails with clang 3.8.0 (Output of clang++ --version && clang++ -std=c++14 -Wall -pedantic main.cpp):
clang version 3.8.0 (tags/RELEASE_380/final 263969)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin
main.cpp:6:15: error: chosen constructor is explicit in copy-initialization
A() : m_a{} {}
^
main.cpp:14:13: note: in instantiation of member function 'A<S, 3>::A' requested here
A<S, 3> an;
^
main.cpp:10:14: note: constructor declared here
explicit S(int i=4) {}
^
main.cpp:6:15: note: in implicit initialization of array element 0 with omitted initializer
A() : m_a{} {}
^
1 error generated.
clang 5.0 also refuses to compile this:
<source>:6:17: error: expected member name or ';' after declaration specifiers
A() : m_a{} {}
^
<source>:6:14: error: expected '('
A() : m_a{} {}
^
2 errors generated.
If I use simple parentheses in As constructor to (i.e. A() : m_a() {}), it compiles fine. From cppreference I would have suspected that both should result in the same (i.e. value initialization). Am I missing something or is this a bug in one of the compilers?
Clang is correct.
Your confusion comes from:
From cppreference I would have suspected that both should result in the same (i.e. value initialization).
No they have different effects. Note the notes in that page:
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.
That means when initialized with braced-init-list, for aggregate type, aggregate-initialization is preferred to be performed. With A() : m_a{} {}, and m_a is an array, which belongs to aggregate type, then aggregate initialization is performed instead:
(emphasis mine)
Each direct public base, (since C++17) array element, or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.
and
If the number of initializer clauses is less than the number of members and bases (since C++17) or initializer list is completely empty, the remaining members and bases (since C++17) are initialized by their default initializers, if provided in the class definition, and otherwise (since C++14) by 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).
That means, the remaining elements, i.e. all the 3 elements of m_a will be copy-initialized from the empty list; for empty list the default constructor of S will be considered but it's declared as explicit; the copy-initialization won't invoke explicit constructors:
copy-list-initialization (both explicit and non-explicit constructors are considered, but only non-explicit constructors may be called)
On the other hand, A() : m_a() {} performs value initialization, then
3) if T is an array type, each element of the array is value-initialized;
then
1) if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;
then the default constructor of S is invoked to initialize the elements of m_a. Whether it's explicit or not doesn't matter for default initialization.
For m_a{}:
[dcl.init]/17.1 sends us to [dcl.init.list], and [dcl.init.list]/3.4 says that we perform aggregate initialization on m_a per [dcl.init.aggr].
The semantics of initializers are as follows. [...]
If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is list-initialized.
[...]
List-initialization of an object or reference of type T is defined as follows:
[...]
Otherwise, if T is an aggregate, aggregate initialization is performed.
[...]
[dcl.init.aggr]/5.2 says that we copy-initialize each element of m_a from an empty initializer list, i.e., {}.
For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:
[...]
Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
[...]
This sends us back to [dcl.init]/17.1 for the initialization of each element, which again sends us to [dcl.init.list].
This time we hit [dcl.init.list]/3.5, which says that the element is value-initialized.
List-initialization of an object or reference of type T is defined as follows:
[...]
Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
[...]
Which brings us to [dcl.init]/8.1, which says that the element is default-initialized.
To value-initialize an object of type T means:
if T is a (possibly cv-qualified) class type with either no default constructor ([class.ctor]) or a default constructor that is user-provided or deleted, then the object is default-initialized;
[...]
Which hits [dcl.init]/7.1, which says we enumerate constructors per [over.match.ctor] and perform overload resolution on the initializer ();
To default-initialize an object of type T means:
If T is a (possibly cv-qualified) class type, constructors are considered. The applicable constructors are enumerated
([over.match.ctor]), and the best one for the initializer () is
chosen through overload resolution. The constructor thus selected is
called, with an empty argument list, to initialize the object.
[...]
and [over.match.ctor] says:
For direct-initialization or default-initialization that is not in the
context of copy-initialization, the candidate functions are all the
constructors of the class of the object being initialized. For
copy-initialization, the candidate functions are all the converting
constructors of that class.
This default-initialization is in the context of copy-initialization, so the candidate functions are "all the converting constructors of that class".
The explicit default constructor is not a converting constructor. As a result, there is no viable constructor. Hence overload resolution fails, and the program is ill-formed.
For m_a():
We hit [dcl.init]/17.4, which says that the array is value-initialized.
The semantics of initializers are as follows. [...]
[...]
If the initializer is (), the object is value-initialized.
[...]
Which brings us to [dcl.init]/8.3, which says that each element is value-initialized.
To value-initialize an object of type T means:
[...]
if T is an array type, then each element is value-initialized;
[...]
Which again brings us to [dcl.init]/8.1, and then to [dcl.init]/7.1, and so we again enumerate constructors per [over.match.ctor] and perform overload resolution on the initializer ();
This time, the default-initialization is not in the context of copy-initialization, so the candidate functions are "all the constructors of the class of the object being initialized".
This time, the explicit default constructor is a candidate and selected by overload resolution. So the program is well-formed.
This is explicitly ill-formed by the Standard (the question is, though, why?):
m_a{} list-initializes the S::m_a:
[dcl.init.list]/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 initializer-list or designated-initializer-clauses of the designated-initializer-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.
As an array, A<S, 3>::m_a is an aggregate type ([dcl.init.aggr]/1).
[dcl.init.aggr]/3.3
When an aggregate is initialized by an initializer list as specified in [dcl.init.list], [...]
3.3 the initializer list must be {}, and there are no explicitly initialized elements.
following, since there are no explicitly initialized elements:
[dcl.init.aggr]/5.2
For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows: [...]
5.2 if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).
Each S of A<S, 3>::m_a is, then, copy-initialized:
[dcl.init]/17.6.3
The semantics of initializers are as follows.
The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression.
If the initializer is not a single (possibly parenthesized) expression, the source type is not defined. [...]
17.6 If the destination type is a (possibly cv-qualified) class type: [...]
17.6.3 Otherwise (i.e., for the remaining copy-initialization cases), user-defined conversion sequences that can convert from the source type to the destination type or (when a conversion function is used) to a derived class thereof are enumerated as described in [over.match.copy], and the best one is chosen through overload resolution.
If the conversion cannot be done or is ambiguous, the initialization is ill-formed.
Since the default constructor of S is explicit, it cannot convert from the source type to the destination type (S).
The syntax using m_a() is, on the other hand, not aggregate member initialization and does not invoke copy-initialization.
If I understand the standard correctly clang is correct.
According to [dcl.init.aggr]/8.5.1: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.
And further down in the same clause [dcl.init.aggr]/8.5.1: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
According to the rules for list initialization [over.match.list]/13.3.1.7
In copy-list-initialization, if an explicit constructor is chosen, the
initialization is ill-formed.
Since C++2003 we have value-initialisation as well as default-initialisation. Meaning that:
struct Foo {
int i;
std :: string s;
};
Foo f1; // f1.s is default-constructed, f1.i is uninitialised
Foo f2 = Foo (); // f1.s is default-constructed, f1.i is zero
Right?
Now suppose I have this class
class Bar : public Foo {
int n;
Foo f [SIZE];
public:
Bar ();
};
When I write the constructor for Bar, I may want to default- or value-initialise either the parent class or the f[] member. I think the choice for initialising the parent is simply:
Bar :: Bar () : Foo (), n (-1) {} // Parent is value-initialised (Foo::i is zero)
Bar :: Bar () : n (-1) {} // Parent is default-initialised (Foo::i is undefined)
But what about the f[] member?
How do I default-initialise all members?
How do I value-initialise all members?
If I use C++11 initializer lists, what happens if the initializer list has a different size than SIZE?
Actually, according to standard value-initialization seems to be the process of calling whatever form of user-defined default constructor there or to zero-initialize objects which don't have a default constructor (see 8.5 [decl.init] paragraph 7:
To value-initialize an object of type T means:
— if T is a (possibly cv-qualified) class type (Clause 9) with a user-provided constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
— if T is a (possibly cv-qualified) non-union class type without a user-provided constructor, then the object is zero-initialized and, if T’s implicitly-declared default constructor is non-trivial, that constructor is called.
— if T is an array type, then each element is value-initialized; — otherwise, the object is zero-initialized.
In this sense you can value initialize your array member since C++1998 using f() in the member initializer list. If you want to use specific values, you need to use C++2011 with an initializer list e.g. f({ 1, 2, 3 }). If there are fewer arguments than there are elements, the remaining elements are value initialized. Actually, the are initialized from an empty initializer list (8.5.1 [dcl.init.aggr] paragraph 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 an empty initializer list.
... and this initialization from an empty initializer list means according to 8.5.4 [dcl.init.list] paragraph 3, bullet 7:
Otherwise, if the initializer list has no elements, the object is value-initialized.