开发者

Initializing structs in C++

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::strin开发者_高级运维g 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 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