DekGenius.com
Team LiB   Previous Section   Next Section

2.1 Declarations and Definitions

A declaration is the all-encompassing term for anything that tells the compiler about an identifier. In order to use an identifier, the compiler must know what it means: is it a type name, a variable name, a function name, or something else? Therefore, a source file must contain a declaration (directly or in an #include file) for every name it uses.

2.1.1 Definitions

A definition defines the storage, value, body, or contents of a declaration. The difference between a declaration and a definition is that a declaration tells you an entity's name and the external view of the entity, such as an object's type or a function's parameters, and a definition provides the internal workings of the entity: the storage and initial value of an object, a function body, and so on.

In a single source file, there can be at most one definition of an entity. In an entire program, there must be exactly one definition of each function or object used in the program, except for inline functions; an inline function must be defined in every source file that uses the function, and the definitions must all be identical.

A program can have more than one definition of a given class, enumeration, inline function, or template, provided the definitions are in separate source files, and each source file has the same definition.

These rules are known as the One Definition Rules, or ODR.

Before you can use an entity (e.g., calling a function or referring to an object), the compiler needs the entity's declaration, but not necessarily its definition. You can use a class that has an incomplete declaration in some contexts, but usually you need a complete definition. (See Chapter 6 for details about incomplete classes.) The complete program needs definitions for all the declared entities, but those definitions can often reside in separate source files. The convention is to place the declarations for classes, functions, and global objects in a header file (whose name typically ends with .h or .hpp), and their definitions in a source file (whose name typically ends with .cpp, .c, or .C). Any source file that needs to use those entities must #include the header file. Templates have additional complications concerning declarations and definitions. (See Chapter 7 for details.)

In this and subsequent chapters, the description of each entity states whether the entity (type, variable, class, function, etc.) has separate definitions and declarations, states when definitions are required, and outlines any other rules pertaining to declarations and definitions.

2.1.2 Ambiguity

Some language constructs can look like a declaration or an expression. Such ambiguities are always resolved in favor of declarations. A related rule is that a declaration that is a type specifier followed by a name and empty parentheses is a declaration of a function that takes no arguments, not a declaration of an object with an empty initializer. (See Section 2.6.3 later in this chapter for more information about empty initializers.) Example 2-1 shows some examples of how declarations are interpreted.

Example 2-1. Disambiguating declarations
#include <iostream>
#include <ostream>

class T
{
public:
  T(  )    { std::cout << "T(  )\n"; }
  T(int) { std::cout << "T(int)\n"; }
};

int a, x;

int main(  )
{
  T(a);         // Variable named a of type T, not an invocation of the T(int)
                // constructor
  T b(  );      // Function named b of no arguments, not a variable named b of
                // type T
  T c(T(x));    // Declaration of a function named c, with one argument of 
                // type T
}

The last item in Example 2-1 deserves further explanation. The function parameter T(x) could be interpreted as an expression: constructing an instance of T with the argument x. Or it could be interpreted as a declaration of a function parameter of type T named x, with a redundant set of parentheses around the parameter name. According to the disambiguation rule, it must be a declaration, not an expression. This means that the entire declaration cannot be the declaration of an object named c, whose initializer is the expression T(x). Instead, it must be the declaration of a function named c, whose parameter is of type T, named x.

If your intention is to declare an object, not a function, the simplest way to do this is not to use the function-call style of type cast. Instead, use a keyword cast expression, such as static_cast<>. (See Chapter 3 for more information about type casts.) For example:

T c(static_cast<T>(x)); // Declares an object named c whose initial value is
                        // x, cast to type T

This problem can crop up when you least expect it. For example, suppose you want to construct a vector of integers by reading a series of numbers from the standard input. Your first attempt might be to use an istream_iterator:

using namespace std;
vector<int> data(istream_iterator<int>(cin), istream_iterator<int>(  ));

This declaration actually declares a function named data, which takes two parameters of type istream_iterator<int>. The first parameter is named cin, and the second is nameless. You can force the compiler to interpret the declaration as an object definition by enclosing one or more arguments in parentheses:

using namespace std;
vector<int> data((istream_iterator<int>(cin)), (istream_iterator<int>(  )));

or by using additional objects for the iterators:

std::istream_iterator<int> start(std::cin), end;
std::vector<int> data(start, end);
    Team LiB   Previous Section   Next Section