Chapter 4. Expressions

An expression is composed of one or more operands and yields a result when it is evaluated.

4.1 Fundamentals

4.1.1 Basic Concepts

Unary operators, such as address-of (&) an dereference (*), act on one operand. Binary operators, such as equality (==) and multiplication (*), act on two operands. Some symbols, such as *, are used as both a unary and a binary operator.

The operands are often converted from one type to another.

The overloaded operators refer to the definitions give an alternative meaning to an existing operator symbol.

Every expression in C++ is either an rvalue (pronounced "are-value") or an lvalue (pronounced "ell-value"). Roughly speaking, when we use an object as an rvalue, we use the object's value. When we use an object as an lvalue, we use the objects's identity. The lvalue can be used when an rvalue is required, but we cannot use an rvalue when an lvalue is required.

4.1.2 Precedence and Associativity

An expression with two or more operators is a compound expression. Precedence and associativity determine how the operands are grouped. The normal grouping can be override by the parentheses.

4.1.3 Order of Evaluation

For operators that do not specify evaluation order, it is an error for an expression to refer to and change the same object.

cout << i << " " << ++i << endl; // undefined

There are four operator that do guarantee the order in which operands are evaluated. The logical And (&&) operator guarantees that its left-hand operand is evaluated first, the right-hand operand is evaluated only if the left-hand operand is true. The only other operators that guarantee the order in which operands are evaluated are logical OR (||) operator, the conditional (?:) operator, and the comma (,).

4.2 Arithmetic Operators

4.3 Logical and Relational Operators

The logical operators take any type of operands that can be converted to bool. These operators all return values of type bool.

The overall result of the logical AND (&&) operator is true if and only if both its operands evaluate as true. The logical OR (||) operator evaluates as true if either of its operands evaluates as true.

The short-circuit evaluation, the right side of an && is evaluated if and only if the left side is true; the right side of an || is evaluated if and only if the left side is false.

The logical NOT operator (!) returns the inverse of the truth value of its operand.

4.4 Assignment Operators

The left-hand operand of an assignment operator must be a modifiable lvalue. The assignment is right associative.

It must parenthesise the assignment for the condition to work properly since the assignment has relatively low precedence.

The assignment can be used in condition, if j is nonzero, the condition will be true.

if (i = j) ...

Compound Assignment Operators

+=   -=   *=   /=   %=   // arithmetic operators
<<=  >>=  &=   ^=   |=   // bitwise operators

4.5 Increment and Decrement Operators

The increment (++) and decrement (--) operators provide a convenient notational shorthand for adding or subtracting 1 from an object.

There are two forms of these operators: prefix and postfix. The prefix form increments (or decrements) its operand and yields the changed object as its result. The postfix operators increment (or decrement) the operand but yield a copy of the original, unchanged value as its result.

The prefix version avoids unnecessary work since the postfix operator must store the original value so that it can return the unincremented value as its result.

4.6 The Member Access Operators

The dot and arrow operators provide for member access. The dot operator fetches a member from an object of class type; arrow is defined so that ptr->mem is a synonym for (*ptr).mem (dereference has a lower precedence that dot).

4.7 The Conditional Operator

The conditional operator (the ?: operator) lets us embed simple if-else logic inside an expression.

cond ? expr1 : expr2;

where cond is an expression that is used as a condition and expr1 and expr2 are expressions of the same type. This operator executes by evaluating cond. If the condition is true, then expr1 is evaluated; otherwise, expr2 is evaluated.

The conditional operator is right associative, meaning that the operands group right to left.

