Initializing structs in C++ - 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 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.

Related

Why doesn't defining array without size with initializer work inside class definition? [duplicate]

Consider the code:
struct Foo
{
const char str[] = "test";
};
int main()
{
Foo foo;
}
It fails to compile with both g++ and clang++, spitting out essentially
error: array bound cannot be deduced from an in-class initializer
I understand that this is what the standard probably says, but is there any particular good reason why? Since we have a string literal it seems that the compiler should be able to deduce the size without any problem, similarly to the case when you simply declare an out-of-class const C-like null terminated string.
The reason is that you always have the possibility to override an in-class initializer list in the constructor. So I guess that in the end, it could be very confusing.
struct Foo
{
Foo() {} // str = "test\0";
// Implementing this is easier if I can clearly see how big `str` is,
Foo() : str({'a','b', 'c', 'd'}) {} // str = "abcd0"
const char str[] = "test";
};
Notice that replacing const char with static constexpr char works perfectly, and probably it is what you want anyway.
As mentioned in the comments and as answered by #sbabbi, the answer lies in the details
12.6.2 Initializing bases and members [class.base.init]
In a non-delegating constructor, if a given non-static data member or
base class is not designated by a mem-initializer-id (including the
case where there is no mem-initializer-list because the constructor
has no ctor-initializer) and the entity is not a virtual base class of
an abstract class (10.4), then
if the entity is a non-static data member that has a brace-or-equal-initializer , the entity is initialized as specified in
8.5;
otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
otherwise, the entity is default-initialized
12.6.2 Initializing bases and members [class.base.init]
If a given non-static data member has both a
brace-or-equal-initializer and a mem-initializer, the initialization
specified by the mem-initializer is performed, and the non-static data
member’s brace-or-equal-initializer is ignored. [ Example: Given
struct A {
int i = /∗ some integer expression with side effects ∗/ ;
A(int arg) : i(arg) { }
// ...
};
the A(int) constructor will simply initialize i to the value of arg,
and the side effects in i’s brace-or equal-initializer will not take
place. — end example ]
So, if there is a non-deleting constructor, the brace-or-equal-initializer is ignored, and the constructor in-member initialization prevails. Thus, for array members for which the size is omitted, the expression becomes ill-formed. §12.6.2, item 9, makes it more explicit where we it specified that the r-value initializer expression is omitted if mem-initialization is performed by the constructor.
Also, the google group dicussion Yet another inconsitent behavior in C++, further elaborates and makes it more lucid. It extends the idea in explaining that brace-or-equal-initializer is a glorified way of an in-member initialization for cases where the in-member initialization for the member does not exist. As an example
struct Foo {
int i[5] ={1,2,3,4,5};
int j;
Foo(): j(0) {};
}
is equivalent to
struct Foo {
int i[5];
int j;
Foo(): j(0), i{1,2,3,4,5} {};
}
but now we see that if the array size was omitted, the expression would be ill-formed.
But then saying that, the compiler could have supported the feature for cases when the member is not initialized by in-member constructor initialization but currently for the sake of uniformity, the standard like many other things, does not support this feature.
If the compiler was allowed to support what you described, and the size of str was deduced to 5,
Foo foo = {{"This is not a test"}};
will lead to undefined behavior.

What is the reason for not being able to deduce array size from initializer-string in member variable?

