Functions

Let us have a look at the following code:

def middle_value(number1, number2, number3):
    if number2 <= number1 <= number3 \
           or number3 <= number1 <= number2:
        return number1
    elif number1 <= number2 <= number3 \
             or number3 <= number2 <= number1:
        return number2
    else:
        return number3

def main():
    print(middle_value(4, 5, 1))

main()
#include <iostream>

using namespace std;

int middle_value(int number1, int number2, int number3) {
    if ( (number2 <= number1 and number1 <= number3)
           or (number3 <= number1 and number1 <= number2) ) {
        return number1;
    } else if ( (number1 <= number2 and number2 <= number3)
           or (number3 <= number2 and number2 <= number1) ) {
        return number2;
    } else {
        return number3;
    }
}

int main() {
    cout << middle_value(4, 5, 1) << endl;
}
  • The most fundamental differences of the example code snippets are related to the demands that dynamic and static typing place: A C++ programmer needs to explicitly (separately) define the parameter and return value types of a function, so that the compiler may check whether the parameters used to call for the function are of the proper type, and that the return value is used in an acceptable way.

  • The actual call notation of a function is identical in both languages:

    function_name(comma_separated_parameters)
    
  • Also, the return value of a function is given identically in the two languages, with a return command.

  • The examples also demonstrate a detail of the relational operators: Python allows you to join relational operators in a chain:

    if a <= b <= c:
        ...
    

    C++ requires you to write logical expressions with the and operator:

    if ( a <= b and b <= c ) {
        ...
    }
    

Details of C++ functions

Before calling a function in C++, the compiler must be aware of its existence, the types and amounts of parameters, and the type of the function’s return value.

A function can be completely defined before you attempt to call it:

#include <iostream>

using namespace std;

double average(double number1, double number2) {
    return (number1 + number2) / 2;
}

int main() {
    cout << average(2.0, 5.0) << endl;
}

Defining a function means writing the code of a function in its entirety.

You can also declare the function before the point of calling it, and define it only after the call:

#include <iostream>

using namespace std;

double average(double number1, double number2);

int main() {
    cout << average(2.0, 5.0) << endl;
}

double average(double number1, double number2) {
    return (number1 + number2) / 2;
}

If you declare a function, you will write a minimal amount of information about it so that the compiler can check whether or not the function is called correctly. Declaring a function in C++ happens like this:

return_value_type function_name(comma_separated_definitions_of_parameter);

The above line (without semicolon) can also be called function header:

Of course, you will need to define the function at some point, because function declaration does not include the body of a function, i.e. the commands you want to perform when calling that function.

In C++, if you need to define a function that does not return any value (it is often called subroutine or subprogram), you can do it by using a special data type called void:

#include <iostream>

using namespace std;

void print_multiplication_table(int number) {
   if ( number <= 0 ) {
      cout << "Error: only positive numbers are valid!" << endl;
      return;
   }
   int multiplier = 1;
   while ( multiplier <= 9 ) {
      cout << multiplier * number << endl;
      ++multiplier;
   }
}

int main() {
   print_multiplication_table(7);
}

A subroutine does not have to have a return command at all. Instead, the return happens automatically when the execution reaches the end of the function’s body.

The amount and types of parameters

Python’s dynamic typing means that a single function may basically use parameters of different types, and act in a different way depending on the parameter type. It goes something like this:

def funcion(param):
    if type(param) is int:
        # What if the parameter has the type int?
    elif type(param) is float:
        # What if the parameter has the type float?
    else:
        # What to do otherwise?

Because C++ uses static typing, an arrangement like the one above will not work, since the compiler must already know the parameter types during compilation.

Then again, there are several C++ mechanisms you can use to create generic/polymorphic functions like the one above. Overloading is the easiest of these mechanisms to understand. It means the programmer may define several functions with the same name, as long as there are differences in their parameter types and/or amounts. Once an overloaded function is called, the compiler is able to use the given parameters at the calling point to decide which version of the function it should call:

#include <iostream>

using namespace std;

double square_of_the_sum(double a, double b) {
    return (a + b) * (a + b);
}

int square_of_the_sum(int a, int b) {
    return (a + b) * (a + b);
}

int main() {
    // Calling the first version (since parameters are double)
    cout << square_of_the_sum(1.2, 3.4) << endl;

    // Calling the second version (since parameters are int)
    cout << square_of_the_sum(3, 4) << endl;
}

As said above, overloading is possible such that functions with the same name have different amount of parameters. Especially if the parameters of the functions are subsets of each other, an alternative for overloading is to use default value parameters. Default value parameters must be listed as the last parameters in the parameter list. For example, the latter parameter of the function

void print_value(int value, unsigned int indent = 0);

is a default-value one. The function prints the given value with the given indentation. If no indentation is given, no indentation happens, since the default value is zero. The following calls are legal:

print_value(1);
print_value(2, 4);
print_value(3, 0);

As we hinted earlier, there are other C++ mechanisms to use at similar situations. You will get to know them in the advanced programming courses (Programming 3: Techniques and Software Design).

Specialities of the main function

In C++, the return value type of the main function is always int. It is customary for the program to end with the return value EXIT_SUCCESS if there are no errors. If there are errors in the execution, the return value will be EXIT_FAILURE. They are constants defined in the library cstdlib. EXIT_SUCCESS has the value 0 and EXIT_FAILURE 1. Sometimes you see programs where main will simply end with the command return 0. For the sake of creating readable program code, it is always better to use fixed constants than magic numbers.

In theory, the main function should end with the return command, because it is of the int type. However, the main function is an exception. If there is no return statement, the compiler will automatically set EXIT_SUCCESS as the return value of the main function.

Caution

Use the aforementioned constants (EXIT_SUCCESS and EXIT_FAILURE) only as a return value of main function. In any context their behaviour is most probably different from you expected. (The type of these constants is not bool.)