Default initialization of class's members depend on each other, undefined behaviour? - c++

In other words, is this undefined behaviour?
class Foo {
public:
int a = b;
int b = a;
};
I thought it is undefined behaviour since when you initialize a, you use the value of b, which is undefined.
But this program, compiled using x86-64 GCC 12.2, using -std=c++20 -Wall -Wextra -Wpedantic -Werror gives no error and returns the expected output:
#include <iostream>
class Foo {
public:
int a = b;
int b = a;
};
int main()
{
Foo bar{.a = 42};
std::cout << bar.a << ' ' << bar.b;
}
Godbolt link.
So is this well-defined behaviour, or is it undefined behaviour and the compiler is missing the issue?

Foo bar{.a = 42}; is initializing a with 42. The default member initializer = b in the class definition is ignored if you give an initializer for the member explicitly.
So the shown program has well-defined behavior and will output 42 42.
If you hadn't specified an explicit initializer for a, then the program would have undefined behavior for the reason you gave: It reads b outside its lifetime (or with indeterminate value). (This happens already in the initialization, not only when reading a or b later in the output statement.)
However, undefined behavior does not mean that the program must behave differently or that the compiler must notify you about it. Undefined behavior means no guarantees, so 42 42 would be a valid output as well, but so would be any other or none at all.

So is this well-defined behaviour, or is it undefined behaviour and the compiler is missing the issue?
This is well-defined behavior. By writing Foo bar{.a = 42};, you're explicitly initializing the data member a with 42. This means that the default member initializer for a will be ignored. In other words, the default member initializer b will not be used to initialize a and so the program doesn't actually use an indeterminate value.

Related

Why is the defaulted default constructor deleted for a union or union-like class?

