Why can I not brace initialize a struct derived from another struct?
Answer for C++ standard versions before C++17:
Your problem has to do with aggregate initialization: struct X
is an aggregate while struct Y
is not. Here is the standard quote about aggregates (8.5.1):
An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no brace-or-equal-initializers 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).
This clause specifies that if a class
has a base class, then it's not an aggregate. Here, struct Y
has struct X
as a base class and thus cannot be an aggregate type.
Concerning the particular problem you have, take the following clause from the standard:
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. If the initializer-clause is an expression and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed.
When you do X x = {0}
, aggregate initialization is used to initialize a
to 0
. However, when you do Y y = {0}
, since struct Y
is not an aggregate type, the compiler will look for an appropriate constructor. Since none of the implicitely generated constructors (default, copy and move) can do anything with a single integer, the compiler rejects your code.
Concerning this constructors lookup, the error messages from clang++ are a little bit more explicit about what the compiler is actually trying to do (online example):
Y Y = {0};
^ ~~~
main.cpp:5:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'int' to 'const Y &' for 1st argument
struct Y : public X {};
^
main.cpp:5:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'int' to 'Y &&' for 1st argument
struct Y : public X {};
^
main.cpp:5:8: note: candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided
Note that there is a proposal to extend aggregate initialization to support your use case, and it made it into C++17. If I read it correctly, it makes your example valid with the semantics you expect. So... you only have to wait for a C++17-compliant compiler.
Struct initialization of derived struct with a templated base type
You can only do aggregate initialization on aggregates. An aggregate is, from [dcl.init.aggr]:
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).
MoveEvent
is not an aggregate. Thus, you will have to add a constructor:
template <class Derived>
struct Event {
};
struct MoveEvent: Event<MoveEvent> {
MoveEvent(int x, int y) : x(x), y(y) { }
int x, y;
};
int main() {
MoveEvent event{5, 4}; // NOW this is fine
}
Can't initialize a struct using an initializer list if it inherits?
It works for C
because it is an aggregate and therefore it is using aggregate initialization but D
is not an aggregate because it has a base class. The obvious work-around as you mention is to write a constructor.
This is covered in the draft C++ standard section 8.5.1
Aggregates [dcl.init.aggr] with 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).
There is a proposal: Extension to aggregate initialization to remove that restriction. As chris points out this was accepted by Evolution Working Group but as far as I understand now needs to accepted by Core as well.
Using initializer list for a struct with inheritance
With inheritance, each base class subobject of the aggregate is initialized like a member. So to aggregate initialization, Child
has two subobjects: Parent
, and i
. So you need two initializers in your braced-init-list:
Child c{ {}, 1 };
Also, in order for Child
to be an aggregate, all subobjects must be public. So you can't have private base classes.
Of course, this assumes that Visual Studio implements the feature correctly. VS2017 15.5 is not C++17 compliant, but 15.7 supports this.
initializer list with struct and inheritance
Your code would work with C++17. Since C++17 both L
and Y
are considered as aggregate type:
no
virtual, private, or protected (since C++17)
base classes
Then brace elision is allowed; i.e. the braces around nested initializer lists for subaggregate could be elided and then you can just write L l {1, 2};
and Y y {1, 2};
(instead of L l {{1, 2}};
and Y y {{1}, 2};
).
If the aggregate initialization uses
copy- (until C++14)
list-initialization syntax (T a = {args..}or T a {args..} (since C++14)
), the braces around the nested initializer lists may be elided (omitted), in which case as many initializer clauses as necessary are used to initialize every member or element of the corresponding subaggregate, and the subsequent initializer clauses are used to initialize the following members of the object.
LIVE
Before C++17 it doesn't work because list initialization is performed instead, the appropriate constructors are tried to be used and fails.
Why is brace-initialization not needed with array of struct?
6.7.9 Initialization/20 states how such struct elements are initialized:
[..] If the initializer of a subaggregate or contained union begins
with a left brace, the initializers enclosed by that brace and its
matching right brace initialize the elements or members of the
subaggregate or the contained union. Otherwise, only enough
initializers from the list are taken to account for the elements or
members of the subaggregate or the first member of the contained
union; any remaining initializers are left to initialize the next
element or member of the aggregate of which the current subaggregate
or contained union is a part.
(emphasis mine)
So it's valid. And thus
ar[] = {
1,"asd",
2, "qwe",
3, "poi"
};
is equivalent to:
ar[] = {
{1,"asd"},
{2, "qwe"},
{3, "poi"}
};
and ar
contains 3 elements.
Brace Initialize struct with virtual functions
There isn't a way. like in this text you mentioned
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).
this means that a class/struct or an array that has
- no user-provided constructors
- no private or protected non-static data members
- no base classes
- and no virtual functions
is an aggregate. and only an aggregate can have brace initialization. so in this case, your struct has a virtual function that violates one of the laws above and makes it a non-aggregate.
I don't know why this is the case.
I guess that if your struct is similar to the struct in c then your struct would work.
so like Hassan's answer, you should use a parameterized constructor instead.
Initializer list for struct deriving from class (C++)
It does not work because Derived
is not an aggregate since it has a base class with virtual members.
The code compiles and runs when removing the virtual destructor in Base
and using C++17
.
If Base
requires a virtual destructor, Derived
can implement a custom constructor allowing initialization using curly brackets.
class Base {
public:
virtual ~Base() = default;
};
struct Derived : public Base {
Derived(int m): m1(m) {}
int m1;
};
int main() {
Derived d{ 1 };
return 0;
}
Why can an aggreggate struct be brace-initialized, but not emplaced using the same list of arguments as in the brace initialization?
Is this an oversight in the Standard?
It is considered a defect in the standard, tracked as LWG #2089, which was resolved by C++20. There, constructor syntax can perform aggregate initialization on an aggregate type, so long as the expressions provided wouldn't have called the copy/move/default constructors. Since all forms of indirect initialization (push_back
, in_place
, make_*
, etc) uses constructor syntax explicitly, they can now initialize aggregates.
Pre-C++20, a good solution to it was elusive.
The fundamental problem comes from the fact that you cannot just use braced-init-lists willy-nilly. List initialization of types with constructors can actually hide constructors, such that certain constructors can be impossible to call through list initialization. This is the vector<int> v{1, 2};
problem. That creates a 2-element vector
, not a 1-element vector whose only element is 2.
Because of this, you cannot use list initialization in generic contexts like allocator::construct
.
Which brings us to:
I would think there's be a SFINAE trick to do that if possible, else resort to brace init that also works for aggregates.
That would require using the is_aggregate
type trait from C++17. But there's a problem with that: you would then have to propagate this SFINAE trick into all of the places where indirect initialization is used. This includes any/variant/optional
's in_place
constructors and emplacements, make_shared/unique
calls, and so forth, none of which use allocator::construct
.
And that doesn't count user code where such indirect initialization is needed. If users don't do the same initialization that the C++ standard library does, people will be upset.
This is a sticky problem to solve in a way that doesn't bifurcate indirect initialization APIs into groups that allow aggregates and groups that don't. There are many possible solutions, and none of them are ideal.
The language solution is the best of the bunch.
Related Topics
Sorting Two Corresponding Arrays
Cout or Printf Which of the Two Has a Faster Execution Speed C++
C++ Templates, Undefined Reference
What Does '<Cuchar>' Provide, and Where Is It Documented
Benchmarking (Python VS. C++ Using Blas) and (Numpy)
Dead Code Detection in Legacy C/C++ Project
Catching Exceptions from a Constructor's Initializer List
How Does the Modulus Operator Work
How to Read a Growing Text File in C++
How to Get a List of Video Capture Devices (Web Cameras) on Linux ( Ubuntu )? (C/C++)
How to Pass a Template Function in a Template Argument List
Should I Store Entire Objects, or Pointers to Objects in Containers
Common Array Length MACro for C