Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing structs in C++

Tags:

c++

struct

People also ask

Can you initialize a struct in C?

Structure members cannot be initialized with declaration.

Does C initialize structs to 0?

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.

How are structs initialized?

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.