Is modifying a variable in its declaration statement well-defined? - c++

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

Related

In-class initialization from static member of the same type

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

Is GCC correct in requiring the constexpr specifier for this reference declaration?

The code below doesn't compile under GCC 5.3.0 because the declaration of r is missing a constexpr specifier.
const int i = 1;
const int& r = i;
constexpr int j = r;
I believe the rejection is correct. How do I prove it using the working draft N4527?
First, since we're using a reference, [expr.const]/(2.9) must not be violated. (2.9.1) applies, though:
an id-expression that refers to a variable or data member of
reference type unless the reference has a preceding initialization and
either — it is initialized with a constant expression
I.e. using r is fine, as long as the initializer - i - is a constant expression (this is shown below).
It's also necessary to check whether the l-t-r conversion in line 3 is legal, i.e. (2.7) must not be violated. However, (2.7.1) applies:
an lvalue-to-rvalue conversion (4.1) unless it is applied to — a
non-volatile glvalue of integral or enumeration type that refers to
a complete non-volatile const object with a preceding
initialization, initialized with a constant expression, or
…so that's fine as well, since the (g)lvalue is r, and it refers to i - which is a non-volatile const object with a constant expression initializer (1).
We postponed showing that i is actually a constant expression, and once that's out of the way, we need to show that r is a constant expression.
[expr.const]/5 pertains to that:
A constant expression is either a glvalue core constant expression
whose value refers to an entity that is a permitted result of a
constant expression (as defined below), or a prvalue core constant
expression whose value is an object where, for that object and its
subobjects:
each non-static data member of reference type refers to an entity that is a permitted result of a constant expression, and
if the object or subobject is of pointer type, it contains the address of an object with static storage duration, the address past
the end of such an object (5.7), the address of a function, or a null
pointer value.
An entity is a permitted result of a constant expression if it is an object with static storage duration that is
either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a
function.
Since i is, in the above context, a (g)lvalue, it has to be a permitted result of a constant expression - which it is, since it has static storage duration and certainly isn't a temporary. Thus i is a constant expression.
r is, however, treated as a prvalue in line 3. Since we already established that r is a core constant expression, we solely need to check the bullet points. They're clearly met, though.
Hence the code is well-formed in namespace scope. It won't be in local scope, as i wouldn't be a permitted result of a constant expression anymore. Clang gives a comprehensive error message.

How will this variable be initialized?