struct A{
A(){}
};
union C{
A a;
int b = 0;
};
int main(){
C c;
}
In the above code, GCC and Clang both complain that the default constructor for union C is defined as deleted.
However, the relevant rule says that:
A defaulted default constructor for class X is defined as deleted if:
X is a union that has a variant member with a non-trivial default constructor and no variant member of X has a default member initializer,
X is a non-union class that has a variant member M with a non-trivial default constructor and no variant member of the anonymous union containing M has a default member initializer,
Notice the emphasized wording. In the example, IIUC, since the variant member b has a default member initializer, the defaulted default constructor shouldn't be defined as deleted. Why do these compilers report this code as ill-formed?
If change the definition of C to
union C{
A a{};
int b;
};
Then all compilers can compile this code. The behavior hints that the rule actually means:
X is a union that has a variant member with a non-trivial default constructor and no default member initializer is supplied for the variant member
Is this a compiler bug or the vague wording of that rule?
This was changed between C++14 and C++17, via CWG 2084, which added the language allowing an NSDMI on (any) union member to restore the defaulted default constructor.
The example accompanying CWG 2084 though is subtly different to yours:
struct S {
S();
};
union U {
S s{};
} u;
Here the NSDMI is on the non-trivial member, whereas the wording adopted for C++17 allows an NSDMI on any member to restore the defaulted default constructor. This is because, as written in that DR,
An NSDMI is basically syntactic sugar for a mem-initializer
That is, the NSDMI on int b = 0; is basically equivalent to writing a constructor with mem-initializer and empty body:
C() : b{/*but use copy-initialization*/ 0} {}
As an aside, the rule ensuring that at most one variant member of the union has an NSDMI is somewhat hidden in a subclause of class.union.anon:
4 - [...] At most one variant member of a union may have a default member initializer.
My supposition would be that since gcc and Clang already allow the above (the NSDMI on the non-trivial union member) they didn't realize that they need to change their implementation for full C++17 support.
This was discussed on the list std-discussion in 2016, with an example very similar to yours:
struct S {
S();
};
union U {
S s;
int i = 1;
} u;
The conclusion was that clang and gcc are defective in rejecting, although there was at the time a misleading note, amended as a result.
For Clang, the bug is https://bugs.llvm.org/show_bug.cgi?id=39686 which loops us back to SO at Implicitly defined constructor deleted due to variant member, N3690/N4140 vs N4659/N4727. I can't find a corresponding bug for gcc.
Note that MSVC correctly accepts, and initializes c to .b = 0, which is correct per dcl.init.aggr:
5 - [...] If the aggregate is a union and the initializer list is empty, then
5.4 - if any variant member has a default member initializer, that member is initialized from its default member initializer; [...]
Unions are a tricky thing, since all members shares the same memory space. I agree, the wording of the rule is not clear enough, since it leaves out the obvious: Defining default values for more than one member of a union is undefined behavior, or should lead to a compiler error.
Consider the following:
union U {
int a = 1;
int b = 0;
};
//...
U u; // what's the value of u.a ? what's the value of u.b ?
assert(u.a != u.b); // knowing that this assert should always fail.
This should obviously not compile.
This code does compile, because A does not have an explicit default constructor.
struct A
{
int x;
};
union U
{
A a; // this is fine, since you did not explicitly defined a
// default constructor for A, the compiler can skip
// initializing a, even though A has an implicit default
// constructor
int b = 0;
};
U u; // note that this means that u.b is valid, while u.a has an
// undefined value. There is nothing that enforces that
// any value contained by a struct A has any meaning when its
// memory content is mapped to an int.
// consider this cast: int val = *reinterpret_cast<int*>(&u.a)
This code cannot compile, because A::x does have an explicit default value, this collides with the eplicit default value for U::b (pun intended).
struct A
{
int x = 1;
};
union U
{
A a;
int b = 0;
};
// Here the definition of U is equivalent to (on gcc and clang, but not for MSVC, for reasons only known to MS):
union U
{
A a = A{1};
int b = 0;
};
// which is ill-formed.
This code will not compile either on gcc, for about the same reason, but will work on MSVC (MSVC is always a bit less strict than gcc, so it's not surprising):
struct A
{
A() {}
int x;
};
union U
{
A a;
int b = 0;
};
// Here the definition of U is equivalent to:
union U
{
A a = A{}; // gcc/clang only: you defined an explicit constructor, which MUST be called.
int b = 0;
};
// which is ill-formed.
As for where the error is reported, either at the declaration or instantiation point, this depends on the compiler, gcc and msvc report the error at the initialization point, and clang will report it when you try to instantiate the union.
Note that is is highly unadvisable to have members of a union that are not bit-compatible, or at the very least bit relatable. doing so breaks type safety, and is an open invitation for bugs into your program. Type punning is OK, but for other use cases, one should consider using std::variant<>.

Reference to the this pointer: GCC vs clang

This is a follow-up of these questions.
Consider the following code:
struct A {
private:
A* const& this_ref{this};
};
int main() {
A a{};
(void)a;
}
If compiled with the -Wextra, both GCC v6.2 and clang v3.9 show a warning.
Anyway, with the slightly modified version shown below they behave differently:
struct A {
A* const& this_ref{this};
};
int main() {
A a{};
(void)a;
}
In this case GCC doesn't give any warning, clang gives the same warning as returned in the previous example.
The warnings are almost the identical.
It follows the one from clang:
3 : warning: binding reference member 'this_ref' to a temporary value [-Wdangling-field]
Which compiler is right?
I would say that GCC is wrong in this case and I were opening an issue, but maybe it's the opposite because of an arcane corner case of the language.
The member declaration
A* const& this_ref{this};
binds a reference to a temporary that only exists during constructor execution (note: this is an rvalue expression).
I'm not sure if this is formally available in that context, but if it is then with any use of that pointer you have serious case of UB.
Re
” Which compiler is right?
… a compiler can issue as many diagnostics as it wants. It's not wrong to issue a diagnostic. So per your description, that both accept the code, then either both compilers are right (which I think is most likely), or both are wrong.
The reason for this warning is IMO this excerpt from standard (12.2.5):
A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the
constructor exits.
and since the keyword this is a prvalue expression, during this_ref initialization a temporary will be created and this_ref is bound to that temporary.
But I have doubt whether your reference is actually initialized in ctor-initializer.
If you write:
struct A {
private:
const int& rr = 1+1;
};
then you will reproduce the exact same problem with gcc, removing private will also remove this warning.
From what I know this pointer might be used in the body of the non-static member function, I have never read that it could be used as argument during default member initialization.
this is prvalue, and temporary object will be created when binding reference to a prvalue, so you're binding reference member to a temporary in default member initializer.
And binding reference member to temporary in default member initializer is ill-formed, which is stated by the standard explicitly.
$12.6.2/11 Initializing bases and members
[class.base.init]:
A temporary expression bound to a reference member from a default
member initializer is ill-formed. [ Example:
struct A {
A() = default; // OK
A(int v) : v(v) { } // OK
const int& v = 42; // OK
};
A a1; // error: ill-formed binding of temporary to reference
A a2(1); // OK, unfortunately
— end example ]
And see CWG 1696, this is applied to C++14.

Binding reference to an object before construction

Is the behavior of the following code well-defined?
struct X { int i; }; // trivial
struct Y : X { Y(){} }; // non-trivial
extern X xobj;
int& r1 = xobj.i; // #1
X xobj;
extern Y yobj;
Y& r2 = yobj; // #2
// int& r3 = yobj.i; // #3 - this is UB according to the standard
Y yobj;
This code is inspired by the example in the C++ standard, namely draft N4140 [class.cdtor]/1.
That's what that paragraph reads:
For an object with a non-trivial constructor, referring to any non-static member or base class of the object
before the constructor begins execution results in undefined behavior. For an object with a non-trivial
destructor, referring to any non-static member or base class of the object after the destructor finishes
execution results in undefined behavior.
An example follows, which shows how pointers may and may not be bound to objects.
So intuitively it seems that #1 and #2 are well-defined, while #3 invokes UB if uncommented, but, first, examples are not normative, second, there's no mention of references in the example, and third and the most important, the above paragraph doesn't imply that otherwise the behavior is well-defined. Or does it? Or maybe there's another relevant quote in the standard that I missed?
Edit: The answer may (arguably) be yes if the objects have static storage duration, but they can be also local, e.g:
struct A { A(){} };
struct B { B(A&){} };
struct C {
B b;
A a;
C() : b(a) {}
};
int main() {
C c;
}
Actually this was the initial inspiration for this question, see Circular dependency in constructor initialization list
The behavior of #2 is definitely well-defined. As mentioned by #dyp, the relevant paragraph is in [basic.life]:
Binding the glvalue yobj to a reference is fine, since its storage lasts throughout the duration of the program ([basic.stc.static]/1) and the reference is bound to a valid object - aliveness aside - which meets the requirement in ([dcl.ref]/5). Similarly, for the second example you showed, as long as no operation is performed on members of the A subobject, the above paragraph applies as well since the constructor of C is called on the allocated storage this refers to.
[...] there's no mention of references in the example [...]
You mean, except for
[...] referring to any non-static member [...]
From the passage you quote, I would say this line causes undefined behaviour:
int& r3 = yobj.i; // #3
because you are:
[...] referring to any non-static member or base class of the object before the constructor begins execution[.]
Also, for this:
and the most important, the above paragraph doesn't imply that otherwise the behavior is well-defined.
You're right, it doesn't:
Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data.

Default Initialization Versus Zero Initialization

I cannot understand the behavior of gcc 4.8.1 or Visual Studio 2015 with respect to default initialization versus value initialization.
It doesn't help that I'm trying to understand the differences between these myself and possibly running into compiler bugs?
My question is: Can someone explain this behavior? And ideally tell me what should be happening.
I have two classes:
class Foo{
int _bar;
public:
void printBar(){ cout << _bar << endl; }
};
class bar{
int ent;
public:
int getEnt(){return ent;}
};
I'm using the following code to test:
int main()
{
Foo foo;
foo.printBar();
Foo().printBar();
bar b;
cout << b.getEnt() << endl;
return 0;
}
On gcc and Visual Studio I get:
134514795
0
0
Now if I change the test code to:
int main()
{
Foo foo;
foo.printBar();
bar b;
cout << b.getEnt() << endl;
return 0;
}
gcc gives me:
0
0
And Visual Studio gives me:
50790236
51005888
Default initialisation, of classes like this without user-defined constructors, does nothing, leaving each trivial member with an indeterminate value.
Value initialisation will zero-initialise each member.
In the first case, you're printing:
the indeterminate value of a default-initialised Foo foo;
the zero value of a value-initialised Foo()
the indeterminate value of a default-initialised bar b;
The third one happens to be zero; perhaps because it reuses the storage of the temporary value-initialised Foo.
In the second case, you're printing the indeterminate values of two default-initialised objects. Coincidentally, they have zero values in one case but not the other.
Both programs have undefined behaviour, since they use uninitialised values.
The logic is quite simple:
Default initialization of a class just default initializes all members.
Default initialization of built-in types leaves member uninitialized.
Accessing an uninitialized object yields undefined behavior.
Undefined behavior can do anything it wants.
Both compilers provide "correct" results. Note that causing nasal demons to be emitted would also be correct.
Foo foo;
This default-initializes foo, and since Foo's default constructor is trivial, it effectively doesn't initialize it at all, so foo._bar can hold any value (including 0).
Foo()
This value-initializes the temporary object, which in case of trivial default constructor means zero-initialization, so Foo()._bar is equal to 0.
n3376 quotes
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. [ Note:
Objects with static or thread storage duration are zero-initialized,
see 3.6.2. — end note ]
8.5/6
To default-initialize an object of type T means: if T is a (possibly
cv-qualified) class type (Clause 9), the default constructor for T is
called (and the initialization is ill-formed if T has no accessible
default constructor);
8.5/10
An object whose initializer is an empty set of parentheses, i.e., (),
shall be value-initialized.
8.5/7
To value-initialize an object of type T means:
...
otherwise, the object is zero-initialized.
8.5/5
To zero-initialize an object or reference of type T means: if T is a
(possibly cv-qualified) non-union class type, each non-static data
member and each base-class subobject is zero-initialized and padding
is initialized to zero bits;
So, in your case, there are nor static storage duration variables, nor thread-local variables, so objects foo and b will be default-initialized, that means, that constructor will be called. Default-constructor (not user-defined) will not initialize members and in members will be arbitrary garbage and this arbitrary garbage may be 0 (thanks to Jarod42 for point this in comment).
And Foo().printBar(); should print 0, since object is zero-initialized.

C++ constructor: garbage while initialization of const reference

what is wrong with this code, why do I get wrong answer:
class X
{
private:
const int a;
const int& b;
public:
X(): a(10) , b(20)
{
// std::cout << "constructor : a " << a << std::endl;
// std::cout << "constructor : b " << b << std::endl;
}
void display()
{
std::cout << "display():a:" << a << std::endl;
std::cout << "display():b:" << b << std::endl;
}
};
int
main(void)
{
X x;
x.display();
return 0;
}
The above code will give me the result as
display():a:10
display():b:1104441332
But If I remove the commented 2 lines inside the default constructor it gives me proper result which is
constructor : a 10
constructor : b 20
display():a:10
display():b:20
please help, Thank you
You are initializing b as a reference to a temporary.
The value 20 is created and exists only for the scope of the constructor.
The behavior of the code after this is very interesting - on my machine, I get different values from the ones you posted, but the fundamental behavior is still nondeterministic.
This is because when the value to which the reference points falls out of scope, it begins to reference garbage memory instead, giving unpredictable behavior.
See Does a const reference prolong the life of a temporary?; the answer https://stackoverflow.com/a/2784304/383402 links to the relevant section of the C++ standard, specifically the below text:
A temporary bound to a reference member in a constructor’s ctor-initializer
(12.6.2) persists until the constructor exits.
This is why you always get the right value in the print within the constructor, and rarely (but possibly sometimes!) after. When the constructor exits, the reference dangles and all bets are off.
I'll let my compiler answer this one:
$ g++ -std=c++98 -Wall -Wextra -pedantic test.cpp
test.cpp: In constructor 'X::X()':
test.cpp:9:26: warning: a temporary bound to 'X::b' only persists until the constructor exits [-Wextra]
$
You should turn on the warnings on your compiler as well.
b refers to a temporary. What you have read (when printing) is an invalid location by the time it is read since the temporary 20 has technically gone out of scope.
To explain inconsistent results:
It is undefined behavior. What you see may be different if you:
change your compiler
change your compiler settings
build for another architecture
change your class' member layout
add or remove things from the memory region near the instance of x
etc.
You should always always avoid undefined behavior.
But why would the value change? Your reference likely refers to a stack address which has been rewritten (e.g. reused) by the time it's printed.
You're binding the const& to a temporary, which doesn't live beyond the call to the constructor. The C++03 standard specfically says "a temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits" (12.2/5 "Temporary objects").
So your code has undefined behavior - you might get nonsense, or something that appears to be 'working'.
FWIW, MSVC 2010 gives the following warning on that code:
C:\temp\test.cpp(12) : warning C4413: 'X::b' : reference member is initialized to a temporary that doesn't persist after the constructor exits