Still, you might wonder, why does the compiler need a prototype? Can’t it just look further in the file and see how the functions are defined? One problem with that approach is that it is not very efficient. The compiler would have to put compiling main() on hold while searching the rest of the file. An even more serious problem is the fact that the function might not even be in the file. C++ allows you to spread a program over several files, which you can compile independently and then combine later. In such a case, the compiler might not have access to the function code when it’s compiling main(). The same is true if the function is part of a library. The only way to avoid using a function prototype is to place the function definition before its first use. That is not always possible. Also the C++ programming style is to put main() first because it generally provides the structure for the whole program.
Prototype Syntax
A function prototype is a statement, so it must have a terminating semicolon. The simplest way to get a prototype is to copy the function header from the function definition and add a semicolon. That’s what the program in Listing 7.2 does for cube():
double cube(double x); // add ; to header to get prototype
However, the function prototype does not require that you provide names for the variables; a list of types is enough. The program in Listing 7.2 prototypes cheers() by using only the argument type:
void cheers(int); // okay to drop variable names in prototype
In general, you can either include or exclude variable names in the argument lists for prototypes. The variable names in the prototype just act as placeholders, so if you do use names, they don’t have to match the names in the function definition.
C++ Versus ANSI C Prototyping
ANSI C borrowed prototyping from C++, but the two languages do have some differences. The most important is that ANSI C, to preserve compatibility with classic C, made prototyping optional, whereas C++ makes prototyping mandatory. For example, consider the following function declaration:
void say_hi();
In C++, leaving the parentheses empty is the same as using the keyword void within the parentheses. It means the function has no arguments. In ANSI C, leaving the parentheses empty means that you are declining to state what the arguments are. That is, it means you’re forgoing prototyping the argument list. The C++ equivalent for not identifying the argument list is to use an ellipsis:
void say_bye(...); // C++ abdication of responsibility
Normally this use of an ellipsis is needed only for interfacing with C functions having a variable number of arguments, such as printf().
What Prototypes Do for You
You’ve seen that prototypes help the compiler. But what do they do for you? They greatly reduce the chances of program errors. In particular, prototypes ensure the following:
• The compiler correctly handles the function return value.
• The compiler checks that you use the correct number of function arguments.
• The compiler checks that you use the correct type of arguments. If you don’t, it converts the arguments to the correct type, if possible.
We’ve already discussed how to correctly handle the return value. Let’s look now at what happens when you use the wrong number of arguments. For example, suppose you make the following call:
double z = cube();
A compiler that doesn’t use function prototyping lets this go by. When the function is called, it looks where the call to cube() should have placed a number and uses whatever value happens to be there. This is how C worked before ANSI C borrowed prototyping from C++. Because prototyping is optional for ANSI C, this is how some C programs still work. But in C++ prototyping is not optional, so you are guaranteed protection from that sort of error.
Next, suppose you provide an argument but it is the wrong type. In C, this could create weird errors. For example, if a function expects a type int value (assume that’s 16 bits) and you pass a double (assume that’s 64 bits), the function looks at just the first 16 bits of the 64 and tries to interpret them as an int value. However, C++ automatically converts the value you pass to the type specified in the prototype, provided that both are arithmetic types. For example, Listing 7.2 manages to get two type mismatches in one statement:
cheers(cube(2));