I have the following
char mem_pool[1024*1024*64];
int main() {
// ...
}
I'm trying to get a thorough understanding how will mem_pool be initialized. After a lot of search my conclusions are :
it's a static initialization (not as in static the keyword, but as in "run before the program does - during static initialization phase")
it will run in 2 phases : zero initialization and default initialization (the second phase won't do anytning)
it's an array of PODs so the default initialization for every element should apply, but due to the previous 2 points we won't have an array of indeterminable values (as we would with a char ar[N] in a function scope) but an array of zeroes.
Could someone help me dissambiguate what's guaranteed by the language and correct me if I'm wrong ?
I also thought of doing any of the following
char mem_pool[1024*1024*64] {};
char mem_pool[1024*1024*64] = "";
I suspect it's a better/recommended practice, but for now I need to understand my initial question.
Your understanding is correct.
The array's elements will all be zero-initialised, because the array has static storage duration:
[C++11: 3.6.2/2]: Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place. [..]
[C++11: 8.5/5]: To zero-initialize an object or reference of type T means:
if T is a scalar type (3.9), the object is set to the value 0 (zero), taken as an integral constant expression, converted to T;
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;
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.
If it did not have static storage duration, the elements would all have indeterminate values:
[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. [..]
[C++11: 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);
if T is an array type, each element is default-initialized;
otherwise, no initialization is performed.

Class that holds a reference to itself

Skimming through the standard draft (n3242) I found this sentence in Clause 9.2 (emphasis mine):
Non-static (9.4) data members shall not have incomplete types. In
particular, a class C shall not contain a non-static member of class
C, but it can contain a pointer or reference to an object of class
C.
From this I argue that is fine to define a class like this:
class A {
public:
A(A& a) : a_(a){
}
private:
A& a_;
};
Then in clause 8.3.2 I found the following:
A reference shall be initialized to refer to a valid object or
function
Question 1: Is it permitted to define an object of this type passing its name as a reference:
A a(a);
or will this trigger undefined behavior?
Question 2: If yes, what are the parts of the standard that permit the initialization of the reference from a still-to-be-constructed object?
Question 3: If no, does this mean the definition of class A is well formed but no first object can be created without triggering UB? In this case what is the rationale behind this?
"valid object" is not defined anywhere in the standard, but it is intented to mean a region of memory with appropriate size and alignment that can contain an object of the specified type. It just means to exclude references to such things as dereferenced null pointers, misaligned regions of memory, etc. An uninitialised object is valid.
There is an open issue to clear up the wording, CWG 453.
n3337 § 3.8/6
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 12.7. Otherwise, such a glvalue
refers to allocated storage (3.7.4.2), and using the properties of the
glvalue that do not depend on its value is well-defined. The program
has undefined behavior if:
— an lvalue-to-rvalue conversion (4.1) is
applied to such a glvalue,
— the glvalue is used to access a
non-static data member or call a non-static member function of the
object, or
— the glvalue is implicitly converted (4.10) to a reference
to a base class type, or
— the glvalue is used as the operand of a
static_cast (5.2.9) except when the conversion is ultimately to cv
char& or cv unsigned char&, or
— the glvalue is used as the operand of
a dynamic_cast (5.2.7) or as the operand of typeid.
So, to answer your questions:
Question 1: Is it permitted to define an object of this type passing
its name as a reference?
Yes. Using just the address seems not to violate this (at least for a variable put on stack).
A a(a);
or will this trigger undefined behavior?
No.
Question 2: If yes, what are the parts of the standard that permit the
initialization of the reference from a still-to-be-constructed object?
§ 3.8/6 (above)
The only question that remains is how this correspond to
A reference shall be initialized to refer to a valid object or
function.
The problem is in term valid object. Because § 8.3.2/4 says that
It is unspecified whether or not a reference requires storage
it seems that § 8.3.2 is problematic and should be reworded. The confusion lead to change proposed in document C++ Standard Core Language Active Issues, Revision 87 dated on 20.01.2014:
A reference shall be initialized to refer to an object or function.
Change 8.3.2 [dcl.ref] paragraph 4 as follows:
If an lvalue to which a reference is directly bound designates neither
an existing object or function of an appropriate type (8.5.3
[dcl.init.ref]), nor a region of memory of suitable size and alignment
to contain an object of the reference's type (1.8 [intro.object], 3.8
[basic.life], 3.9 [basic.types]), the behavior is undefined.
From n1905, 3.3.1.1
The point of declaration for a name is immediately after its complete
declarator (clause 8 ) and before its initializer (if any), except as
noted below.
[ Example:
int x = 12;
{ int x = x; }
Here the second x
is initialized with its own (indeterminate) value.
—end example ]
My emphasis ( correct me if I am wrong ): In your example -
A a(a);
is equivalent to -
A a = a; // Copy initialization
So, according to standard a is initialized with it's own indeterminate value. And the member is holding reference to one such indeterminate value.

is i can be different value or always be 0 in this code sample in c++

#include <iostream>
int j;
int main(void) {
int i;
std::cout<<i<<std::endl;
std::cout<<j<<std::endl;
return 0;
}
is i can be different value or always be 0? is it right for initial ? is it unexpected result for i?
Your program has undefined behavior, since it requires an lvalue-to-rvalue conversion on an object with indeterminate value (see paragraph 4.1/1 of the C++11 Standard).
In simpler terms, i does not have any well-defined value, since you are not initializing it, and trying to read its (non-)value is undefined behavior.
Per paragraph 8.5/7 of the C++11 Standard:
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);
— if T is an array type, each element is default-initialized;
— otherwise, no initialization is performed.
Also, per paragraph 8.5/12:
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. [...]
There is no predefined (default) value for i. By default, some environments may set it to zero (Visual Studio debugger used to do that a few version ago - I don't know if it still does it).
The value for i is undefined. In practice, the value will probably be taken from whatever was in the stack memory at the address that was allocated.