Structure members cannot be initialized with declaration.
You don't have to initialise every element of a structure, but can initialise only the first one; you don't need nested {} even to initialise aggregate members of a structure. Anything in C can be initialised with = 0 ; this initialises numeric elements to zero and pointers null.
When initializing a struct, the first initializer in the list initializes the first declared member (unless a designator is specified) (since C99), and all subsequent initializers without designators (since C99)initialize the struct members declared after the one initialized by the previous expression.
Your struct is an aggregate, so the ordinary rules for aggregate initialization work for it. The process is described in 8.5.1. Basically the whole 8.5.1 is dedicated to it, so I don't see the reason to copy the whole thing here. The general idea is virtually the same it was in C, just adapted to C++: you take an initializer from the right, you take a member from the left and you initialize the member with that initializer. According to 8.5/12, this shall be a copy-initialization.
When you do
A a = { 0 };
you are basically copy-initializing a.s
with 0
, i.e. for a.s
it is semantically equivalent to
string s = 0;
The above compiles because std::string
is convertible from a const char *
pointer. (And it is undefined behavior, since null pointer is not a valid argument in this case.)
Your 42
version will not compile for the very same reason the
string s = 42;
will not compile. 42
is not a null pointer constant, and std::string
has no means for conversion from int
type.
P.S. Just in case: note that the definition of aggregate in C++ is not recursive (as opposed to the definition of POD, for example). std::string
is not an aggregate, but it doesn't change anything for your A
. A
is still an aggregate.
8.5.1/12 "Aggregates" says:
All implicit type conversions (clause 4) are considered when initializing the aggregate member with an initializer from an initializer-list.
So
A a = {0};
will get initialized with a NULL char*
(as AndreyT and Johannes indicated), and
A a = {42};
will fail at compile time since there's no implicit conversion that'll match up with a std::string
constructor.
0 is a null pointer constant
S.4.9:
A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero.
A null pointer constant can be converted to any other pointer type:
S.4.9:
A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type
What you gave for the definition of A
is considered an aggregate:
S.8.5.1:
An aggregate is an array or a class with no user-declared constructors, no private or protected non-static data members, no base classes, and no virtual functions.
You are specifying an initializer clause:
S.8.5.1:
When an aggregate is initialized the initializer can contain an initializer-clause consisting of a brace enclosed, comma-separated list of initializer-clauses for the members of the aggregate
A
contains a member of the aggregate of type std::string
, and the initializer clause applies to it.
Your aggregate is copy-initialized
When an aggregate (whether class or array) contains members of class type and is initialized by a brace enclosed initializer-list, each such member is copy-initialized.
Copy initializing means that you have the equivalent to std::string s = 0
or std::string s = 42
;
S.8.5-12
The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form T x = a;
std::string s = 42
will not compile because there is no implicit conversion, std::string s = 0
will compile (because an implicit conversion exists) but results in undefined behavior.
std::string
's constructor for const char*
is not defined as explicit
which means you can do this: std::string s = 0
Just to show that things are actually being copy-initialized, you could do this simple test:
class mystring
{
public:
explicit mystring(const char* p){}
};
struct A {
mystring s;
};
int main()
{
//Won't compile because no implicit conversion exists from const char*
//But simply take off explicit above and everything compiles fine.
A a = {0};
return 0;
}
As people have pointed out, this "works" because string has a constructor that can take 0 as a parameter. If we say:
#include <map>
using namespace std;
struct A {
map <int,int> m;
};
int main() {
A a = {0};
}
then we get a compilation error, as the map class does not have such a constructor.
In 21.3.1/9 the standard forbids the char*
argument of the relevant constructor of a std::basic_string
from being a null pointer. This should throw a std::logic_error
, but I have yet to see where in the standard is the guarantee that violating a precondition throws a std::logic_error
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With