How does the assignment operator work here? [duplicate] - c++

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
}

Related

Is nullptr falsy?

When used as a boolean expression or transformed into a boolean either explicitly or implicitly, is nullptr consistently false? Is this implementation defined or specified in the standard?
I wrote some code to test, but am not certain if it tests this property fully. I couldn't find an existing SO answer that talked specifically about this. cppreference doesn't mention this from what I see.
if (nullptr) {
;
} else {
std::cout << "Evaluates to false implicitly\n";
}
if (!nullptr) {
std::cout << "Evaluates to false if operated on\n";
}
if (!(bool)(nullptr)) {
std::cout << "Evaluates to false if explicitly cast to bool\n";
}
Expected and actual:
Evaluates to false implicitly
Evaluates to false if operated on
Evaluates to false if explicitly cast to bool
According to the C++ 17 Standard (5.13.7 Pointer literals)
1 The pointer literal is the keyword nullptr. It is a prvalue of type
std::nullptr_t. [ Note: std::nullptr_t is a distinct type that is
neither a pointer type nor a pointer-to-member type; rather, a prvalue
of this type is a null pointer constant and can be converted to a
null pointer value or null member pointer value. See 7.11 and 7.12. —
end note ]
And (7 Standard conversions)
4 Certain language constructs require that an expression be converted
to a Boolean value. An expression e appearing in such a context is
said to be contextually converted to bool and is well-formed if and
only if the declaration bool t(e); is well-formed, for some invented
temporary variable t (11.6).
And at last (7.14 Boolean conversions)
1 A prvalue of arithmetic, unscoped enumeration, pointer, or
pointer-to-member type can be converted to a prvalue 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. For
direct-initialization (11.6), a prvalue of type std::nullptr_t can be
converted to a prvalue of type bool; the resulting value is false.
That is you may write for example
bool b( nullptr );
but you may not write (though some compilers have a bug relative to this)
bool b = nullptr;
So nullptr can be contextually converted to an object of the type bool for example in selection statements like the if-statement.
Let's consider for example the unary operator ! as in an if statement
if ( !nullptr ) { /*...*/ }
According to the description of the operator (8.5.2.1 Unary operators)
9 The operand of the logical negation operator ! is contextually
converted to bool (Clause 7); its value is true if the converted
operand is false and false otherwise. The type of the result is bool
So nullptr in this expression is not converted to a pointer. It is directly contextually converted to bool.
The result of your code is guaranteed, [dcl.init]/17.8
Otherwise, if the initialization is direct-initialization, the source type is std​::​nullptr_­t, and the destination type is bool, the initial value of the object being initialized is false.
That means, for direct-initialization, a bool object may be initialized from nullptr, with the result value false. Then for (bool)(nullptr), nullptr is converted to bool with value false.
When using nullptr as condition of if or the operand of operator!, it's considered as contextual conversions,
the implicit conversion is performed if the declaration bool t(e); is well-formed
That means, both if (nullptr) and !nullptr, nullptr will be converted to bool with value false.
Yes, but you should avoid using this fact.
Comparing pointers to false, or to 0, is a common trope in C/C++ coding. I suggest that you avoid using it. If you want to check for nullness, use:
if (x == nullptr) { /* ... */}
rather than
if (!x) { /* ... */}
or
if (not x) { /* ... */}
The second variant adds another bit of confusion for the reader: What is x? Is it a boolean? A plain value (e.g. an integer)? A pointer? An optional? Even if x has a meaningful name, it won't help you much: if (!network_connection) ... it could still be a complex structure convertible to an integer or a boolean, it might be a boolean indicator of whether there's a connection, it could a pointer, a value or an optional. Or something else.
Also, remembering that nullptr evaluates to false is another bit of information you need to store in the back of your brain to properly decode the code you're reading. We may be used to it from the olden days or from reading other people's code - but if we weren't, it would not have been obvious that nullptr behaves like that. In a sense, it's not dissimilar for other obscure guarantees, like how the value at index 0 of an empty std::string is guaranteed to be \0. Just don't make your code rely on this stuff unless you absolutely have to.
PS : There is actually a lot less use for null pointers these days. You can force pointers to never be null if they don't need to; you can use references instead of pointers; and you can use std::optional<T> to return either a T or "no T". Perhaps you could just avoid mentioning nullptr altogether.

Variable initialization in C++: a unique method

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

Low level details of C/C++ assignment operator implementation. What does it return?

