Chapter 6. Functions

6.1 Function Basics

A function definition typically consists of a return type, a name, a list of zero or more parameters, and a body. The function can be executed by the call operator, which is pair of parentheses. The call operator takes an expression that is a function or points to a function. Inside the parentheses is a comma-separated list of arguments. The arguments are used to initialise the function's parameters.

6.1.1 Local Objects

In C++, names have scope, and objects have lifetimes. It is important to understand both of these concepts.

  • The scope of a name is the part of the program's text in which that name is visible.

  • The lifetime of an object is the time during the program's execution that the object exists.

Parameters and variables defined inside a function body are referred to as local variables. They are "local" to that function and hide declarations of the same name made in an outer scope.

Objects defined outside any function exist throughout the program's execution. Such objects are created when the program starts and are not destroyed until the programs ends.

Each local static object is initialised before the first time execution passes through the object's definition. Local statics are not destroyed when a function ends; they are destroyed when the program terminates.

6.1.2 Function Declarations

A function may be defined only once but may declared multiple times. A function declaration is just like a function definition except that a declaration has no function body. In a declaration, a semicolon replaces the body. Function declarations are also known as the function prototype.

Functions should be declared in header files and defined in source files.

6.1.3 Separate Compilation

The separate compilation lets us split our programs into several files, each of which can be compiled independently.

To produce an executable file, it needs to compile these files as follow:

$ CC factMain.cc fact.cc # generates factMain.exe or a.out
$ CC factMain.cc fact.cc -o main # generates main or main.exe

Most compilers provide a way to separately compile each file. This process usually yields a file with the .obj (Windows) or .o (UNIX) file extension, indicating that the file contains object code.

The compiler lets us link object files together to form an executable:

$ CC -c factMain.cc # generates factMain.o
$ CC -c fact.cc # generates fact.o
$ CC factMain.o fact.o # generates factMain.exe or a.out
$ CC factMain.o fact.o -o main # generates main or main.exe

6.2 Argument Passing

When a parameter is reference, we say that its corresponding argument is "passed by reference" or that the function is "called by reference".

When the argument value is copied, the parameter and argument are independent object. We say such arguments are "passed by value" or alternatively that the function is "called by value".

6.2.1 Passing Argument by Value

// function that takes a pointer and sets the pointed-to value to zero
void reset(int *ip) {
    *ip = 0;    // changes the value of the object to which ip points
    ip = 0;    // changes only the local copy of ip; the argument is unchanged
}

6.2.2 Passing Argument by Reference

// function that takes a reference to an int and sets the given object to zero
void set(int &i) {
    i = 0;
}

Using References to Avoid Copies

bool isShorter(const string &s1, const string &s2) {
    return s1.size() < s2.size();
}

Using Reference Parameters to Return Additional Information

A function can return only a single value. However, sometimes a function has more than one value to return. Reference parameters let us effectively return multiple results.

6.2.3 const Parameters and Arguments

void fcn(const int i) { /* fcn can read but not write to i */ }

6.2.4 Array Parameters

Arrays have two special properties that affect how we defined and use functions that operate on arrays. It cannot pass an array by value since we cannot copy an array. Because arrays are converted to pointers, when we pass an array to a function, we are actually passing a pointer to the array's first element.

Because arrays are passed as pointers, functions ordinarily don't know the size of the array they are given. They must rely on additional information provided by the caller.

Passing a Multidimensional Array

The size of the second dimensional is part of the element type and must be specified.

6.2.5 main: Handling Command-Line Options

Such command-line options are passed to main in two parameters:

int main(int argc, char *argv[]) { ... }
int main(int argc, char **argv) { ... }

6.2.6 Functions with Varying Parameters

An initialiser_lizer is a library type that represents an array of values of the specified type. This type is defined in the initializer_list header.

Operation

Description

initializer_list<T> lst;

Default initialisation; an empty list of elements of type T.

initializer_list<T> lst{a, b, c...};

