Draft 2002-10-10

Chapter 5

Statements

Statements define and control a program's work. This chapter describes the syntax and rules for statements. The syntax rules apply recursively, and any place that calls for a statement, you can substitute (almost) any of the statements in this chapter, especially a compound statement, which is how you can have a conditional or loop with a multi-statement body.

Expression Statements

An expression statement is simply an expression followed by a semicolon. The expression is evaluated, and the result is discarded.

expr ;
;
The expression is optional. A statement with no expression is called a null statement. Null statements are often used for loops when you don't need anything in the loop body.
Following are several examples of expression statements.
42;          // valid but pointless
cout << 42;  // more typical
x = y * z;   // remember that assignment is an expression
;            // null statement

Declarations

A declaration can appear anywhere a statement appears, and certain statements permit additional declarations within the statement.

Declarations made in a substatement (of conditional and loop statements) are limited in scope to the substatement, even if the substatement is not a compound statement. For example, the following statement

while ( test() )
  int x = init();

is equivalent to:

while ( test() ) {
  int x = init();
}

Declaration Statements

A simple declaration can appear anywhere a statement can be used. You can declare an object, a type, or a namespace alias. You can also use a using declaration or using directive. You can declare a function, but not define a function. You cannot define a namespace or declare a template.

In traditional C programming, declarations appear at the start of each block or compound statement. In C++ (and in the C99 standard), declarations can appear anywhere a statement can, which means you can declare variables close to where they are used. Example 5-1 shows examples of how declarations can be mixed with statements.

Example 5-1: Mixing declarations and statements.

#include <cctype>
#include <iomanip>
#include <iostream>
#include <ostream>
#include <string>

// Count lines, words, and characters in the standard input.
int main()
{
  unsigned long num_lines, num_words, num_chars;

  num_lines = num_words = num_chars = 0;

  using namespace std;
  string line;
  while (getline(cin, line)) {
    ++num_lines;
    num_chars += line.length() + 1;

    bool in_word = false;
    for (size_t i = 0; char c = line[i]; ++i)
     if (isspace(static_cast<unsigned char>(c))) {
       if (in_word)
         ++num_words;
       in_word = false;
     } else if (! in_word)
       in_word = true;
    if (in_word)
      ++num_words;
  }
  cout << right <<
    setw(10) << num_lines <<
    setw(10) << num_words <<
    setw(10) << num_chars << '\n';
}

Sometimes a construct can look like an expression statement or a declaration. These ambiguities are resolved in favor of declarations. Example 5-2 shows some declarations that look like they might be expressions.

Example 5-2: Declarations that seem like expressions.

class cls {
public:
  cls();
  cls(int x);
};

int x;
int* y = &x;

int main()
{
  // The following are unambiguously expressions:
  // constructing instances of cls.
  cls(int(x));
  cls(*static_cast<int*>(y));

  // Without the redundant casts, though, they would look
  // like declarations, not expressions.
  cls(x);   // declares a variable x
  cls(*y);  // declares a pointer y
}

Condition Declarations

The for, if, switch, and while statements permit a declaration within the statement's condition. For example,

if (int x = test_this(a, b)) { cout << x; }

If the condition contains a declaration, the scope of the declared name extends to the end of the entire statement. In particular, a name declared in the condition of an if statement is visible in the else part of the statement. The name cannot be redeclared in the immediate substatement (but can be redeclared in a nested statement). The name is not visible in subsequent statements. For example,

if (int x = test_this(a, b)) {
  cout << x;
} else {
  cout << -x; // same x as above
  double x;   // error: redeclare x
  if (x < 0)
    float x;  // valid: inner block
}
cout << x; // invalid: x not in scope

A for loop permits two special declarations; both are in the same scope. See the description of the for loop for details.

The syntax for a condition declaration is as follows:

type-specifiers declarator = expression

See Chapter 3 for information about type specifiers and declarators.

Compound Statements

A compound statement is a sequence of zero or more statements in curly braces.

{ statement ... }
{ }
A compound statement can be used as a single statement anywhere a statement is called for. A compound statement delimits a declarative scope, that is, any name declared in the compound statement is not visible outside the statement, and names in the statement can hide names from outside the statement.
The most common use for compound statements are for the bodies of conditional and loop statements, but you can also stick a compound statement in the middle of a compound statement to restrict the scope of variables that are local to the compound statement. See the other examples in this chapter for uses of compound statements.

Conditionals

The two conditional statements are if statements and switch statements.

if Statements

An if statement has one of the following forms:

if ( condition ) statement
The condition is converted to a bool, and if it is true, the statement is executed. Otherwise, the statement is skipped and execution continued with the subsequent statement.
if ( condition ) statement else statement
The condition is converted to a bool, and if it is true, the first statement is executed. Otherwise, the second statement is executed. Declarations in the first statement are not visible in the second. If if statements are nested, the else part binds with the closest if statement. Example 5-3 shows nested if statements.

Example 5-3: Nesting if statements.

if (c1)
  if (c2)
    cout << "c1 and c2 are true\n";
  else
    cout << "c1 is true, but c2 is false\n";
else if (c2)
  cout << "c1 is false, but c2 is true\n";
else
  cout << "c1 and c2 are false\n";

switch Statements

A switch statement chooses one execution path from among many alternatives.

switch ( condition ) statement
The condition must have an integral or enumerated type, or be of class type where the class has a single conversion function to an integral or enumerated type. The condition is evaluated once. Its value is compared against case labels in the statement. If a case constant matches the condition, execution continues with the statement immediately after the case label. If no case matches the condition, execution continues after the default label, if one is present. If there is no default label, the switch's statement is skipped and execution continues with the subsequent statement.
A case or default label does not affect control flow. Use the break statement (described later in this chapter) to exit from a switch statement.
Example 5-4 shows sample switch statements. The syntax for case and default labels is as follows.
case constant-expression : statement
default : statement
The constant-expression must have an integral or enumerated type. The value is implicitly converted to the type of the condition. In a single switch statement, all case constants must have different values.
There can be at most one default in the switch statement; it can appear anywhere in the statement. (The default case does not have to be last, as in some languages.)
By convention, case and default labels appear at the top level of the switch's substatement. They can appear in nested statements, but that makes the statement hard to read.
A single statement can have any number of labels.

Example 5-4: Switch statements.

enum color { black, red, green, yellow, blue,
             magenta, cyan, white };
color get_pixel(unsigned r, unsigned c) { ... }

void demo()
{
  using std::cout;
  int r = ...
  int c = ...

  switch (get_pixel(r, c))
  {
    cout << "this is never executed, but it is valid\n";
    case black:
       cout << "no color\n";
       break; // Don't forget the break statements!
    case red: case green: case blue:
      cout << "primary\n";
      break; // Omitting break is a common mistake.
    default:
      cout << "mixed\n";
      switch (get_pixel(r+1, c+1))
      case white:
        cout << "  white\n"; // This case is private to the
                             // inner switch statement.
      if (r > 0)
        // If the color is yellow, the switch branches
        // directly to here. For colors other than red,
        // green, blue, and black, execution jumps to the
        // default label and gets here if r > 0.
        case yellow:
         cout << " yellow or r > 0\n";

      break; // A break after the last case is not necessary,
             // but a good idea in case you add a case later.
  }
}

Loops

Two loop statements test at the top of the loop (for and while). One (do) tests at the bottom, thereby ensuring the loop body will be executed at least once. This section describes the loop statements. See the next section for additional statements that affect or control loop execution.

The loop statements can declare variables in the scope of the loop substatement. Every time the loop iterates, it re-enters the substatement scope. That means objects that are declared in the substatement are created and destroyed every loop iteration.

while Statements

A while statement repeats a statement while a condition is true.

while ( condition ) statement
The condition is evaluated and converted to bool. If the value is true, statement is executed, and the loop repeats. If the value is false, the loop finishes and execution continues with the subsequent statement. Thus, if condition is false the first time it is evaluated, the statement is never executed.
A while loop is typically used for unbounded iteration, that is, when you don't know beforehand how many times the loop will iterate. Examples 5-5 shows one common use of the while loop.

Example 5-5: Controlling I/O with a while loop.