cout << ((grade < 60) ? "fail" : "pass"); // print pass or fail
cout << (grade < 60 ? "fail" : "pass"; // print 0 or 1
cout << grad < 60 ? "fail" : "pass"; // error: compares cout to 60

4.8 The Bitwise Operators

The bitwise operators take integral type operands that they use as a collection of bits.

The left-shift operator ( the << operator) inserts 0-valued bits on the right. The behaviour of the right-shift operator (the >> operator) depends on the type of the left-hand operand: If that operand is unsigned, then the operator inserts 0-valued bits on the left; if it is a signed type, the result is implementation defined, either copies of the sign bit or 0-valued bit are inserted on the left.

The bitwise NOT operator (the ~operator) generates a new value with the bits of its operand inverted.

For each bit position in the result of the bitwise AND operator (the & operator) the bit is 1 if both operands contain 1; otherwise, the result is 0. For the OR operator (the | operator), the bit is 1 if either or both operands contain 1; otherwise, the result is 0. For XOR operator (the ^ operator), the bit is 1 if either but not both operands contain1; otherwise, the result is 0.

4.9 The sizeof Operator

The sizeof operator returns the size, in bytes, of an expression or a type name.

4.10 Comma Operator

The comma operator takes two operands, which is evaluates from left to right.

4.11 Type Conversions

The implicit conversions among the arithmetic types are defined to preserve precision, if possible.

The compiler automatically converts operands in the following circumstances:

  • In most expressions, values of integral types smaller than int are first promoted to an appropriate larger integral type.

  • In conditions, non-bool expressions are converted to bool.

  • In initialisations, the initialiser is converted to the type of the variable; in assignments, the right-hand operands is converted to the type of the left-hand.

  • In arithmetic and relational expressions with operands of mixed types, the types are converted to a common type.

4.11.1 The Arithmetic Conversions

The arithmetic conversions define a hierarchy of type conversions in which operands to an operator are converted to the widest type.

The integral promotions convert the small integral types to a larger integral type. The types bool, char, signed char, unsigned char, short and unsigned short are promoted to int if all possible values of that type fit in an int.

Understanding the Arithmetic Conversions

bool flag;
char cval;
short sval;
unsigned short usval;
int ival;
unsigned int uival;
long lval;
unsigned long ulval;
float fval;
double dval;

3.1415L + 'a'; // 'a' promoted to int, then that int converted to long double
dval + ival; // ival converted to double
dval + fval; // fval converted to double
ival = dval; // dval converted to int
flag = dval; // if dval is 0, then flag is false, otherwise true
cval + fval; // cvalpromoted to int, then that int converted to float
sval + cval; // sval and cval promoted to int
cval + lval; // cval converted to long
ival + ulval; // ival converted to unsigned long
usval + ival; // promotion depends on the size of unsigned short and int
uival + lval; // conversion depends on the size of unsigned int and long

4.11.2 Other Implicit Conversions

Array to Pointer Conversions:

int ia[10]; // array of ten ints
int* ip = ia; // convert ia to a pointer to the first element

Pointer Conversion: A constant integral value of 0 and the literal nullptr can be converted to any pointer type; a pointer to any non-const type can be converted to void*, and a pointer to any type can be converted to a const void*.

Conversions to bool: There is an automatic conversion from arithmetic or pointer types to bool. If the pointer or arithmetic value is zero, the conversion yields false; any other value yields true.

Conversion to const: It can convert a pointer to a non-const type to a pointer to the corresponding const type, and similarly for reference.

Conversions Defined by Class Types: Class types can define conversions that the compiler will apply automatically.

4.11.3 Explicit Conversions

A named cast has the following form: cast-name<type>(expression). The cast-name may be one of static_cast, dynamic_cast, const_cast, and reinterpret_cast.

A static_cast is often useful when a larger arithmetic type is assigned to a smaller type, it is also useful to perform a conversion that the compiler will not generate automatically.

A const_cast changes only a low-level const in its operand. Only a const_cast may be used to change the constness of an expression.

A reinterpret_cast generally performs a low-level reinterpretation of the bit pattern of its operands, it is inherently machine dependent. Safely using reinterpret_cast requires completely understanding the types involved as well as the details of how the compiler implements the case.

In early version of C++, an explicit cast took one of the following two forms:

type (expr);
(type) expr;

Last updated