lst has as many elements as there are initialisers; elements are copies of the corresponding initialiser. Elements in the list are const.

lst2(lst)

Assigning an initializer_list does not copy the elements in the list (share the elements).

lst2 = lst

Copying an initializer_list does not copy the elements in the list (share the elements).

lst.size()

Number of elements in the list.

lst.begin()

Returns a pointer to the first element in lst.

lst.end()

Returns a point to the last element in lst.

Unlike vector, the elements in an initializer_list are always const values; there is no way to change the value of an element in an initializer_list.

Ellipsis Parameters

Ellipsis parameters are in C++ to allow programs to interface to C code that uses a C library facility named varargs.

void foo(parm_list, ...);
void foo(...);

6.3 Return Types and the return Statement

A return statement terminates the function that is currently executing and returns control to the point from which the function was called. There are two forms of return statements:

return;
return expression;

6.3.1 Functions with No Return Value

A return with no value may be used only in a function that has a return type of void. Functions that return void are not required to contain a return.

6.3.2 Functions That Return a Value

The second form of the return statement provides the function's result. The value returned must have the same type as the function return type, or it must have a type that can be implicitly converted to that type.

How Values Are Returned

Values are returned in exactly the same way as variables and parameters are initialised: The return value is used to initialise a temporary at the call site, and that temporary is the function call.

As with any other reference, when a function returns a reference, that reference is just another name for the object to which it refers.

Never Return a Reference or Pointer to a Local Object

When a function completes, its storage is freed. After a function terminates, references to local objects refer to memory that is no longer valid.

Functions That Return Class Types and the Call Operator

The call operator has the same precedence as the dot and arrow operators.

Reference Returns Are Lvalues

Whether a function call is an lvalue depends on the return type of the function. Calls to functions that return references are lvalue; other return type yield rvalue.

List Initialising the Return Value

Under the new standard, functions can return a braced list of values. As in any other return, the list is used to initialise the temporary that represents the function's return.

Return from main

The main function is allowed to terminate without a return. If control reaches the end of main and there is no return, then the compiler implicitly inserts a return of 0.

A zero return indicates success; most other values indicate failure. To make return values machine independent, the cstdlib header defines two preprocessor variables: EXIT_SUCCESS and EXIT_FAILURE .

Recursion

A function that calls itself, either directly or indirectly, is a recursive function.

6.3.3 Returning a Pointer to an Array

A function cannot return an array since we cannot copy an array. However, a function can return a pointer or a reference to array.

Trailing returns can be defined for any function, but are most useful for functions with complicated return types, such as pointers to array.

auto func(int i) -> int(*)[10];

6.4 Overloaded Function

Functions that have the same name but different parameter lists and that appear in the same scope are overloaded.

The main function may not be overloaded.

A parameter that has a top-level const is indistinguishable from one without a top-level const:

Record lookup(Phone);
Record lookup(const Phone); // redeclares Record lookup(Phone)

On the other hand, we can overload based on whether the parameter is reference to the const or non-const version of a given type; such consts are low-level:

Record lookup(Account&);
Record lookup(const Account&); // new function that takes a const reference

Function matching is the process by which a particular function call is associated with a specific function from a set of overloaded functions.

  • The compiler finds exactly one function that is a best match for the actual arguments and generates code to call that function.

  • There is no function with parameters that match the arguments in the call, in which case the compiler issues an error message that there was no match.

  • There is more than one function that matches and none of the matches is clearly best. This case is also an error; it is an ambiguous call.

6.4.1 Overloading and Scope

Overloading has no special properties with respect to scope: As usual, if we declare a name in an inner scope, that name hides uses of that name declared in an outer scope.

6.5 Features for Specialised Uses

6.5.1 Default Arguments

Some functions have parameters that are given a particular value in most, but not all, calls. In such cases, we can declare that common value as a default argument for the function. Functions with default arguments can be called with or without that argument.

