简体   繁体   中英

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::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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM