Previous | Table of Contents | Next |
The first problem that many C++ programmers encounter stems from the maximal munch strategy of lexical analysis: As the compiler reads characters to form a token, it takes the longest string of characters that could possibly form a token. When multiple tokens are written with no space separating them, the compiler might sometimes take several tokens to be one.
The most common example of this phenomenon is probably in the context of nested templates. For example, the C++ library includes a template class called vector. For any type T with appropriate properties, the library template gives a definition for vector<T>. The vector template meets its own requirements, which makes it possible to define vectors of vectors. Such two-level vectors are useful for stimulating two-dimensional matrices, which the library does not directly support.
One would think it possible to define a vector of vectors by writing, for example,
vector<vector<int>> matrix; // wrong
Unfortunately, this obvious usage fails. Moreover, some implementations give obscure diagnostic messages that make the problem far from obvious. In this example, the problem is that the compiler takes two immediately adjacent > characters to be a shift-right operator, rather than two closing angle brackets. To ensure that the compiler treats the two brackets as brackets, one must say
vector<vector<int> > matrix; // right
or, more symmetrically
vector< vector<int> > matrix; // also right
The confusion between >> and > > is common enough that almost every C++ programmer will encounter it. There are other, similar lexical confusions that are so obscure that a programmer can go for an entire career without seeing them:
if (x<::sqrt(y)) // wrong cout << x is too small << endl;
This example runs afoul of the fact that <: is an alternative way of spelling [ in C++, to cater to systems that do not support the [ character. Again, the solution is to use a space to separate the offending symbols:
if (x< ::sqrt(y)) // right cout << x is too small << endl;
and again, adding another space makes the result more symmetric and possibly more attractive:
if (x < ::sqrt(y)) // also right cout << x is too small << endl;
In both cases, the lesson is the same: Whenever you put two symbols next to each other, without intervening space, there is the possibility that they will combine to form a third symbol. When that happens, the result may be a diagnostic message that appears to have nothing to do with the original mistake. If you know that such mistakes can happen, they are usually easy to find.
C++ inherits its declaration syntax from C, and along with that syntax, it inherits the notion of a declarator. A declaration normally consists of a type, followed by one or more declarators. Each declarator includes the name of an object and describes the relationship between that object and the given type.
For example, if we write
int x;
the type is int and the declarator is x. This declaration says that the object x has type int. Similarly, if we write
int *p;
the declarator is *p, and the declaration says, in effect, that *p has type int. By implication, p has type pointer to int.
C++ programmers tend to write such declarations as
int* p;
to make it clearer that p has type int*. However, this style does not affect the fact that *p (or * p) is the declarator, not p.
Suppose that a C programmer wants to declare both x and p in a single declaration. The normal way to do that in C is to write
int *p, x; // *p and x have type int
which suggests that *p and x both have type int. However, if we rewrite this declaration in typical C++ style as
int* p, x; // * p and x have type int
the fact that p and x have different types becomes much less obvious. Indeed, even the C usage is marginal in this example; it might have been better to write
int (*p), x;
or
int* p; int x;
either of which would make the programmers intentions plain.
The const modifier is used to describe the type of an object whose value is promised not to change. Understanding how const works is easy in straightforward contexts, but there are potential surprises even in the C subset of C++. When C++ extends the notions of C to include constant member functions and pointers to members, one must keep a firm grasp to avoid becoming unstuck.
8.2.3.1. const in Type Specifiers
Introducing const into declarations can make it less obvious where the declarator begins. Lets start with some simple examples:
const extern int x; const int extern x; int const extern x; int extern const x; extern int const x; extern const int x;
All six of these declarations mean the same thing: The object named x is a constant integer that is defined somewhere else (extern). In these examples, the order of the keywords const, extern, and int is immaterial.
Although we can scramble the first three words of this declaration, we cant move x:
int extern x const;
is illegal.
We know that x is part of the declarator because it is the first word in the declaration that cannot be part of the name of a type. Thus, for example,
extern const int a, b, c;
has three declarators, namely a, b, and c.
To figure out where the declarators start in a declaration is usually easy: Start at the beginning and skip over as many consecutive type names and keywords as you can. The first thing you reach that is neither a type name nor a keyword is the beginning of the first declarator.
The part of the declaration before the declarators doesnt have a universally common name but is sometimes called the specifiers. The specifiers may appear in any order without changing the meaning of a declaration.
Previous | Table of Contents | Next |