Variable initialization in C++: a unique method - c++

Recently I came across this piece of code. I don't know why I never saw this kind of syntax in all my "coding life".
int main()
{
int b;
int a = (b=5, b + 5);
std::cout << a << std::endl;
}
a has value of 10. What exactly is this way of initialization called? How does it work?

This statement:
int a = (b=5, b + 5);
Makes use of the comma operator. Per Paragraph 5.18/1 of the C++11 Standard:
[...] A pair of expressions separated by a comma is evaluated left-to-right; the left expression is a discarded value
expression (Clause 5).83 Every value computation and side effect associated with the left expression
is sequenced before every value computation and side effect associated with the right expression. The type
and value of the result are the type and value of the right operand; the result is of the same value category
as its right operand, and is a bit-field if its right operand is a glvalue and a bit-field. If the value of the right
operand is a temporary (12.2), the result is that temporary.
Therefore, your statement is equivalent to:
b = 5;
int a = b + 5;
Personally, I do not see a reason for using the comma operator here. Just initialize your variable the easily readable way, unless you have a good reason for doing otherwise.

operator , evaluates arguments one after another and return the last value
It may be used not only in initialization

The comma , operator allows you to separate expressions. The compount statement made by
exp1, exp2, ..., expn
evaluates to expn.
So what happens is that first b is set to 5 and then a is set to b + 5.
A side note: since , has the lowest precedence in the table of operators the semantics of
int a = b = 5, b+5;
is different from
int a = (b = 5, b+5);
because the first is parsed as (int a = b = 5), b + 5

When used in an expression the comma operator will evaluate all of its operands (left-to-right) and return the last.

The initialization is called copy initialization. If you ignore the complex expression on the right, it's the same as in:
int a = 10;
This is to be contrasted with direct initialization, which looks like this:
int a(10);
(It's possible that you were separately confused about how to evalue a comma expression. Please indicate if that's the case.)

Related

Does each expression in C++ have a non-reference type

