int foo = foo; compiles.
Which part of the C++ standard allows this?
3.3.1 Point of declaration [basic.scope.pdecl]
The point of declaration for a name is immediately after its complete declarator (clause 8) and before its initializer (if any),
The behaviour is well defined if the declaration is at file scope. If you have the declaration at function scope and if you use foo later on [which would be initialized to some unspecified value in that case] the behaviour would be undefined.
This?
int main() {
int foo = foo;
}
The object foo does exist after the =, according to [basic.scope.pdecl]:
The point of declaration for a name is immediately after its complete declarator (clause 8) and before its initializer (if any).
However, the program as a whole is undefined, because you use (on the RHS) an uninitialised value:
int x = x;
Here [..] x is initialized with its own (indeterminate) value.
And:
Though "inferred and ill-specified" by the standard, an lvalue-to-rvalue conversion is performed on the RHS expression foo.
And ([conv.lval]):
An lvalue (3.10) of a non-function,
non-array type T can be converted to
an rvalue. If T is an incomplete type,
a program that necessitates this
conversion is ill-formed. If the
object to which the lvalue refers is
not an object of type T and is not an
object of a type derived from T, or if
the object is uninitialized, a program
that necessitates this conversion has
undefined behavior.
With proper warning levels, you will get told about it; however, programs invoking Undefined Behaviour are allowed to compile. They just can do anything at all when you run them.
Or, what about this?
int foo = foo;
int main() {}
Notice that foo is a "global". These are zero-initialised as a first step, according to [basic.start.init]:
Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place.
So you'll get an int foo with value 0; it's valid, at this point, as per [basic.scope.pdecl] above, and as per [stmt.decl]:
The zero-initialization (8.5) of all
local objects with static storage
duration (3.7.1) is performed before
any other initialization takes place. [..]
You then value-initialise it to foo (itself), i.e. 0.
This is well-defined... if a little cryptic.
In the interests of thoroughness, here's a third and final case:
int foo = 42;
int main() {
int foo = foo;
}
Sadly, this is the same as the first case. Since the local foo is already declared and in scope by the time the initializer is evaluated, the initializer uses the local foo and you're still stuck with the Undefined Behaviour. The global foo is not used.
Related
Consider the following program:
struct Empty { };
struct NonEmpty { int x{}; };
struct S {
static const Empty e; // declaration
static const NonEmpty n; // declaration
static const int a; // declaration
static const int b{}; // declaration and initializer
};
Empty ge; // declaration & definition
NonEmpty gn; // declaration & definition
int ga; // declaration & definition
int main() {
auto le1 = S::e; // OK
auto ln1 = S::n; // #1 error: undefined reference
auto la1 = S::a; // #2 error: undefined reference
auto lb1 = S::b; // OK
auto le2 = ::ge; // OK
auto ln2 = ::gn; // OK
auto la2 = ::ga; // OK
}
None of the static data members of S are defined, but of the two integral type static data members S::a and S::b the latter specifies a brace-or-equal-initializer at its in-class declaration, as it may, as per [class.static.data]/4. S::e and S::n may not specify brace-or-equal-initializer:s at their in-class declaration.
All these static data members of S have, like their global namespace-scope counterpart variables ge, gn and ga, static storage duration. As per [basic.start.static]/2 they all thus undergo zero initialization as part of static initialization.
The program above is, however, rejected by GCC and Clang (various language versions, various compiler versions) at #1 and #2, when trying to copy-initialize local variables from the S::n and S::a static data members.
I'm assuming the compilers are correct, but I cannot find the normative section which supports this rejection, or maybe rather, why the program accept the case with the empty class static data member S::e. That the case of S::b is accepted "makes sense", but again I can't sort this out normatively (odr-use, conv.lval, basic.life, ... ?).
Question
What normative text explains why the program above rejects reading the value of S::e and S::a as ill-formed, whilst it accepts read the values of S::e (empty) and S::b (initialized)? Say, in N4868.
[basic.def.odr]/4 governs when a variable is odr-used (requiring a definition).
S::e and S::n are not eligible for exception (4.1) because they have non-reference type. They are not eligible for exception (4.2) because they are not usable in constant expressions because they fail to be potentially-constant, and they do not meet the "non-volatile-qualified non-class type to which the lvalue-to-rvalue conversion is applied" criterion either. They are not eligible for exception (4.3) because they are not discarded either; they are bound to the reference parameters of the copy constructors.
This implies that S::e and S::n are odr-used. You only got a diagnostic for S::n, not S::e. This does not imply that your use of S::e was acceptable. The implementation is not required to diagnose a violation of the ODR. The compilers you have used have probably elided the call to the (trivial) copy constructor of Empty, and generated object files where the linker has no way to know that S::e was supposed to have been defined.
S::b falls under exception (4.2) because it is usable in constant expressions, and the lvalue-to-rvalue conversion is immediately applied (i.e., the expression referring to S::b is "converted" into the value of S::b immediately). It is not odr-used, and does not need a definition.
S::a does not fall under exception (4.2) because its initializing declaration is not reachable, which has made it not usable in constant expressions. It is odr-used, and needs a definition.
Is the following code valid, e.g. doesn't bring undefined behaviour?
struct S
{
int i = s.i;
static S s;
};
S S::s;
int main()
{
S a; // a.i = 0
S::s.i = 42;
S b; // b.i = 42
}
As far as I know all variables with static storage duration are zero initialized. Hence s.i is 0 on S::s creation, and all is good. But maybe I'm missing something.
I would argue it's well defined.
[class.static.data]/6
Static data members are initialized and destroyed exactly like
non-local variables.
[basic.start.static]/2 (emphasis mine)
A constant initializer for a variable or temporary object o is an
initializer whose full-expression is a constant expression, except
that if o is an object, such an initializer may also invoke constexpr
constructors for o and its subobjects even if those objects are of
non-literal class types. [ Note: Such a class may have a non-trivial
destructor. — end note ] Constant initialization is performed if a
variable or temporary object with static or thread storage duration is
initialized by a constant initializer for the entity. If constant
initialization is not performed, a variable with static storage
duration or thread storage duration is zero-initialized. Together,
zero-initialization and constant initialization are called static
initialization; all other initialization is dynamic initialization.
All static initialization strongly happens before ([intro.races]) any dynamic initialization. [ Note: The dynamic initialization of
non-local variables is described in [basic.start.dynamic]; that of
local static variables is described in [stmt.dcl]. — end note ]
[dcl.init]/6 (emphasis mine)
To zero-initialize an object or reference of type T means:
if T is a scalar type, the object is initialized to the value obtained by converting the integer literal 0 (zero) to T;
if T is a (possibly cv-qualified) non-union class type, each non-static data member, each non-virtual base class subobject, and, if
the object is not a base class subobject, each virtual base class
subobject is zero-initialized and padding is initialized to zero bits;
if T is a (possibly cv-qualified) union type, the object's first non-static named data member is zero-initialized and padding is
initialized to zero bits;
if T is an array type, each element is zero-initialized;
if T is a reference type, no initialization is performed.
Because int i = s.i; means s.i goes through dynamic initialization, it's guaranteed to be zero initialized beforehand. So when it'll be used to initialize itself later, it's value won't be indeterminate. A 0 is to be expected.
You are missing something. Variables with static storage duration are zeroed, and then their constructor is called.
What I can't quite tell is whether the initialization of S.i with the value of S.i is undefined behaviour (because S.i is not initialized at this point) or not (because it must be zero).
Edit: The code in Defect Report 2026 is very similar in effect to this, and is declared to be ill-formed (which means the compiler must error). My suspicion is that the intention of the committee is that the OP's code is undefined behaviour.
Edit 2: The above DR refers to constexpr values. That probably changes things enough that is irrelevant.
Having said that: if you are relying on very careful reading of the standard to make your code legal, you are relying on the compiler author to have read it as carefully. You may be right, but that doesn't help in the short-term if the compiler author has misread and implemented something else (although hopefully, they will eventually fix the bug).
For example:
#include<iostream>
using namespace std;
int main() {
int i = i=0; //no warning
cout << i << endl;
return 0;
}
Compiled in vs2015 with no warning and output 0. Is this code snippet well-defined although it seems a little weird?
However, in this online compiler (g++ prog.cc -Wall -Wextra -std=c++17) it throws a warning of:
prog.cc: In function '`int main()`':
prog.cc:8:12: warning: operation on '`i`' may be undefined [-Wsequence-point]
`int i=i=0;`
There are two possible cases, depending on whether the object's lifetime has begun. This is determined by the first rule in [basic.life]:
The lifetime of an object or reference is a runtime property of the object or reference. An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a
constructor other than a trivial default constructor. [ Note: Initialization by a trivial copy/move constructor is non-vacuous initialization. — end note ] The lifetime of an object of type T begins when:
storage with the proper alignment and size for type T is obtained, and
if the object has non-vacuous initialization, its initialization is complete, except that if the object is a union member or subobject thereof, its lifetime only begins if that union member is the initialized member in the union, or as described in ([class.union]).
Objects of class or aggregate type
std::string s = std::to_string(s.size()); // UB
In this case, the lifetime of the object doesn't start until initialization is complete, so this rule in [basic.life] is applicable:
Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see ([class.cdtor]). Otherwise, such a glvalue refers to allocated storage, and using the properties of the glvalue that do not depend on its value is well-defined. The
program has undefined behavior if:
the glvalue is used to access the object, or
the glvalue is used to call a non-static member function of the object, or
the glvalue is bound to a reference to a virtual base class, or
the glvalue is used as the operand of a dynamic_cast or as the operand of typeid.
In this example, the glvalue is used to access a non-static member, resulting in undefined behavior.
Objects of primitive type
int i = (i=0); // ok
int k = (k&0); // UB
Here, even though there is an initializer, the initialization cannot be non-vacuous because of the type. Therefore, the object's lifetime has started and the above rule does not apply.
Still, the existing value in the object is indeterminate (unless the object has static storage duration, in which case static initialization gives it a value of zero). A glvalue referring to an object with indeterminate value must never undergo lvalue-to-rvalue conversion. Thus "write-only" operations are permitted, but most1 operations reading the indeterminate value lead to undefined behavior.
The applicable rule is found in [dcl.init]:
If no initializer is specified for an object, the object is default-initialized. When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced. [ Note: Objects with static or thread storage duration are zero-initialized, see ([basic.start.static]). — end note ]
If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases:
If an indeterminate value of unsigned narrow character type or std::byte type is
produced by the evaluation of:
the second or third operand of a conditional expression,
the right operand of a comma expression,
the operand of a cast or conversion to an unsigned narrow character type or std::byte type, or
a discarded-value expression,
then the result of the operation is an indeterminate value.
If an indeterminate value of unsigned narrow character type or std::byte type is produced by the evaluation of the right operand of a simple assignment operator whose first operand is an lvalue of unsigned narrow character type or std::byte type, an indeterminate value replaces the value of the object referred to by the left operand.
If an indeterminate value of unsigned narrow character type is produced by the evaluation of the initialization expression when initializing an object of unsigned narrow character type, that object is initialized to an indeterminate value.
If an indeterminate value of unsigned narrow character type or std::byte type is produced by the evaluation of the initialization expression when initializing an object of std::byte type, that object is initialized to an indeterminate value.
1 There's a narrow exception for using character types to copy indeterminate values, making the destination value also indeterminate. The value still can't be used in other operations such as bitwise operators or arithmetic.
Is modifying variable in its declaration statement well-defined?
int i = i=0;//no warning
The statement above is initialization and is well-defined since the two i's are in the same scope.
As per basic.scope.pdecl#1
The point of declaration for a name is immediately after its complete
declarator and before its initializer (if any), except as noted below.
[ Example:
unsigned char x = 12; // Warning -Wunused-variable
{ unsigned char x = x; }
^ warning -Wuninitialized
Here the second x is initialized with its own (indeterminate) value. — end example ]
In the example, the second x is in a different block scope, its value is then indeterminate. And will have a warning:
warning: 'x' is used uninitialized in this function [-Wuninitialized]
Given the fact that local variables with automatic storage will have indeterminate value if not initialized, I believe there's an assignment taking place which has this order.
int (i = (i = 0));
Example
int x; // indeterminate
int i = i = x = 2; // x is assigned to two, then x's value is assigned to i
cout << x << " " << i; // i = 2, x = 2
Where in the C++14 Standard, does it prohibit the declaration of object a below?
class A{ int i = 1; public: A():i{1}{} };
int main()
{
constexpr A a{};
}
See live example
Note that I highlighted the word declaration, because I don't think bullet points (2.7.2) or (2.7.3), in §5.19[expr.const]p2 is an answer for the question.
[dcl.constexpr]p9:
A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.19). [...]
The error you're getting now is because your type is not a literal type. Your type is not a literal type because it does have a custom constructor, but doesn't have any constexpr constructor. The wording in the error message is rather clear about the exact requirements.
If you add a constexpr constructor (but not the default constructor), the error message changes:
class A{ int i = 1; public: A():i{1}{} constexpr A(int){} };
int main()
{
constexpr A a{};
}
Now the error message becomes
error: call to non-constexpr function ‘A::A()’
constexpr A a{};
This is the second part I bolded: it's not the initialiser that has to be a constant expression. You're right, your initialiser isn't an expression at all. It's the constructor call that must be a constant expression, and although it isn't expressed explicitly in the source code, it is an expression nonetheless. This is covered in [expr.const] rather clearly:
an invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor (12.4) [...]
to which you already refer in your question.
Well, your default constructor is not constexpr. Therefore, you cannot create a default constructed constexpr object.
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.