Consider the code:
struct Foo
{
const char str[] = "test";
};
int main()
{
Foo foo;
}
It fails to compile with both g++ and clang++, spitting out essentially
error: array bound cannot be deduced from an in-class initializer
I understand that this is what the standard probably says, but is there any particular good reason why? Since we have a string literal it seems that the compiler should be able to deduce the size without any problem, similarly to the case when you simply declare an out-of-class const C-like null terminated string.
The reason is that you always have the possibility to override an in-class initializer list in the constructor. So I guess that in the end, it could be very confusing.
struct Foo
{
Foo() {} // str = "test\0";
// Implementing this is easier if I can clearly see how big `str` is,
Foo() : str({'a','b', 'c', 'd'}) {} // str = "abcd0"
const char str[] = "test";
};
Notice that replacing const char with static constexpr char works perfectly, and probably it is what you want anyway.
As mentioned in the comments and as answered by #sbabbi, the answer lies in the details
12.6.2 Initializing bases and members [class.base.init]
In a non-delegating constructor, if a given non-static data member or
base class is not designated by a mem-initializer-id (including the
case where there is no mem-initializer-list because the constructor
has no ctor-initializer) and the entity is not a virtual base class of
an abstract class (10.4), then
if the entity is a non-static data member that has a brace-or-equal-initializer , the entity is initialized as specified in
8.5;
otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
otherwise, the entity is default-initialized
12.6.2 Initializing bases and members [class.base.init]
If a given non-static data member has both a
brace-or-equal-initializer and a mem-initializer, the initialization
specified by the mem-initializer is performed, and the non-static data
member’s brace-or-equal-initializer is ignored. [ Example: Given
struct A {
int i = /∗ some integer expression with side effects ∗/ ;
A(int arg) : i(arg) { }
// ...
};
the A(int) constructor will simply initialize i to the value of arg,
and the side effects in i’s brace-or equal-initializer will not take
place. — end example ]
So, if there is a non-deleting constructor, the brace-or-equal-initializer is ignored, and the constructor in-member initialization prevails. Thus, for array members for which the size is omitted, the expression becomes ill-formed. §12.6.2, item 9, makes it more explicit where we it specified that the r-value initializer expression is omitted if mem-initialization is performed by the constructor.
Also, the google group dicussion Yet another inconsitent behavior in C++, further elaborates and makes it more lucid. It extends the idea in explaining that brace-or-equal-initializer is a glorified way of an in-member initialization for cases where the in-member initialization for the member does not exist. As an example
struct Foo {
int i[5] ={1,2,3,4,5};
int j;
Foo(): j(0) {};
}
is equivalent to
struct Foo {
int i[5];
int j;
Foo(): j(0), i{1,2,3,4,5} {};
}
but now we see that if the array size was omitted, the expression would be ill-formed.
But then saying that, the compiler could have supported the feature for cases when the member is not initialized by in-member constructor initialization but currently for the sake of uniformity, the standard like many other things, does not support this feature.
If the compiler was allowed to support what you described, and the size of str was deduced to 5,
Foo foo = {{"This is not a test"}};
will lead to undefined behavior.

C++: initialization of int variables by an implicit constructor