I m a total newbie to a C++ world (and C too). And don't know all its details. But one thing really bothers me.
It is constructions like :
while (a=b) {...} .As I understand this magic works because assignment operator in C and C++ returns something.
So the questions: what does it return? Is this a documented thing?Does it work the same in C and C++. Low level details about assignment operator and its implementation in both C and C++ (if there is a difference) will be very appreciated!
I hope that this question won't be closed, because I can't find a comprehensive explanation and good material on this theme from the low level point of view all the more so.
For built-in types in C++ evaluating an assignment expression produces an lvalue that is the left hand side of the assignment expression. The assignment is sequenced before the result can be used, so when the result is converted to an rvalue you get the newly assigned value:
int a, b=5;
int &c = (a=b);
assert(&c==&a);
b=10;
assert(10==(a=b));
C is almost but not exactly the same. The result of an assignment expression in C is an rvalue the same as the value newly assigned to the left hand side of the assignment.
int *c = &(a=b); // not legal in C because you can only take the address of lvalues.
Usually if the result of an assignment is used at all it's used as an rvalue (e.g., a=b=c), so this difference between C++ and C largely goes unnoticed.
The assignment operator is defined (in C) as returning the value of the variable that was assigned to - i.e. the value of the expression (a=b) is the value of a after the expression has been evaluated.
It can be defined to be something different (of the same type) for user-defined operator overloads in C++, but I suspect most would consider this to be a very unpleasant use of operator overloading.
You can use this (non-boolean) value in a while (or an if, etc.) because of type conversion - using a value in a conditional context causes it to be implicitly converted to something which makes sense in a conditional context. In C++, this is bool, and you can define your own conversion (for your own type) by overloading operator bool(). In C, anything other than 0 is true.
To understand such expressions, you have to first understand that, positive integers are considered as 'true' and 0 is considered as false.
An assignment evaluates to the left hand side of the operator = as its value. So, while(a=b) { } would mean, while(1 /*true*/) if a after being assigned to b evaluates to non-zero. Else, it is considered as while(0 /*false*/)
Similarly, with the operator (a=b)?1:0 is the value of a after being assigned to b .. if it is non-zero then the value is taken as true and the statement following ? will be executed, or the statement following : is executed.
Assignments usually evaluate to the value at the left hand side of the operator = where as, logical operators(such as ==, && etc) evaluate to 1 or 0.
Note: with C++, it will depend upon if or not, a certain operator is overloaded.. and it will also depend upon the return type of the overloaded operator.
The assignment operators in C and C++ return the value of the variable being assigned to, i.e., their left operand. In your example of a = b, the value of this entire expression is the value that is assigned to a (which is the value of b converted into the type of a).
So you can say that the assignment operator "returns" the value of its left operand.
In C++ it's a little more complicated because you can overload the = operator with an actual user-defined function, and have it return something other than the value (and type) of the left operand.

Is this undefined behaviour in C++?

I was wondering whether the access to x in the last if below here is undefined behaviour or not:
int f(int *x)
{
*x = 1;
return 1;
}
int x = 0;
if (f(&x) && x == 1) {
// something
}
It's not undefined behavior as operator && is a sequence point
It is well defined.
Reference - C++03 Standard:
Section 5: Expressions, Para 4:
except where noted [e.g. special rules for && and ||], the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is Unspecified.
While in,
Section 1.9.18
In the evaluation of the following expressions
a && b
a || b
a ? b : c
a , b
using the built-in meaning of the operators in these expressions, there is a sequence point after the evaluation of the first expression (12).
It is defined. C/C++ do lazy evaluation and it is defined that first the left expression will be calculated and checked. If it is true then the right one will be.
No, because && defines an ordering in which the lhs must be computed before the rhs.
There is a defined order also on ||, ?: and ,. There is not on other operands.
In the comparable:
int x = 0;
if (f(&x) & x == 1) {
// something
}
Then it's undefined. Here both the lhs and rhs will be computed and in either order. This non-shortcutting form of logical and is less common because the short-cutting is normally seen as at least beneficial to performance and often vital to correctness.
It is not undefined behavior. The reason depends on two facts, both are sufficient for giving defined behavior
A function call and termination is a sequence point
The '&&' operator is a sequence point
The following is defined behavior too
int f(int *x) {
*x = 1;
return 1;
}
int x = 0;
if (f(&x) & (x == 1)) {
// something
}
However, you don't know whether x == 1 evaluates to true or false, because either the first or the second operand of & can be evaluated first. That's not important for the behavior of this code to be defined, though.
It's not undefined, but it shouldn't compile either, as you're trying to assign a pointer to x (&x) to a reference.
&& will be evaluated from left to right (evaluation will stop, if the left side evaluates false).
Edit: With the change it should compile, but will still be defined (as it doesn't really matter if you use a pointer or reference).
It will pass the address of the local variable x in the caller block as a parameter to f (pointer to int). f will then set the parameter (which is a temporary variable on the stack) to address 1 (this causes no problem) and return 1. Since 1 is true, the if () will move on to evaluate x == 1 which is false, because x in the main block is still 0.
The body of the if block will not be executed.
EDIT
With your new version of the question, the body will be executed, because after f() has returned, x in the calling block is 1.

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
}