#include <algorithm>
#include <iostream>
#include <iterator>
#include <ostream>
#include <string>
#include <vector>

// Sort lines of text.
int main()
{
  using namespace std;
  string line;
  vector<string> data;

  while (getline(cin, line))
    data.push_back(line);
  sort(data.begin(), data.end());
  copy(data.begin(), data.end(),
    ostream_iterator<string>(cout, "\n"));
} 

for Statements

A for loop is a generalization of the traditional counted loop that appears in most programming languages.

for ( init ; condition ; iterate-expr ) statement
for ( ; ; ) statement
The init part of the for statement can be an expression or a simple declaration. The init part offers more flexibility than a condition. Where a condition can declare only one name, the init part can declare multiple names. The syntax is as follows:
specifier-list declarator-list
See Chapter 3 for more information about specifiers and declarators. As with the condition, the scope of the init part extends to the end of statement. The init and condition parts are in the same scope.
The for loop starts by executing the init part, if present. Then it evaluates the condition (just like a while loop), and if the condition is true, executes statement. Then it evaluates iterate-expr and evaluates condition again. When condition is false, the loop finishes and execution continues with the subsequent statement. Thus, the init part is evaluated exactly once. The condition is evaluated at least once. The statement is executed the same number of times that iterate-expr is evaluated, which might be zero.
A continue statement in a for loop evaluates the iterate-expr and then tests the condition. A break statement exits the loop without evaluating the iterate-expr.
The init, condition, and interate-expr parts are all optional. If the init or iterate-expr parts are omitted, nothing extra happens to initialize the loop or after the statement executes. If the condition is omitted, true is used.
The most common use of a for loop is to count a bounded loop, although its flexibility makes it useful for unbounded loops, too, as you can see in Example 5-6.

Example 5-6: Multiple uses of for loops.

// One way to implement the for_each standard algorithm.
template<typename InIter, typename Function>
Function for_each(InIter first, InIter last, Function f)
{
  for ( ; first != last; ++first)
    f(*first);
  return f;
}

// One way to implement the generate_n standard algorithm.
template<typename OutIter, typename Size, typename Generator>
void generate_n(OutIter first, Size n, Generator gen)
{
  for (Size i = 0; i < n; ++i, ++first)
    *first = gen;
}

do Statements

The do statement is like a while statement that tests at the end of the loop body.

do statement while ( expression ) ;
The statement is executed, then the expression is evaluated and converted to bool. If the value is true, the statement is repeated and the expression is checked again. When the expression is false, the loop finishes and execution continues with the subsequent statement.
Thus, statement is always executed at least once. A continue statement jumps to the end of statement and tests expression.

Control Statements

The control statements cause execution to change from its normal sequence. When execution leaves a scope, all automatic objects that were created in that scope are destroyed.

break;
A break statement can be used only in the body of a loop or switch statement. It terminates the loop or switch statement and transfers execution to the statement immediately following the loop or switch.
In a nested loop or switch, the break applies only to the innermost statement. To break out of multiple loops and switches, you must use a goto statement, or redesign the block to avoid avoid nested loops and switches (say by factoring the inner statement into a separate function). Example 5-7 shows a simple use of break.

Example 5-7: Using break in the find_if algorithm.

// One way to implement the find_if standard algorithm.
template<typename InIter, typename Predicate>
InIter find_if(InIter first, InIter last, Predicate pred)
{
  for ( ; first != last; ++first)
    if (pred(*first))
      break;

  return first;
}
continue;
A continue statement can be used only in the body of a loop. It causes the loop to skip the remainder of its body and immediately retest its condition prior to iterating again (if the condition is true). In a for loop, the iterate-expr is evaluated before testing the condition.
goto identifier ;
The goto statement transfers control to the statement in the current function that has identifier as a label. Jumping over a declaration is allowed only if the declared object is not initialized (that is, it must have POD type and not have an initializer; see Chapter 7 for information about POD types).
return ;
return expr ;
The return statement transfers execution out of a function to the caller. The first form does not return a value, so it should be used only in functions of type void, constructors, and destructors. The latter form can be used only in functions that return values and not in constructors and destructors.
The value of expr is converted to the function's return type and returned to the caller. The compiler is free to construct a temporary object and copy expr when returning. Some compilers optimize away the extra copy.
If execution reaches the last statement of a function without executing a return statement, an implicit return; is assumed. If the function has a non-void return type, the behavior is undefined.
The main function is special. If it ends without a return statement, return 0; is assumed.
identifier : statement
Any statement can have a label. The only use for a label is to be a target of a goto statement. Label identifiers must be unique within a function; the scope of a label is the function where it is declared. Label identifiers do not conflict with any other identifiers; they have a separate namespace.
A statement can have multiple labels, including case and default labels.