Hi i am reading about expression in C++ and across the statement
Statement 0.0
Each expression has some non-reference type
The quoted statement is from en.cppreference.com/w/cpp/language/value_category. Check line 2 at the top of the page.
Now i took some examples to understand what this means. For example:
int i = 100; // this expression has type int
int &j = i; // this expression has type int or int&?
My confusion is that i know that j is a reference to int that is j is int& but according to the quoted statement every expression has a non-reference type will imply that int &j = i; has type int. Is this correct?
Other examples that i am getting confused about:
int a[4] = {2,4,4,9};
a[3]; // will this expression be int& type or int type?
Now in the statement a[3]; i know that a is a array lvalue and so a[3] returns a lvalue reference to the last element. But getting confused about will the quoted statement 0.0 imply that this whole expression a[3]; be a int or an int& type?
Here is another example:
b[4]; // Here assume that b is an array rvalue. So will this expression has type int&& or int?
So my question is that does something similar happen for pointers also? Meaning do we have a similar statement(0.0) for pointers also?
int x = 34;
int *l = &x; // will this expression have type int* or int?
I now that here l is a pointer to int(compound type). If there is no similar statement for pointers then what is the need for this statement for references? That is why do we strip off the reference part only?
int i = 100; // this expression has type int
int &j = i; // this expression has type int or int&?
These statements are not expressions at all. These are declararions. They do contain sub expressions 100 and i, both of which have the type int. If you used the id expression j after this declaration, the type of that expression would be int.
So my question is that does something similar happen for pointers also?
No. Pointers are non-reference types, and something similar doesn't happen to expressions with pointer types.
why do we strip off the reference part only?
This is simply how the language works. It allows us treat objects and references to objects identically.
This is part of why you dont need to (nor can you) explicitly use an indirection operator to access the referred object, unlike needing to use an indirection operator to access a pointed object.
Here is the actual language rule (from latest standard draft):
[expr.type]
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.
Expression : An expression is made up of one or more operands and yields a result when it is evaluated.
Note the wording " when it is evaluated ".
Examples of expressions are: the literal 4, or some variable n. Again note that the expression is not yet evaluated and so have no result. Also you can create more complicated expressions from an operator and one or more operands. For example 3 + 4*5 is an unevaluated-expression. An expression with two or more operators is called a compound expression.
Each expression in C++ is either a rvalue or a lvalue.
Expression Statement: Statements end with a semicolon. When we add a semicolon to an expression, it becomes an expression statement. The affect of this is that this causes the expression to be evaluated and its result discarded at the end of the statement. So for example the literal 5 is an expression but if you add a semicolon ; after it then we will have a expression statement. Also the result of this expression will be discarded at the end of the expression statement. Lets looks at another example, cout << n; is a expression statement as a whole. It consists of the following expressions:
1. expression `cout`
2. expression 'n'
And it consists of one operator << and a null statement ;
This whole statement will cause a side-effect which will be the printing of value of n on the screen.
Update:
Example 1: std::cout << n; will have a side-effect of printing the value of n on the screen but more importantly it will also have a resulting value which will be the object std::cout which is discarded at the end of the statement.
Example 2: int i(20 + 1); consists of 3 things:
type : int
identifier i
expression 20 + 1
Example 3: float p; This has no expression. This is just variable definition. Also called declaration statement.
Example 4: float k = 43.2; This has an expression at the right hand side which is 43.2, a type float and an identifier k.
Example 5: i = 43;. This is an expression statement. There are two expressions and one operator here. The result is the variable i.
Example 6: int &r = i;. This is a declaration statement since it consists of the expression i on the right hand side. Also on the left hand side we have a type(int) and a declarator(&r). Since it is not an expression statement there will be no value that will be discarded.
Example 7: int *p = &i; This is an declaration statement since it consists of the expression i on the right hand side. Also on the left hand side we have a type(int) and a declarator(*p). Since it is not an expression statement there will be no value that will be discarded.
Example 8: i = a < b ? a : b; This is an expression statement. Here the expressions are:
the left hand side variable i
the variable a on the right hand side
the variable b on the right hand side
Also there is one condition in the middle(a < b ). If the condition evaluates to true then the result of this will be the variable a and b otherwise.
I have come to the following conclusions. To begin with, note that the type of an entity is only about what you can do with this entity, for example, addition is defined for integer types, but not for all class types.
As the question states, an expression can only have a non-reference type, i.e. can be either a fundamental type, a pointer type, a function type, ..., an array type, or a class type, but not a reference type. Which means that the available options for the type of expression listed above allow us to describe everything so that we can do everything with this expression that is possible to do with it in C++. That is, even in situations where we might expect the type to be a reference type, for example,
T a;
static_cast<T&>(a); // suspicious expression
or
T a;
T& f(int &a) { return a; }
f(a); // another suspicious expression
we cannot do anything with this expressions that we could not do with an expression of the non-reference type T and of the same value category.
Note, that we can write
int a;
static_cast<int&>(a)=3;
but the program containing following lines will be ill-formed,
int b;
static_cast<int>(b)=3; // error: lvalue required as left operand of assigment.
because the ability to stand on the left side of an assignment is determined by the value category. Thus, describing an expression with a type and a value category, as opposed to describing it with a type alone, removes the need for a separate reference type, because that property is already described by the value category alone.
C++ expressions can have forms like j+0 or (j). These expressions definitely have type int.
To simplify the grammar, the name of a variable by itself is also usable as an expression. If we didn't have this rule, in the grammar we'd have a lot of variable-or-expression constructs. But it means that the expression j has the same type as the expression j+0, namely int.
I also encountered the same question and above answers don't solve it. But I find a post written by SCOTT MEYERS which discusses the question.
In a nutshell, if you take the standard literally then it is true that an expression can have reference type but may not very useful for understanding the language.

How does the assignment operator work here? [duplicate]

