If all of your class/struct data members lack initializers, you can use uniform initialization syntax to construct the object.
struct foo
{
int i;
float f;
};
...
foo bar{ 5, 3.141f };
But if one or more members have initializers, uniform initialization syntax becomes invalid.
struct foo
{
int i;
float f = 0;
};
...
foo bar{ 5, 3.141f }; // Compiler error.
I surmise that the addition of a data member initializer automatically implements one or more default constructors and suppresses the default implementation of the initialization_list constructor. Is that the intended standard? Why does it work this way?
Yes, this is intended by the standard. What you are attempting here is aggregate initialization. Unfortunately, your second foo is no longer considered an aggregate due to the equal initializer of f. See 8.5.1 [dcl.init.aggr] (emphasis mine):
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equalinitializers for non-static data members (9.2), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
Because you have an equal initializer for the member f, you will need to provide a custom constructor to support the syntax you are after:
struct foo
{
int i;
float f = 0;
constexpr foo(int i, float f) : i(i), f(f) { }
};
...
foo bar{ 5, 3.141f }; // now okay
As to why this was specified in the standard, I have no idea.
Related
Someone gave me (part of) the following code:
struct MyStruct
{
int x = {};
int y = {};
};
I never saw this syntax before, what does initialization with {} mean?
This is default member initializer (since C++11),
Through a default member initializer, which is a brace or equals initializer included in the member declaration and is used if the member is omitted from the member initializer list of a constructor.
The initialization itself is copy-list-initialization (since C++11), as the effect, the data member x and y would be value-initialized (and zero-initialized as built-in type) to 0.
Since the C++11 standard there are two ways to initialize member variables:
Using the constructor initialization list as "usual":
struct Foo
{
int x;
Foo()
: x(0)
{
}
};
Use the new inline initialization where members are getting their "default" values using normal initialization syntax:
struct Foo
{
int x = 0;
};
Both these ways are for many values and types equivalent.
Is there a way to use initializer lists to initialize Bar?
struct Foo {
int i[2];
};
struct Bar : Foo {};
Foo f{0, 1}; // OK
Bar b{0, 1}; // error: no matching function for call to
// ‘Bar::Bar(< brace-enclosed initializer list>)’
Foo is an aggregate, and aggregates can be initialised using braced-enclosed lists performing aggregate initialisation. That is why Foo can be initialised using {0, 1} even without a constructor.
However, whether Bar is also an aggregate depends on the version of C++ which you're using. In C++14 and earlier, aggregates cannot have base classes, so Bar is not an aggregate. It therefore cannot be initialised using aggregate initialisation and would need an appropriate constructor. [Live example]
In C++17, the definition of "aggregate" was broadened to also include classes with non-virtual public base classes, so in C++17, Bar is an aggregate and indeed, can be initialised using aggregate initialisation: [Live example]
Since Foo is an aggregate type it's being initialized through aggregate initialization, which does not support base classes initialization as of c++14. But the good news are that it will be supported in c++17 :)
Example taken from cppreference:
// aggregate
struct base1 { int b1, b2 = 42; };
// non-aggregate
struct base2 {
base2() : b3(42) {}
int b3;
};
// aggregate in C++17
struct derived : base1, base2 { int d; };
derived d1{ {1, 2}, { }, 4}; // d1.b1 = 1, d1.b2 = 2, d1.b3 = 42, d1.d = 4
derived d2{ { }, { }, 4}; // d2.b1 = 0, d2.b2 = 42, d2.b3 = 42, d2.d = 4
It compiles, but with C++17 where aggregates can have base classes. This allows for list initialization of base data members. see here for more:
http://en.cppreference.com/w/cpp/language/aggregate_initialization
The effects of aggregate initialization are:
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.
I'm wetting my feet with C++11 and am really confused why this doesn't work:
template <class T>
struct A {
size_t size() const { return sizeof(T); }
};
struct B : A<B> {
int x;
int y;
};
B var {1, 5};
I'm using gcc 4.8.2 and get an error saying:
no matching function for call to 'B(<brace-enclosed initializer list>)'
It works just fine when I don't derive from A, so does the derivation somehow change the POD-ness of my struct B?
Aggregate-initialization requires your type to be an aggregate. An aggregate cannot have base classes:
An aggregate is an array or a class (Clause 9) with no user-provided
constructors (12.1), no private or protected non-static data members
(Clause 11), no base classes (Clause 10), and no virtual functions
(10.3).
I believe modern C++ initializer lists are very useful for initializing objects, to the point of removing the need for defining your own constructor:
struct point
{
float coord[3];
};
point p = {1.f, 2.f, 3.f}; // nice !
However, this doesn't work when my class inherits from another class:
template<typename T>
class serializable
{
protected:
serializable() = default;
...
// other stuff
}
struct point : public serializable<point>
{
float coord[3];
};
point p = {1.f, 2.f, 3.f}; // Doesn't work :(
I tried adding point() = default; to my point class, but that didn't work either. How can I still initialize point with an initializer list?
Your original case relied upon aggregate initialization [dcl.init.list]:
List-initialization of an object or reference of type T is defined as follows:
...
— Otherwise, if T is an aggregate, aggregate initialization is performed
Where an aggregate and aggregate initialiazation are, from [dcl.init.aggr], emphasis mine:
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or
protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).
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.
But now, since point has a base class (serializable<point>), point is no longer an aggregate and no longer supports aggregate initialization.
The solution is to simply provide such a constructor to initialize point:
struct point : public serializable<point>
{
template <typename... T>
point(T... ts)
: coord{ts...}
{ }
float coord[3];
};
Consider the following code:
class A
{
private:
struct B { private: int i; friend class A; };
public:
static void foo1()
{
B b;
b.i = 0;
}
static void foo2()
{
B b = {0};
}
};
Why does foo1 work but not foo2? Is not the struct initializer constructor visible for class A? Is there anyway to make this work in C++11?
(Note, removing the private makes foo2 working.)
Why does foo1 work but not foo2? Is not the struct initializer constructor visible for class A?
B b = {0};
Does not work because B is not an Aggregate. And it is not an Aggregate because it has an non-static private data member. If you remove the private specifier, B becomes an Aggregate and hence can be initialized in this manner.
C++03 Standard 8.5.1 Aggregates
Para 7:
If there are fewer initializers in the list than there are members in the aggregate, then each member not explicitly initialized shall be value-initialized (8.5).
[Example:
struct S { int a; char* b; int c; };
S ss = { 1, "asdf" };
initializes ss.a with 1, ss.b with "asdf", and ss.c with the value of an expression of the form int(), that is,0. ]
C++03 standard 8.5.1 §1:
An aggregate is an array or a class (clause 9) with no user-declared
constructors (12.1), no private or protected non-static data members (clause 11),
no base classes (clause 10), and no virtual functions (10.3).