I am learning C++ and I am a bit confused about the initialization of int variables.
This code (including the comments) is a copy/paste from Nawaz's answer in this topic Why does C++ require a user-provided default constructor to default-construct a const object?
struct POD
{
int i;
};
POD p1; //uninitialized - but don't worry we can assign some value later on!
p1.i = 10; //assign some value later on!
POD p2 = POD(); //initialized
For p2, I understand that the following is happening:
The default constructor POD() is called to create a temporary POD object. The constructor is not user-defined, so it is implicit. For built-in types like int, the implicit default constructor does nothing (no initialization). Therefore i contains some random stuff.
The copy constructor is called to create p2 using the temporary POD object (whose i is still uninitialized). Therefore the i member of p2 should not be initialized either.
However, the comment says that p2 is initialized!
Any explanation is welcome. Thanks.
For built-in types like int, the implicit default constructor does nothing (no initialization).
This is true but it is also not. Default initialization results in an unitialized object, while value initialization doesn't.
Why are these cases different?
1. C++11, 8.5/11
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an object with automatic or dynamic storage duration has indeterminate value.
If you use int i; this results in an uninitialized integer!
2. C++11, 8.5/10
An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.
If you use int i = int(); you have a value-initialized i. Now, what is value-initialized?
3. C++11, 8.5/7
To value-initialize an object of type T means:
[...] (some options where T may be class or array type)
otherwise, the object is zero-initialized.
Ok now we know that int i = int(); means having i=0.
Since your struct is POD, value-initializing it means value-initializing all of its members.
You can have a shortcut on the general behaviour
int i1, i2 = int();
std::cout << i1 << std::endl;
std::cout << i2 << std::endl;
If the memory where i1 resides, isn't zero by any luck you can have the output will probably be
somevalue
0
[As #jogojapan mentioned correctly: Reading from i1 is undefined in first place, so don't do it. You'll most likely observe what I describe here but since the standard doesn't enforce compilers to behave this way i1 may be zero, or brake the expected result in any other strange way.]
Be aware of the following:
Note: Since () is not permitted by the syntax for initializer,
X a();
is not the declaration of a value-initialized object of class X, but the declaration of a function taking no argument and returning an X.
Emphasis on standard quotes are mine.
If class type POD has no user-defined constructor (as is the case in your example), then POD() does not call default constructor. Instead the object is created without using any constructors at all. The compiler performs so called value-initialization of the temporary object. Value-initialization is a self-sufficient method of initialization, which does not necessarily use constructors. Instead, value-initialization of the entire object recursively performs value-initialization of all of its subobjects, one after another. For members of type int value-initialization means zero-initialization.
This is why POD().i is guaranteed to be zero. That zero was not placed there by any constructors. That zero was placed there by value-initialization.

Can a member struct be zero-init from the constructor initializer list without calling memset?

Let's say I have the following structure declaration (simple struct with no constructor).
struct Foo
{
int x;
int y;
int z;
char szData[DATA_SIZE];
};
Now let's say this struct is a member of a C++ class as follows:
class CFoobar
{
Foo _foo;
public:
CFoobar();
};
If I declare CFoobar's constructor as follows:
CFoobar::CFoobar()
{
printf("_foo = {%d, %d, %d}\n", _foo.x, _foo.y,_foo.z);
for (int x = 0; x < 100; x++)
printf("%d\n", _foo.szData[x]);
}
As you would expect, when CFoobar's constructor runs, garbage data gets printed out Obviously, the easy fix is to memset or ZeroMemory &_foo. It's what I've always done...
However, I did notice that if add _foo to the constructor's initialization list with no parameters as follows:
CFoobar::CFoobar()
: _foo()
{
That this appears to zero-out the member variables of _foo. At least that was the case with g++ on linux.
Now here's my question: Is this standard C++, or is this compiler specific behavior?
If it's standard behavior, can someone quote me a reference from an official source? Any "gotchas" in regards to implicit zero-init behavior with more complicated structs and classes?
Yes, this is defined behaviour according to the standard. 12.6.2 [class.base.init] / 3 : "if the expression-list of the mem-initializer is omitted, the base class or member subobject is value-initialized."
Be warned, though, if Foo wasn't a POD-type but still had no user-declared constructor (e.g. it had a std::string type) then some very popular compilers would not correctly value-initialize it.
All compilers that I know of do correctly perform value-initialization of POD members when you use () as the initializer in a constructor initializer-list.
i find it hard to read the standard, but I found it I think:
To value-initialize an object of type T means:
if T is a non-union class type without a user-declared constructor, then every non-static data member and base-
class component of T is value-initialized
Value-initialization for such a class object may be implemented by zero-initializing the object and then calling the default constructor.
Section 8.5
It's the equivalent of float foo = float();
It will zero the object, even if the value representation is not all-bits-zero. I.e. it's even better than memset().

C++ empty-paren member initialization - zeroes out memory?

I originally wrote some code like this:
class Foo
{
public:
Foo() : m_buffer()
{}
private:
char m_buffer[1024];
};
Someone who is smarter than me said that having the m_buffer() initializer would zero out the memory. My intention was to leave the memory uninitialized. I didn't have time to discuss it further, but it piqued my curiosity.
Previously, I had thought it was wise to always list each member in the initializer list.
Could someone please describe this behavior further?
1) Why does the empty-paren initializer fill in memory?
2) Does it only hold for POD datatypes? I heard that it was so, but don't have the standard handy.
Thanks
If you have a member initialized like that, it will be value-initialized. That is also true for PODs. For a struct, every member is value-initialized that way, and for an array, every element of it is value-initialized.
Value-initialization for a scalar type like pointer or integer you will have it inialized to 0 converted to the right type. So you will get null pointers or false or whatever type you have concretely.
Note that the rule changed subtly from C++98 to C++03 (what we have right now), which can have surprising effects. C++98 didn't have that value-initialization. It said default initialization happens, which for a non-POD type always meant it's default constructor invokes. But value-initialization in C++03 has special meaning if there is no user-declared constructor: Every element is value-initialized then.
Here is the difference:
struct A { int c; ~A() { } }; // non-POD, but no user declared ctor
struct B { A a; B():a(){ } } b;
Now, in C++03, you will be guaranteed that b.a.c is zero. While in C++98, b.a.c will have some indeterminated value.
Previously, I had thought it was wise
to always list each member in the
initializer list.
This is to make sure all the members are initialized.
To solve your task simply remove m_buffer from the initializer list.
template <typename T>
struct C
{
C():
buff(),
var(),
object()
{
}
T buff[128];
T var;
std::string object;
};
Whatever T type is using T() is go for default constructor. For int, chars, etc it is 0, for arrays it is {T()}. And for classes it is simply their default constructor.