Consider:
if (a=5) {
/* do something */
}
How does the assignment work as a condition?
Is it based on non-zero value of l-value?
C++ — ISO/IEC 14882:2003(E)
[5.17/1] There are several assignment operators, all of which group
right-to-left. All require a modifiable lvalue as their left operand,
and the type of an assignment expression is that of its left operand.
The result of the assignment operation is the value stored in the left
operand after the assignment has taken place; the result is an lvalue.
The result of the expression a = 5 is 5.
[6.4/4] [..] The value of a condition that is an expression is the value of the
expression, implicitly converted to bool for statements other than
switch. [..]
A conversion to bool takes place.
[4.12/1] An rvalue of arithmetic, enumeration, pointer, or pointer to member
type can be converted to an rvalue of type bool. A zero value, null
pointer value, or null member pointer value is converted to false; any
other value is converted to true.
5 converts to boolean true.
[6.4.1/1] If the condition (6.4) yields true the first
substatement is executed. [..]
true is treated as an if statement success.
C — ISO/IEC 9899:1999(E)
[6.5.16/3] An assignment operator stores a value in the object
designated by the left operand. An assignment expression has the value
of the left operand after the assignment, but is not an lvalue. [..]
The result of the expression a = 5 is 5.
[6.8.4.1/2] In both forms, the first substatement is executed if the
expression compares unequal to 0. [..]
5 is treated as an if statement success.
General
Code like this is almost always a mistake; the author likely intended if (a == 5) {}. However, sometimes it is deliberate. You may see code like this:
if (x = foo()) {
cout << "I set x to the result of foo(), which is truthy";
// ... stuff
}
Every non-zero value will be considered as true.
So some people will suggest you write
5 == a
to avoid that you make mistake == by =.
if(a=x) is equivalent to if(x) in addition to a assigned with x. So if the expression x evaluates to a non-zero value, then if(x) simply becomes if(true). Otherwise, it becomes if(false).
In your case, since x = 5, that means f(a=5) is equivalent to if(true) in addition to a assigned with 5.
Yes, it is based on the zero/non-zero value which a is assigned. To some people (myself included) it is also considered bad practice to have expressions with side-effects in your code, so the mentioned code fragment would preferably be written as something like
a = 5;
...
if (a != 0) {
...
}
In more modern usage, you may sometimes see this pattern used to handle optionals:
std::optional x = ...;
if (auto v = x) {
// Block only executes if x contained a value, accessible as *v
}

In C and C++, is an expression using the comma operator like "a = b, ++a;" undefined?

Take these three snippets of C code:
1) a = b + a++
2) a = b + a; a++
3) a = b + a, a++
Everyone knows that example 1 is a Very Bad Thing, and clearly invokes undefined behavior. Example 2 has no problems. My question is regarding example 3. Does the comma operator work like a semicolon in this kind of expression? Are 2 and 3 equivalent or is 3 just as undefined as 1?
Specifically I was considering this regarding something like free(foo), foo = bar. This is basically the same problem as above. Can I be sure that foo is freed before it's reassigned, or is this a clear sequence point problem?
I am aware that both examples are largely pointless and it makes far more sense to just use a semicolon and be done with it. I'm just asking out of curiosity.
Case 3 is well defined.
First, let's look at how the expression is parsed:
a = b + a, a++
The comma operator , has the lowest precedence, followed by the assignment operator =, the addition operator + and the postincrement operator ++. So with the implicit parenthesis it is parsed as:
(a = (b + a)), (a++)
From here, section 6.5.17 of the C standard regarding the comma operator , says the following:
2 The left operand of a comma operator is evaluated as a void expression; there is a sequence point between its
evaluation and that of the right operand. Then the right
operand is evaluated; the result has its type and value
Section 5.14 p1 of the C++11 standard has similar language:
A pair of expressions separated by a comma is evaluated left-to-right;
the left expression is a discarded- value expression.
Every value computation and side effect associated with the left
expression is sequenced before every value computation and side effect
associated with the right expression. The type and value of the result
are the type and value of the right operand; the result is of the same
value category as its right operand, and is a bit-field if its right
operand is a glvalue and a bit-field.
Because of the sequence point, a = b + a is guaranteed to be fully evaluated before a++ in the expression a = b + a, a++.
Regarding free(foo), foo = bar, this also guarantees that foo is free'ed before a new value is assigned.
a = b + a, a++; is well-defined, but a = (b + a, a++); can be undefined.
First of all, the operator precedence makes the expression equivalent to (a = (b+a)), a++;, where + has the highest precedence, followed by =, followed by ,. The comma operator includes a sequence point between the evaluation of its left and right operand. So the code is, uninterestingly, completely equivalent to:
a = b + a;
a++;
Which is of course well-defined.
Had we instead written a = (b + a, a++);, then the sequence point in the comma operator wouldn't save the day. Because then the expression would have been equivalent to
(void)(b + a);
a = a++;
In C and C++14 or older, a = a++ is unsequenced , (see C11 6.5.16/3). Meaning this is undefined behavior (Per C11 6.5/2). Note that C++11 and C++14 were badly formulated and ambiguous.
In C++17 or later, the operands of the = operator are sequenced right to left and this is still well-defined.
All of this assuming no C++ operator overloading takes place. In that case, the parameters to the overloaded operator function will be evaluated, a sequence point takes place before the function is called, and what happens from there depends on the internals of that function.

