An expression is a combination of function calls, operators, and operands that expresses or computes a value. This chapter describes the rules for writing and understanding expressions.
An lvalue is an expression that yields an object reference, such as a variable name, an array subscript reference, dereferencing a pointer, or calling a function that returns a reference.
An rvalue is an expression that is not an lvalue. Examples of rvalues include literals, the results of a operators, and function calls that return non-references.
Strictly speaking, a function is an lvalue, but the only uses for a function lvalue are to call the function or to takes it address. Most of the time, when a C++ programmer uses the term lvalue, he or she means an object lvalue, and this book follows the convention.
The term lvalue originates in C, where an lvalue could be used on the left side of an assignment statement, and an rvalue was an expression that could be used only on the right side of an assignment. For example,
#define rvalue 42 int lvalue; lvalue = rvalue;
In C++, these simple rules are no longer true, but the names remain because they are close to the truth. The most significant departure from C is that an lvalue might be const
, in which case it cannot be the target of an assignment.
You must have an lvalue in order to modify an object, so assignment operators require their left operand to be an lvalue. The address (&
) operator also requires an lvalue operand as do the increment (++
) and decrement (--
) operators. Other operators require rvalues.
Some other rules for lvalues are rvalues are as follows:
Example 4-1 shows several different kinds of rvalues and lvalues.
Example 4-1: Lvalues and rvalues.
class number { public: number(int i = 0) : value(i) {} operator int() const { return value; } number& operator=(const number& n); private: int value; }; number operator+(const number& x, const number& y); int main() { number a[10], b(42); number* p; a; // lvalue a[0]; // lvalue &a[0]; // rvalue *a; // lvalue p; // lvalue *p; // lvalue 10; // rvalue number(10); // rvalue a[0] + b; // rvalue b = a[0]; // lvalue }
In an arithmetic expression, binary operators require operands of the same type. The type of one operand can be converted so it matches that of the other operand. The matching type is usually the type of the result. (Relational and equality operators yield a bool
result.)
Type promotion takes place when needed, automatically. The general rule for implicit type promotion is that promotion preserve the original value to the greatest extent possible. Specifically, the rules are as follows:
long
double
, the other is converted to long
double
;double
, the other is converted to double
;float
, the other is converted to float
;char
, signed
char
, unsigned
char
, short
int
, unsigned
short
int
, and bit-fields are converted to int
if int
can represent all possible values of the original type; otherwise the values are converted to unsigned
int
. Bitfields that do not fit in unsigned
int
are not promoted.wchar_t
or enumerations are converted to int
, unsigned
int
, long
, or unsigned
long
, whichever is the first type that can hold all possible values of the source type.bool
are promoted to int: true
becomes 1
and false
becomes 0
.unsigned
long
, the other is converted to unsigned
long
.long
, and the other is unsigned
int
, the other is converted as follows:unsigned
int
fit in a long
int
, the unsigned
int
is converted to long
int
.unsigned
long
.long
, the other is converted to long
.unsigned
, the other is converted to unsigned
.int
.In addition to type promotion, several other conversions are performed automatically when needed:
const
int
lvalue is converted to an int
rvalue.Note that these conversion decisions are based on the types, not on the values, so the result type is always known at compile time.
Arithmetic, enumeration, and pointer values can be converted to bool
: null pointers and zero arithmetic values are false
, and everything else is true
. A common idiom is to test pointers as follows:
if (ptr) ... // do something with *ptr else ... // pointer is null if (! ptr) // another way to test if ptr is null
This idiom is so common that some classes have operators to convert an object to void*
. For example, basic_ios
defines operator
void*()
to return a null pointer if the iostream's failbit
is set. (See <ios> in Chapter 13 for details.)
In addition to implicit type conversions and promotions, you can explicitly cast a value to a different type. C++ offers several different ways to cast a type. The preferred way is to use the template-like syntax, but two shorter forms are also accepted:
(type) expr type ( expr ) const_cast< type >( expr ) dynamic_cast< type >( expr ) reinterpret_cast< type >( expr ) static_cast< type >( expr )
The first form is borrowed from C. It converts expr to a new value whose type is type. The second form is similar, but works only if type is a single identifier (such as a class name, enumeration name, a typedef name, or a single keyword for a fundamental type).
The last four forms are usually preferred over the first two because they require the programmer to be specific about the nature of the type cast. If you simply want to force an expression to be const
, or to remove a const
(say, prior to passing a pointer to a legacy library that is not written in C++ and does not know about const
declarations), use const_cast<>
.
If you have a pointer or reference to an object whose declared type is a base class, but you need to obtain a derived class pointer or reference, you should use dynamic_cast<>
. The dynamic cast performs a runtime check to make sure the cast is valid. A dynamic cast works only with polymorphic classes, that is, classes with at least one virtual function.
For example, suppose you have a graphics package, where all graphical classes derive from the base class, shape
. Various shapes have derived classes, such as circle
and polygon
. Suppose further that the application keeps a list of shape
pointers for a drawing. Most of the application's work is done through virtual functions, such as shape::draw
, but sometimes the application must cast a shape
pointer to, say, a polygon
pointer and perform polygon-specific work that is not exposed through a virtual function (e.g., display the number of edges). Use dynamic_cast<polygon*>(shape_ptr)
, which checks that the shape truly is a polygon
.
If you must perform a downward cast on non-polymorphic types, you should first reexamine your design. Downward casts should be rare, and typically happen only in situations where you are working with polymorphic classes. Nonetheless, you can perform a non-polymorphic downward cast with static_cast<>
. There is no runtime check, and errors result in undefined behavior.
The more common use for static_cast<>
is to force one enumeration to a different enumerated type, to force an arithmetic type to a different type, or to force a particular conversion from a class-type object that has multiple type conversion operators.
A reinterpret_cast<>
is reserved for the rare situations when you need to interpret the raw representation of one object as a different type. For example, an internal debugging package might record debug log files. The logger might convert pointers to integers using reinterpret_cast<>
and print the integers using a specific format.
If you try to perform the wrong kind of cast with one of the template-like cast operators, the compiler informs you of your mistake. For example, if you use static_cast<>
and accidentally cast away const
-ness, the compiler complains. Or if you use static_cast<>
where you should have used reinterpret_cast<>
, the compiler complains.
The short forms do not provide this extra level of error-checking because one form must do for the several different kinds of type casts. When you see a type cast, you should read it as a warning that something unusual is happening. The longer forms of type casts provide additional clues to the reader about the programmer's intent, and help the compiler enforce that intent.
Read the detailed rules for each form of type cast later in this chapter.
A constant expression is one that can be evaluated at compile time.
Most of these contexts require an integer result: array bounds, enumerator values, case
expressions, bit-field sizes, and static member initializers. Such expressions can involve only literals, enumerators, const
variables, integral or enumerated template parameters, and sizeof
expressions. Floating-point literals must be cast to integer or enumerated types. For example,
const int a = 39; enum { x = int(3.14) + a / sizeof(char) };
The address of a static lvalue object is a constant address, as is the address of a function. A string literal, being a static array of characters is a constant address expression.
A static const
data member can be initialized in the class declaration if the initializer is a constant integral or enumerated expression. The member can be used as a constant expression elsewhere in the class declaration. For example,
template<typename T, size_t size> class array { public: static const size_t SIZE = size; ... private: T data[SIZE]; };
See Chapter 7 for more information about static data members.
A constant expression whose value is the integer zero can be a null pointer constant. A null pointer constant can be converted to a null pointer value. The difference between the null pointer constant and a null pointer value is crucial.
A null pointer value has an implementation-defined bit pattern. Many implementations use all zero bits, but some do not. Thus, the null pointer constant is not representative of the bits that make up a null pointer value, but serves only as a mnemonic for the programmer, much as =
0
does for a pure virtual function (Chapter 6).
When you assign a null pointer constant to a pointer-type variable, the compiler takes care of converting the null pointer constant to a null pointer value of the appropriate type. Similarly, when comparing a pointer to a null pointer value, the compiler ensures the comparison is meaningful. In particular, a null pointer value is never equal to any other valid pointer value. A null pointer value is always equal to a null pointer value. A null pointer value, when converted to bool
, is false
.
The NULL
macro, defined in <cstdlib>
and other headers (see Chapter 13), expands to a null pointer constant, i.e., 0
. Using NULL
instead of 0
can be a helpful reminder to the reader or maintainer of a program, especially when typedefs obscure the nature of a type. For example,
Token tok1 = 0; // Is Token a pointer or an integer? Token tok2 = NULL; // Token is probably a pointer
At its most fundamental level, the execution of a C++ program is the successive evaluation of expressions, under the control of statements (Chapter 5), where some expressions can produce side effects. Any expression might have side effects: accessing a volatile object, modifying any object, calling a library function, or calling any other function that has side effects.
During the execution of a program, there are well-defined points in time, called sequence points. At a sequence point, the side effects have all been completed for expressions that have been evaluated so far, and no side effects have yet started for any expression yet to be evaluated. Between sequence points, the compiler is free to reorder expressions in any way that preserves the original semantics.
There are sequence points in the following positions:
if
statement, etc.expr1
) in each of the following builtin operators (but not for overloaded operators):expr1 && expr2
expr1 || expr2
expr1 ? expr2 : expr3
expr1 , expr2
In general, the order in which operands are evaluated is unspecified. For example, in the expression f()
/
g()
, f()
might be called first, or g()
might be called first. The difference can be significant when the functions have side effects. Example 4-2 shows a contrived situation where a program prints 2
if g()
is called first, or 1
if f()
is called first.
Example 4-2: Demonstrating order of evaluation.
#include <iostream> #include <ostream> int x = 1; int f() { x = 2; return x; } int g() { return x; } int main() { std::cout << f() / g() << '\n'; }
A simpler example follows. The increment of i
can happen before or after the assignment, so i might be 2 or 3.
int i = 1; i = i++ + 1; // value of i is unspecified
In a function call, all arguments are evaluated before the function is called. As you might expect, the order in which the arguments are evaluated is unspecified.
The logical operators (&&
and ||
) perform short-circuit evaluation. The left operand is evaluated, and if the expression result is known, the right operand is not evaluated. For example,
if (false && f()) ... // f() is never called if (true || f()) ... // f() is never called
If the logical operator is overloaded, however, it cannot perform short-circuit evaluation. Like any other function, all the arguments are evaluated before the function is called. For this reason, you should avoid overloading the &&
and ||
operators.
C++ has the usual unary operators, such as logical negation (!a
), binary operators such as addition (a+b
), and even a ternary operator (a?b:c
). Unlike many other languages, array subscripting is also an operator (a[b]
), and a function call is an n-ary operator (e.g., a(b,
c,
d)
).
Every operator has a precedence. Operators with higher precedence are grouped so they are evaluated before operators with lower precedence. (Note that precedence determines how the compiler parses the expression, not necessarily the actual order of computation.)
Some operators group left-to-right, as in x
-
y
-
z
, which is equivalent to (x
-
y)
-
z
. Other operators group right-to-left, as in x
=
y
=
z
, which is equivalent to x
=
(y
=
z)
. The order is called the operator's associativity.
When reading C++ expressions, you must be aware of the precedence and associativity of the operators. For example, *ptr++
is read as *(ptr++)
because the postfix ++
operator has higher precedence than the unary *
operator.
The following sections describe all the expressions, grouped by precedence. Table 4-1 summarizes all the expressions and their associativity.
Group | Associativity | Expression |
---|---|---|
Primary(Highest precedence) | Left-to-right | literalthis ( expr ) name:: nameclass-or-namespace :: name |
Postfix | Left-to-right | pointer [ expr ] expr ( expr , ... ) type ( expr , ... ) object. memberpointer -> membercast_keyword < type >( expr ) typeid( expr ) typeid( type ) lvalue ++ lvalue -- |
Unary | Right-to-left | ++ lvalue-- lvalue~ exprcompl expr! exprnot expr+ expr- expr* pointer& lvaluesizeof exprsizeof( type ) new-exprdelete-expr |
Cast | Right-to-left | ( type ) expr |
Member | Left-to-right | object .* exprpointer ->* expr |
Multiply | Left-to-right | expr * exprexpr / exprexpr % expr |
Add | Left-to-right | expr + exprexpr - expr |
Shift | Left-to-right | expr << exprexpr >> expr |
Relational | Left-to-right | expr < exprexpr > exprexpr <= exprexpr >= expr |
Equality | Left-to-right | expr == exprexpr != exprexpr not_eq expr |
Bitwise And | Left-to-right | expr & exprexpr bitand expr |
Bitwise Exclusive Or | Left-to-right | expr ^ exprexpr xor expr |
Bitwise Inclusive Or | Left-to-right | expr | exprexpr bitor expr |
Logical And | Left-to-right | expr && exprexpr and expr |
Logical Or | Left-to-right | expr || exprexpr or expr |
Conditional | Right-to-left | expr ? expr : expr |
Assignment | Right-to-left | expr = exprexpr op= exprthrow exprthrow |
Comma (Lowest precedence) | Left-to-right | expr , expr |
A primary expression is the basic building block for more complex expressions. It is either an expression in parentheses, a literal, or a name (possibly qualified). The various forms of primary expressions are as follows:
const
char
or const
wchar_t
) are lvalues. All other literals are rvalues. this
(
expression )
operator
symbol <
optional-template-args >
operator
type~
class-name::
identifier:: operator
symbol template
unqualified-name::
nested-name or class-name :: template
nested-name. See also Chapter 8 for information about template members.::
nested-name unqualified-name::
nested-name template
unqualified-nameIn the rest of this chapter, the syntax element name-expr refers to a qualified or unqualified name, as described in this section. In particular, a name-expr can be used to the right of .
or ->
in a postfix expression. Example 4-3 shows some primary expressions.
Example 4-3: Primary expressions.
namespace ns { int x; class cls { public: cls(int); ~cls(); }; } int x; 3.14159 // literal (2 + 3 * 4) // parenthesized expression x // unqualified identifier ns::x // qualified identifier ns::cls::cls // qualified constructor operator* // unqualified operator name
Some of the expressions in this group do not use postfix syntax, but they have the same precedence as the postfix operators, so they are lumped into the same group. The postfix expressions are as follows:
[
expr ]
*((
pointer)
+
(
expr))
. If the array index is out of bounds, the behavior is undefined. The result is an lvalue whose type is the base type of pointer. (
optional-expr-list )
void
, no value is returned. See Chapter 6 for more information about functions.(
type)
expr. If the type is a reference, the result is an lvalue; otherwise it is an rvalue..
name-exprmutable
, the result is not const
even if object is const
; otherwise, the result is const
if either object or name-expr are const
. Similarly, the result is volatile
if either object or name-expr is volatile
.&
) or call the function.obj.memfun(arg)
.->
name-expr(*(
pointer)).
name-expr.++
+
1
.bool
, the new value is always true
. The bool
-specific behavior is deprecated.--
bool
. The new value is lvalue -
1
.const_cast<
type >(
expr )
const
and volatile
qualifiers can be changed.const_cast
that removes a const
qualifier is generally a bad idea,a nd modifying the resulting object results in undefined behavior. It is sometimes necessary to cast away const
-ness, especially when passing pointers to legacy libraries. If you need to cast away const
-ness to modify a data member of a const
object, declare the member mutable
.dynamic_cast<
type >(
expr )
dynamic_cast<>
cannot cast away const
-ness. The cast works as follows:void*
, the return value is a pointer to the most derived object that expr points to.bad_cast
exception is thrown.dynamic_cast<>
.Example 4-4: Using dynamic_cast<>.
#include <iostream> #include <ostream> class base { public: virtual ~base() {} }; class derived : public base {}; class most_derived : public derived {}; class other : public base {}; int main() { base* b = new derived; dynamic_cast<most_derived*>(b); // null pointer dynamic_cast<derived&>(*b); // okay dynamic_cast<other*>(b); // null pointer derived* d = new most_derived; b = d; b = dynamic_cast<base*>(d); // okay. but dynamic_cast<> // is unnecessary }
reinterpret_cast<
type >(
expr )
reinterpret_cast<>
, no conversion functions or constructors are called. Casting to a reference yields an lvalue; otherwise it yields an rvalue. A reinterpret_cast<>
cannot cast away const
-ness. Only the following conversions are allowed:reinterpret_cast<T&>(x)
is just like *reinterpret_cast<T*>(&x)
.reinterpret_cast<>
is rare is an ordinary application. Example 4-5 shows some uses of reinterpret_cast<>
.Example 4-5: Using reinterpret_cast<>.
#include <cassert> #include <iomanip> #include <iostream> #include <ostream> int foo() { return 0; } int main() { using namespace std; float pi = 3.1415926535897; int ipi; // Print numbers in pretty hexadecimal. cout << setfill('0') << showbase << hex << internal; // Show the representation of a floating point number. assert(sizeof(int) == sizeof(float)); ipi = reinterpret_cast<int&>(pi); cout << "pi bits=" << setw(10) << ipi << '\n'; // Show the address of foo(). cout << "&foo=" << setw(10) << reinterpret_cast<int>(&foo) << '\n'; // error: cannot mix object & function pointers. reinterpret_cast<int*>(&foo); }
static_cast<
type >(
expr )
static_cast<>
cannot cast away const
-ness. The permitted conversions are as follows:(
expr)
is allowed, the result is the value of tmp, where tmp is an unnamed temporary object.void
, the result of expr is discarded.dynamic_cast<>
for safe error-checking.)long
can be cast to short
. Integers can be cast to enumerations. Enumerations can be cast to other enumerations.void*
pointer can be converted to any object pointer. Converting a pointer to void*
and back produces the original value.static_cast<>
.Example 4-6: Using static_cast<>.
#include <iostream> #include <ostream> class base {}; class derived : public base {}; class other : public base {}; enum color { red, black }; enum logical { no, yes, maybe }; int main() { base* b = new derived; static_cast<derived&>(*b); // okay static_cast<other*>(b); // undefined behavior derived* d = new derived; b = d; b = static_cast<base*>(d); // okay, but unnecessary color c = static_cast<color>(yes); int i = 65; std::cout << static_cast<char>(i); }
typeid(
expr )
const
std::type_info
(or an implementation-defined type that derives from type_info
). See <typeinfo> in Chapter 13 for information about this class.bad_typeid
is thrown.typeid(
type )
typeid(
expr )
. See Example 4-7 for uses of typeid
.Example 4-7: Using typeid.
#include <iostream> #include <ostream> #include <typeinfo> class base { public: virtual ~base() {} }; class derived : public base {}; enum color { red, black }; // The actual output is implementation-defined, but should // reflect the types shown in the comments. int main() { base* b = new derived; std::cout << typeid(*b).name() << '\n'; // derived std::cout << typeid(base).name() << '\n'; // base derived* d = new derived; std::cout << typeid(*d).name() << '\n'; // derived std::cout << typeid(derived).name() << '\n'; // derived std::cout << typeid(red).name() << '\n'; // color std::cout << typeid(color).name() << '\n'; // color }
A unary expression uses a unary prefix operator, as follows:
++
lvalue++x
is equivalent to x
+=
1
, unless x
is of type bool
, in which case it is x=true
.--
lvaluebool
), and returns the new value as an lvalue . The expression --x
is equivalent to x
-=
1
.*
pointer&
lvalue&
qualified-name&
operand to a qualified name. Even in the scope of a class, &
unqualified-name is not a pointer to member.&
operator.Example 4-8: Using the & operator.
class demo { public: int x; static int y; int get_x() { return x; } }; int demo::y = 10; int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } int main() { demo d; int demo::*p; int (demo::*func)(); int *i; int local = 42; int *ptr = &local; p = &demo::x; i = &demo::y; func = &demo::get_x; d.*p = *ptr; *i = (d.*func)(); int (*adder)(int, int); adder = &add; d.*p = adder(42, *i); return d.y; }
+
expr-
expr~
exprcompl
expr~C()
, where C
is a class name, ~C()
is interpreted as the complement operator, not the destructor of C
. If C
does not have an overloaded operator~
or an implicit conversion to an integral or enumerated type, it is an error. To force a reference to C
's destructor, use a member reference, e.g., this->~C()
, or a qualified name, e.g., C::~C()
.!
exprnot
exprbool
. The result is an rvalue of type bool
. If expr is true
, the result is false
; if expr is false
, the result is true
.sizeof
exprsizeof (
type )
sizeof(char)
==
1
. You cannot take the size of a bit field, a function, or an incomplete type. The size of a reference is the size of the referenced type.sizeof
operator always returns a value greater than zero for a class or object of class type. The size of a base class subobject within a derived class object can be zero, so the compiler is not necessarily wasting space. You can see this in Example 4-9, which shows that the size of the derived class is the same as the size of the base class.size_t
. (See <cstdlib> in Chapter 13.)Example 4-9: Using the sizeof operator.
#include <iostream> #include <ostream> class base {}; class derived : public base {}; int main() { // The values actually printed depend on the // implementation, but many common implementations // print the values shown. using namespace std; cout << sizeof(base) << '\n'; // prints 1 cout << sizeof(derived) << '\n'; // prints 1 base b[3]; cout << sizeof(b) << '\n'; // prints 3 derived d[5]; cout << sizeof(d) << '\n'; // prints 5 }
new
typenew
type (
optional-expr-list )
new (
expr-list)
typenew (
expr-list)
type (
optional-expr-list )
new
::new
new
keyword can be prefixed with the global scope operator. The actual memory allocation is performed by an allocation function. A class can provide its own allocation function by overriding operator
new
. A plain new expression looks up the allocation function first in the class (if the allocated type is a class type), and if not found, in the global scope. Use the global scope operator to look only in the global scope. (See <new> in Chapter 13 for the default allocation functions.)(
expr-list )
type-specifiers ptr-operators dimensions
*
or &
for pointers or references. The array dimensions are optional. All dimensions except the first must be a constant integral expression (enclosed in square brackets). The first dimension can be any integral expression.(
optional-expr-list )
()
), the object is initialized to a default value (that is, by calling a default constructor if one is present, or initializing to a "zero" value: arithmetic zero, false, or null pointer).operator
new
) is called with at least one argument: the number of bytes to allocate. If a placement is used, the placement arguments are passed as additional arguments to the allocation function. If the allocation function cannot fulfill the request, it typically throws bad_alloc
. If you pass std::nothrow
as the placement argument, though, the default allocation function returns a null pointer for an error, instead of throwing bad_alloc
.operator
new[]
. The requested size is the number of elements time the size of each element. Even if the array size is zero, the returned pointer is not null. The allocation function might allocate additional memory to store the size of the array (so all the array elements can be properly destroyed when the memory is freed). The allocated memory is aligned to the most restrictive boundary for any type. Thus, you can, say, allocate an array of char and use the memory to store any object. The standard containers often do this; see <memory> in Chapter 13 for algorithms that work with uninitialized memory.delete
expression). Following are some examples of new
expressions:int n = 10; // note that n is not const new int // pointer to uninitialized int new int() // pointer to int, initialized to 0 new int[n] // n uninitialized ints new (int*) // pointer to uninitialized pointer to int new (int (*[n])()) // n function pointers typedef int (*int_func)(); new int_func[n]; // n function pointers new (int*[n][4]) // n×4 array of pointers to int new complex<int>(42) // pointer to a complex object new complex<int>[5] // 5 default-initialized complex objects
delete
pointerdelete[]
pointervoid
. The delete
keyword can be prefixed with the global scope operator. The actual memory deallocation is performed by a deallocation function. A class can provide its own deallocation function by overriding operator
delete
. A plain delete expression looks up the deallocation function first in the class (if the pointer type is a pointer to a class type), and if not found, in the global scope. Use the global scope operator to look only in the global scope. (See <new> in Chapter 13 for the default deallocation functions.)delete[]
. To free a scalar, you must use plain delete
. If you make a mistake, the results are undefined. Note that the compiler cannot, in general, help you avoid mistakes because a pointer to a scalar cannot be distinguished from a pointer to an array. (Some libraries are more forgiving of this error than others.)const
object. It is safe to delete a null pointer value, in which case the deallocation function is not called.The cast expression is a holdover from C. In C++, the preferred cast syntax uses one of the explicit cast operators, which are described under Postfix Expressions earlier in this chapter. The C-style casts are still used for their brevity, though.
(
type )
exprconst_cast<
type >(
expr )
static_cast<
type >(
expr )
const_cast<
type >( static_cast<
type1 >(
expr ))
reinterpret_cast<
type >(
expr )
const_cast<
type >( reinterpret_cast<
type1 >(
expr ))
const
-cast with a static or reinterpret cast.The pointer to class member operators bind a member pointer to an object. The result can be a data member or a member function. A member function can be used only to call the function. Example 4-8, earlier in this chapter, shows some uses of pointers to members.
.*
expr ->*
exprThe multiplicative operators require arithmetic or enumeration types; the usual conversions are performed, and an rvalue is returned. If the result is too large, the behavior is undefined (except for unsigned types, where arithmetic is performed modulo the integer size; see Chapter 1 for details). Many C++ implementations ignore integer overflow.
*
expr2 /
expr2 %
expr2(a/b)*b
+
a%b
==
a
. If both operands are non-negative, the result is non-negative; otherwise, the sign of the result is implementation-defined.If the operands both have arithmetic or enumerated types, the usual conversions are performed, and the result has the expected value. If the result is too large, the behavior is undefined (except for unsigned types, where arithmetic is performed modulo the integer size; see Chapter 1 for details). Many C++ implementations ignore integer overflow.
+
expr -
exprptrdiff_t
(declared in <cstdlib>
), and is equal to the difference of the indices of the two objects.+
(-(
expr2))
.A shift expression shifts the bits of the left operand by an amount specified by the right operand. The operands must be an integral or enumerated type, which are promoted to integral types. The result type is the promoted type of the left operand.
The result is undefined if the right operand is negative or is larger than the number of bits in the left operand.
<<
expr2 >>
expr2A relational expression compares two values for relative order. Note that they have higher precedence than the equality operators, so the following two expressions are equivalent:
a < b == c > d (a < b) == (c > d)
The result is an rvalue of type bool
. The operands must have arithmetic, enumeration, or pointer type. For arithmetic and enumeration types, the usual conversions are performed, and the resulting values are compared.
When comparing pointers, the pointers must have the same type (after the usual conversions and ignoring cv-qualification), one must be a pointer to void
, or one operand must be a null pointer constant. If the pointer types are compatible, they are compared as follows (where p is the left operand and q is the right operand):
<=
q and p >=
q are true, and p <
q and p >
q are false.>
q if the member where p points is declared later than the member where q points. If the members are separated by an access specifier label, the results are unspecified.Example 4-10 shows some pointer comparisons.
Example 4-10: Comparing pointers
#include <iostream> #include <ostream> struct Demo { int x; int y; }; union U { int a; double b; char c[5]; Demo d; }; int main() { Demo demo[10]; std::cout << std::boolalpha; // Everything prints "true" std::cout << (&demo[0] < &demo[2]) << '\n'; std::cout << (&demo[0] == demo) << '\n'; std::cout << (&demo[10] > &demo[9]) << '\n'; std::cout << (&demo[0].x < &demo[0].y) << '\n'; U u; std::cout << (&u.d == static_cast<void*>(u.c)) << '\n'; std::cout << (&u.a == static_cast<void*>(&u.b)) << '\n'; }
<
expr2true
if expr1 is less than expr2. >
expr2true
if expr1 is greater than expr2. <=
expr2true
if expr1 is less than or equal to expr2. >=
expr2true
if expr1 is greater than or equal to expr2.The equality expression compares two values to see if they are equal or different. Note that they have lower precedence than the relational operators, so the following two expressions are equivalent:
a < b == c > d (a < b) == (c > d)
The result is an rvalue of type bool
. The operands must have arithmetic, enumeration, or pointer type. For arithmetic and enumeration types, the usual conversions are performed, and the resulting values are compared.
Note that comparing the results of floating point values for equality rarely has the result you want. Instead, you probably want a fuzzy comparison that allows for floating-point imprecision. See Appendix ### for more information.
When comparing pointers, the pointers must have the same type (after the usual conversions). The pointers are equal if any of the following conditions hold:
==
exprtrue
if the operands are equal. !=
expr not_eq
exprfalse
if the operands are equal.The bitwise and expression is permitted only on integral types after the usual arithmetic conversions.
&
expr bitand
exprThe bitwise exclusive or expression is permitted only on integral types after the usual arithmetic conversions.
^
expr xor
exprThe bitwise inclusive or expression is permitted only on integral types after the usual arithmetic conversions.
|
expr bitor
exprThe logical and expression implicitly converts its operands to type bool
. The result has type bool
: true
if both operands are true
, otherwise false
. The logical and operator is a short-circuit operator, so the second operand is evaluated only if the first is true
.
&&
expr and
exprThe logical or expression implicitly converts its operands to type bool
. The result has type bool
: false
if both operands are false
, otherwise true
. The logical and operator is a short-circuit operator, so the second operand is evaluated only if the first is false
.
||
expr or
exprThe conditional expression is like an if
statement in an expression.
?
true-expr :
false-exprbool
. If the value is true
, only the second operand is evaluated; if false
, only the third operand is evaluated. The result of the conditional expression is the result of the second or third operand, whichever is evaluated.T
. Otherwise, the result is an rvalue, and the type is determined as follows:throw
expression, the result type is that of the other operand.In addition to plain assignment (x
=
y
), there are several other assignment statements, each of which is a shorthand for an arithmetic operation and an assignment. The left operand must be a modifiable lvalue. The result is the resulting value of the left operand, and the result type is the type of the left operand.
=
expr +=
expr -=
exprx
op=
y
is shorthand for x
=
x
op y
, except that x
is evaluated only once. The type of x
must be an arithmetic type or a pointer. The usual conversions are performed for op, and the result is converted to the type of x
. *=
expr /=
expr %=
expr <<=
expr >>=
expr &=
expr and_eq
expr ^=
expr xor_eq
expr |=
expr or_eq
exprx
op=
y
is shorthand for x
=
x
op y
, except that x
is evaluated only once. The type of x
must be arithmetic. The usual conversions are performed for op, and the result is converted to the type of x
.throw
exprthrow
terminate()
. (See <exception> in Chapter 13 for more information about terminate()
.)catch
clause. When the handler finishes, the object is destroyed. An implementation can optimize away the extra copy and initialize the catch
object directly with the exception expr. Even if the object is never copied, the class must have a copy constructor.try
statement in Chapter 5 for a description of how exceptions are handled and for examples of throw
expressions. See also Chapter 6 for information about throw
specifications in function declarations.The comma operator evaluates its left operand, discards the result, and then evaluates the right operand. The result is the value of the right operand, and the type is the type of the right operand. If the right operand is an lvalue, the result is an lvalue; otherwise the result is an rvalue.
,
expr2sin((angle = 0.0, angle + 1.0));