Exceptions

The try statement catches exceptions. See also Chapter 6 for information about try function bodies. See Chapter 4 for information about throwing exceptions.

try compound-statement handlers...
Unlike other statements, try requires a compound statement, after which is a list of one or more handlers. The compound-statement is executed, and if an exception is raised, the handlers are tested to see if any handler can handle the exception. If so, the handler's statement is executed. Otherwise, control passes to the try statement that was entered most recently. If no more try statements were entered, terminate() is called. When a handler is found, all automatic and temporary objects that were created in the try statement are destroyed. If any functions were called, those functions are terminated, and automatic and temporary objects are destroyed.
The syntax for handlers is a list of one or more catch clauses, where each catch clause has the following syntax:
catch ( type declarator ) compound-statement
catch (...) compound-statement
The first form species a type (as a list of type specifiers, see Chapter 3) and a declarator (also in Chapter 3). When an exception is thrown, the type of the exception object is compared with type, and control transfers to the first catch handler with a matching type. The compound-statement is executed, and when it finishes, execution continues with the statement that follows the last catch clause of the try statement (if no other exceptions are thrown). If a plain throw is evaluated (with no argument), the current exception object is rethrown; otherwise the exception object is destroyed when the handler finishes.
A "matching" handler is determined as follows, if T is the type of the exception object:
A common idiom is to throw an exception object and catch a reference. That avoid unnecessary copies of the exception object.
The second form of catch handler matches all exceptions. Handlers are examined in order, so catch (...) should always be last.
Example 5-8 shows some typical uses of try statements.

Example 5-8: Throwing and catching exceptions.

#include <cstdlib>
#include <fstream>
#include <iostream>
#include <numeric>
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

class bad_data : public std::out_of_range {
public:
  bad_data(int value, int min, int max)
  : std::out_of_range(make_what(value, min, max))
  {}
private:
  std::string make_what(int value, int min, int max);
};

std::string bad_data::make_what(int value, int min, int max)
{
  std::ostringstream out;
  out << "Invalid datum, " << value << ", must be in [" <<
          min << ", " << max << "]";
  return out.str();
}

// Read a set of numbers from an input stream.
// Verify that all data are within the defined boundaries.
// Throw bad_data if any datum is invalid. If an exception
// is thrown, tmp's destructor is automatically called
// (if it has a destructor).
template<typename T, typename charT, typename traits>
void getdata(std::basic_istream<charT,traits>& in,
             std::vector<T>& data, T min, T max)
{
  T tmp;
  while (in >> tmp)
  {
    if (tmp < min)
      throw bad_data(tmp, min, max);
    else if (tmp > max)
      throw bad_data(tmp, min, max);
    else
      data.push_back(tmp);
  }
}

// arbitrary precision integer
class bigint {
public:
  bigint();
  ~bigint();
  ...
};

int main(int argc, char** argv)
{
  using namespace std;
  if (argc < 2) {
    cerr << "usage: " << argv[0] << " FILE\n";
    return EXIT_FAILURE;
  }

  vector<bigint> data;
  ifstream in(argv[1]);
  if (! in) {
    perror(argv[1]);
    return 2;
  }
  try {
    getdata(in, data, 0, 100);
  } catch (const bad_data& ex) {
    cerr << argv[1] << ": " << ex.what() << '\n';
    return 3;
  }
  if (data.size() == 0)
    cout << "no data\n";
  else {
    bigint sum(accumulate(data.begin(),data.end(),bigint());
    std::cout << "avg=" << sum / data.size() << '\n';
  }
}