C++, ternary operator operand evaluation rules

Lets say i have following code:
std::vector<T> R;
if (condition) R = generate();
...
for (int i = 0; i < N; ++i) {
const auto &r = (R.empty() ? generate() : R);
}
It appears that generate is called regardless of R.empty(). Is that standard behavior?
From Paragraph 5.16/1 of the C++ 11 Standard:
Conditional expressions group right-to-left. The first expression is contextually converted to bool (Clause 4). It is evaluated and if it is true, the result of the conditional expression is the value of the second expression, otherwise that of the third expression. Only one of the second and third expressions is evaluated. Every value computation and side effect associated with the first expression is sequenced before every value computation and side effect associated with the second or third expression.

Using assignment as a condition expression?

Consider:
if (a=5) {
/* do something */
}
How does the assignment work as a condition?
Is it based on non-zero value of l-value?
C++ — ISO/IEC 14882:2003(E)
[5.17/1] There are several assignment operators, all of which group
right-to-left. All require a modifiable lvalue as their left operand,
and the type of an assignment expression is that of its left operand.
The result of the assignment operation is the value stored in the left
operand after the assignment has taken place; the result is an lvalue.
The result of the expression a = 5 is 5.
[6.4/4] [..] The value of a condition that is an expression is the value of the
expression, implicitly converted to bool for statements other than
switch. [..]
A conversion to bool takes place.
[4.12/1] An rvalue of arithmetic, enumeration, pointer, or pointer to member
type can be converted to an rvalue of type bool. A zero value, null
pointer value, or null member pointer value is converted to false; any
other value is converted to true.
5 converts to boolean true.
[6.4.1/1] If the condition (6.4) yields true the first
substatement is executed. [..]
true is treated as an if statement success.
C — ISO/IEC 9899:1999(E)
[6.5.16/3] An assignment operator stores a value in the object
designated by the left operand. An assignment expression has the value
of the left operand after the assignment, but is not an lvalue. [..]
The result of the expression a = 5 is 5.
[6.8.4.1/2] In both forms, the first substatement is executed if the
expression compares unequal to 0. [..]
5 is treated as an if statement success.
General
Code like this is almost always a mistake; the author likely intended if (a == 5) {}. However, sometimes it is deliberate. You may see code like this:
if (x = foo()) {
cout << "I set x to the result of foo(), which is truthy";
// ... stuff
}
Every non-zero value will be considered as true.
So some people will suggest you write
5 == a
to avoid that you make mistake == by =.
if(a=x) is equivalent to if(x) in addition to a assigned with x. So if the expression x evaluates to a non-zero value, then if(x) simply becomes if(true). Otherwise, it becomes if(false).
In your case, since x = 5, that means f(a=5) is equivalent to if(true) in addition to a assigned with 5.
Yes, it is based on the zero/non-zero value which a is assigned. To some people (myself included) it is also considered bad practice to have expressions with side-effects in your code, so the mentioned code fragment would preferably be written as something like
a = 5;
...
if (a != 0) {
...
}
In more modern usage, you may sometimes see this pattern used to handle optionals:
std::optional x = ...;
if (auto v = x) {
// Block only executes if x contained a value, accessible as *v
}