As an addendum to this question, what is going on here:
#include
using namespace std;
struct A {
string s;
};
int main() {
A a = {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.
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.
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;
}
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
.
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.