As an addendum to this question , what is going on here:
#include <string>
using namespace std;
struct A {
string s;
};
int main() {
A a = {0};
}
Obviously, you can't set a std::string to zero. Can someone provide an explanation (backed with references to the C++ Standard, please) about what is actually supposed to happen here? And then explain for example):
int main() {
A a = {42};
}
Are either of these well-defined?
Once again an embarrassing question for me - I always give my structs constructors, so the issue has never arisen before.
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 as
with 0
, ie for as
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.
PS 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
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.