Modern C++ - Expressions
5. Operators and expressions
Expressions, Expression evaluation
Expressions are formed from operators and values or variables. The most simple expressions are similar to the mathematical expressions.
A + B * C
What is the meaning?
A + (B * C)
Because the precedence of multiplication is higher/stronger than the addition.
What happens, when the operators are on the same precedence level?
A * B / C * D
In FORTRAN77, it was not defined, what is the meaning of this expression:
((A * B) / C) * D
(A * B * D) / C
(A / C) * B * D
…and if A, B, C, D are INTEGERs, the final result could be different.
In modern languages, expressions are defined by precedence and associativity rules.
In some languages, like Java, associativity always left-to-right. In C++ associativity is defined by precedence rules. In most precedence groups it is left-to-right, but for unary, ternary and assignment operators they are right-to-left.
Operators in C++
Precedence | Operator | Description | Assoc |
---|---|---|---|
Scope | :: | scope qualifier | L->R |
Postfix | ++ | postfix increment | L->R |
– – | postfix decrement | ||
() | function call | ||
[] | array index | ||
. | struct/union member access | ||
-> | member access via pointer | ||
type() | functional cast | ||
type{} | functional cast (C++11) | ||
Unary | ++ | prefix increment | R->L |
– – | prefix decrement | ||
+ | unary plus | ||
– | unary minus | ||
! | logical negation | ||
~ | binary negation | ||
(type) | type conversion | ||
* | pointer indirection | ||
& | address of | ||
sizeof | size of type/object | ||
new new[] | dynamic memory allocation | ||
delete delete[] | dynamic memory deallocation | ||
Member ptr | .* ->* | pointer-to-member | L->R |
Multiplicative | * / % | multiplication, division, remainder | L->R |
Additive | + – | addition, substraction | L->R |
Shift | « » | bitwise left/right shift | L->R |
Relational | < <= > >= | relational operations | L->R |
Equality | == != | equal, not equal | L->R |
Bitwise | & | bitwise AND | L->R |
^ | bitwise XOR (exclusive OR) | L->R | |
| | bitwise OR | L->R | |
Logical | && | logical AND | L->R |
|| | logical OR | L->R | |
Ternary | ? : | conditional expression | R->L |
Throw | throw | throw excepsion | R->L |
Assignment | = | assignment | R->L |
+= –= | compound assignment | ||
*= /= %= | |||
«= »= | |||
&= |= ^= | |||
Comma | , | sequence operator | L->R |
There are other operators which are never ambigous:
const_cast static_cast dynamic_cast reinterpret_cast
typeid sizeof... noexcept alignof
There are alternative spellings for boolean operators:
|| or
&& and
! not
Evaluation of expressions
Although, expressions are defined by precedence and associativity, how the expression is evaluated is implementation defined.
What will print the following program?
1 #include <iostream>
2 int main()
3 {
4 int i = 1;
5 std::cout << "i = " << i << ", ++i = " << ++i << std::endl;
6 return 0;
7 }
Surprisingly, the program can print the following:
$ ./a.out
i = 2, ++i = 2
In fact, some compilers on some platforms can also print:
$ ./a.out
i = 1, ++i = 2
The evaluation order may not be defined for expressions.
Exceptions are those operations which are sequence points:
- shortcut logical operators ( && || )
- conditional operator ( ? : )
- comma operator
Also, when a function is called, first the operands should have evaluated, (but parameter evaluation happens in undefined order between the parameters).
Look at the following example:
1 #include <iostream>
2 int f()
3 {
4 std::cout << "f" << std::endl;
5 return 2;
6 }
7 int g()
8 {
9 std::cout << "g" << std::endl;
10 return 1;
11 }
12 int h()
13 {
14 std::cout << "h" << std::endl;
15 return 0;
16 }
17 void func(int fpar, int gpar, int hpar)
18 {
19 std::cout << "(f() == g() == h()) == " << (fpar == gpar == hpar) << std::endl;
20 }
21 int main()
22 {
23 func(f(),g(),h());
24 return 0;
25 }
$ g++ -ansi -pedantic -Wall -W op.cpp
op.cpp: In function ‘void func(int, int, int)’:
op.cpp:19:51: warning: suggest parentheses around comparison in operand of ‘==’ [-Wparentheses]
std::cout << "(f() == g() == h()) == " << (fpar == gpar == hpar) << std::endl;
^
gsd@ken:~/tmp$ ./a.out
h
g
f
(f() == g() == h()) == 1
In the example the result of the expression 1 is well-defined. This should be the result regardless of compilers and platforms. However, the order of evaluation of the expression is not defined and may be dependent of the actual platform, compiler, or even compilation flags (e.g. optimizations).
Common errors
In the following we describe a few common mistakes regarding operator precendence of C++ programs.
Wrong precedence
1 /*
2 * LIKELY BAD!
3 */
4 #include <stdio.h>
5 int main()
6 {
7 int mask = 0x01;
8 if ( mask & 0x10 == 16 )
9 {
10 printf("This is strange!\n");
11 }
12 printf("mask = %d, 0x10 = %d, mask & 0x10 = %d, mask & 0x10 == 16 = %d\n",
13 mask, 0x10, mask & 0x10, mask & 0x10 == 16);
14 return 0;
15 }
$ g++ -ansi -pedantic -Wall -W f.cpp
f.cpp: In function ‘main’:
f.cpp:6:13: warning: suggest parentheses around comparison in operand of ‘&’ [-Wparentheses]
if ( mask & 0x10 == 16 )
^
f.cpp:11:58: warning: suggest parentheses around comparison in operand of ‘&’ [-Wparentheses]
mask, 0x10, mask & 0x10, mask & 0x10 == 16);
^
$ ./a.out
This is strange!
mask = 1, 0x10 = 16, mask & 0x10 = 0, mask & 0x10 == 16 = 1
$
Bitwise operator precedence is lower than equation relation precedence. Use parentheses to express your intentions.
Correct way
1 /*
2 * OK!
3 */
4 #include <stdio.h>
5 int main()
6 {
7 int mask = 0x01;
8 if ( (mask & 0x10) == 16 )
9 {
10 printf("This is strange!\n");
11 }
12 printf("mask = %d, 0x10 = %d, (mask & 0x10) = %d, (mask & 0x10) == 16 = %d\n",
13 mask, 0x10, mask & 0x10, (mask & 0x10) == 16);
14 return 0;
15 }
$ g++ -ansi -pedantic -Wall -W f.c
gsd@Kubuntu-e1:~/ftp$ ./a.out
mask = 1, 0x10 = 16, (mask & 0x10) = 0, (mask & 0x10) == 16 = 0
Assignment vs equality check
1 /*
2 * BAD!
3 */
4 #include <stdio.h>
5 int main()
6 {
7 int i = 0;
8 if ( i = 1 )
9 {
10 printf("This is strange!\n");
11 }
12 return 0;
13 }
$ g++ -ansi -pedantic -Wall -W f.c
f.cpp: In function ‘main’:
f.cpp:6:3: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
if ( i = 1 )
^
$ ./a.out
This is strange!
The safe way
If you compare a literal with a value always put the literal on the left side!
1 #include <stdio.h>
2 int main()
3 {
4 int i = 0;
5 if ( 1 = i )
6 {
7 printf("This is strange!\n");
8 }
9 return 0;
10 }
$ gcc -ansi -pedantic -Wall -W f.c
f.cpp: In function ‘main’:
f.cpp:6:10: error: lvalue required as left operand of assignment
if ( 1 = i )
^
Missing sequence point
1 /*
2 * BAD!
3 */
4 #include <stdio.h>
5 int main()
6 {
7 int t[10];
8 int i = 0;
9 while( i < 10 )
10 {
11 t[i] = i++;
12 }
13 for ( i = 0; i < 10; ++i )
14 {
15 printf("%d ", t[i]);
16 }
17 return 0;
18 }
$ g++ -ansi -pedantic -Wall -W f.c
f.cpp: In function ‘main’:
f.cpp:9:13: warning: operation on ‘i’ may be undefined [-Wsequence-point]
t[i] = i++;
^
$ ./a.out
613478496 0 1 2 3 4 5 6 7 8
$
Do not try to be too C++-ish! Write your intentions in the most simple way. If you need events happenning is sequential order, use sequence points.
Correct way
1 /*
2 * OK
3 */
4 #include <stdio.h>
5 int main()
6 {
7 int t[10];
8 int i = 0;
9 while( i < 10 )
10 {
11 t[i] = i;
12 ++i;
13 }
14 for ( i = 0; i < 10; ++i )
15 {
16 printf("%d ", t[i]);
17 }
18 return 0;
19 }