Although it is normal practice to declare a function once inside a header, it is legal to redeclare a function multiple times. Any subsequent declaration can add a default only for a parameter that has not previously had a default specified.

string screen(sz, sz, char = ' ');
string screen(sz, sz, char = '*'); // error: redeclaration
string screen(sz = 24, sz = 80, char); // ok: adds default argument

6.5.2 Inline and constexpr Functions

A function specified as inline is expanded "in line" at each call. The inline specification is only a request to the compiler. The compiler may choose to ignore this request.

In general, the inline mechanism is meant to optimise small, straight-line functions that are called frequently.

constexpr Functions

A constexpr function is a function that can be used in a constant expression. The return type and the type of each parameter in a must be a literal type, and the function body must contain exactly one return statement.

Put inline and constexpr Functions in Header Files

Unlike other functions, inline and constexpr functions may be defined multiple times in the program. After all, the compiler needs the definition, not just the declaration, in order to expand the code. As a result, inline and constexpr functions normally are defined in headers.

6.5.3 Aids for Debugging

When the application is completed and ready to ship, the debugging code is turned off. This approach uses two preprocessor facilities: assert and NDEBUG.

The assert Preprocessor Macro

The assert is a preprocessor macro. A preprocessor macro is a preprocessor variable the acts somewhat like an inline function.

assert(expr);

evaluate expr and if the expression is false, then assert writes a message and terminates the program. If the expression is true, then assert does nothing.

The assert macro is defined in the cassert header.

The NDEBUG Preprocessor Variable

The behaviour of assert depends on the status of a preprocessor variable named NDEBUG. If NDEBUG is defined, assert does nothing. By default, NDEBUG is not defined, so, by default, assert performs a run-time check.

We can "turn off" debugging by providing a #define to define NDEBUG. Alternatively, most compilers provide a command-line option that lets us define preprocessor variables:

$ CC -D NDEBUG main.C

has the same effect as writing #define NDEBUG at the beginning of main.C.

6.6 Function Matching

The first step of function matching identifies the set of overloaded functions considered for the call. The functions in this set are the candidate functions. A candidate function is a function with the same name as the called function and for which a declaration is visible at the point of the call.

The second step selects from the set of candidate functions those functions that can be called with the arguments in the given call. The selected functions are the viable functions.

The third step of function matching determines which viable function provides the best match for the call.

There is an overall best match if there is one and only one function for which:

  • The match for each argument is no worse than the match required by any other viable function.

  • There is at least one argument for which the match is better than the match provided by any other viable function.

6.6.1 Argument Type Conversions

  1. An exact match. An exact match happens when:

    1. The argument and parameter types are identical.

    2. The argument is converted from an array or function type to the corresponding pointer type.

    3. A top-level const is added to or discarded from the argument.

  2. Match through a const conversion

  3. Match through a promotion

  4. Match through an arithmetic or pointer conversion

  5. Match through a class-type conversion

6.7 Pointers to Functions

A function pointer is just that, a pointer that denotes a function rather than an object. A function's type is determined by its return type and the types of its parameters.

bool lengthCompare(const string&, const string&);
bool (*pf)(const string&, const string&); // function pointer

Using Function Pointers

pf = lengthCompare; // pf now points to the function named lengthCompare
pf = &lengthCompare; // equivalent assignment: address-of operator is optional
bool b1 = pf("hello", "goodbye");
bool b2 = (*pf)("hello", "goodbye");
bool b3 = lengthCompare("hello", "goodbye");

Function Pointer Parameters

We cannot define parameters of function type but can have a parameter that is a pointer to function.

// third parameter is a function type
void useBigger(const string &s1, const string &s2,
    bool pf(const string &, const string &));
// equivalent declaration
void useBigger(const string &s1, const string &s2,
    bool (*pf)(const string &, const string &));

Returning a Pointer to Function

We can't return a function type but can return a pointer to a function type.

Last updated

Was this helpful?