Programming languages often have syntax that makes it easier to write some pieces of code but do not really allow us to do anything fundamentally new. These bits of syntax are sometimes called syntactic sugar — they makes the code “taste” better, but do not add any “nutritional value”.
This section contains two bits of syntactical sugar that make it possible to write and call functions more flexibly. You can write programs without ever using the techniques discussed here. But they can be handy, and you are likely to see them in code written by others (especially in languages other than C++), and are thus worth at least being aware of.
When you define a function, you can provide multiple versions of the function with different numbers of parameters. This is called overloading. The compiler will choose the correct version of the function based on the number and types of the arguments you provide.
int toInches(int feet) {
int totalInches = feet * 12;
return totalInches;
}
int toInches(int yards, int feet) {
int totalFeet = yards * 3 + feet;
int totalInches = totalFeet * 12;
return totalInches;
}
int toInches(int miles, int yards, int feet) {
int totalFeet = miles * 5280 + yards * 3 + feet;
int totalInches = totalFeet * 12;
return totalInches;
}
You could call this function like toInches(5), in which case the compiler would use the version that just takes feet. You could call it like toInches(2, 5) and the compiler would use the version that accepts two integers - yards and feet. Or you could call it with toInches(1, 2, 5) to use the version that takes three parameters.
Why is this trick useful? It allows someone writing a code library to make it more flexible. We have seen how the cmath library uses it to provide multiple versions of functions like pow and sqrt. There are versions that take doubles, but there are also versions that take floats and other sized data types. If overloading didn’t exist, the library would have to give each function a different name, like powDouble, powFloat, etc... That would be a mess!
If you are going to use this trick, you do have to make sure all the functions have different lists of parameters. All that matters is the types and number of parameters, not the names of the parameters. So you can’t write both void doThing(int inches) and void doThing(int cm). Those both have the same parameter list - (int). If you made the call doThing(10), there would be no way to tell which version of the function you were trying to use.
It is possible to specify default values for parameters in a function. These default values get used if the caller does not provide the appropriate parameter. Like overloading, this trick can make functions more flexible - one function can respond to multiple different kinds of call. Here is an example of the syntax:
int toInches(int feet, int yards = 0, int miles = 0) {
int totalFeet = miles * 5280 + yards * 3 + feet;
int totalInches = totalFeet * 12;
return totalInches;
}
Notice the = 0 after yards and miles. Those are the default values - they will be used if the caller leaves out the miles and or yards. To call the function, you can specify either one int (feet), two ints (feet and yards), or three ints (feet, yards, miles) as shown in this program. Note that
Although when calling the function we can skip providing values for either miles or yards and miles, we cannot skip over yards and provide a value for miles. If skipping were allowed, something like toFeet(5, 2) would be ambiguous - the compiler would not know if the 2 was for yards or miles.
To prevent this kind of confusion, default parameters have to follow these rules:
In the function definition, parameters with default values must come after ones that do not have defaults. We could NOT write the function above as int toInches(int miles = 0, int yards = 0, int feet).
When calling the function, you can stop providing values at any point you like and rely on default values for the remaining parameters. But you can not skip some parameters and then start providing values for later ones. If I want to call the function above with 5 feet and 1 miles, you would have to use toInches(5, 0, 1). Since we are specifying a miles value, we also must specify the yards.
There is some overlap in the kinds of things made easier by default values and overloaded functions. Default values tend to make it easier to provide multiple ways to call the same basic function, while overloading allows for more flexibility in how different kinds of parameters are handled.
In the case of toInches, default value trick allows the writer of the function to provide multiple ways to call the function without having to write multiple overloaded versions. Which is nice. But, default values are less flexible. You can write overloaded functions that take completely different lists of parameters like writeToLog(int num) and writeToLog(double num, int numDigit), but you could not reasonably combine those completely different functions into one version using default parameters.