Function 3
Function modifiers
The function declaration has the following form:
return_type func_name(parameter_list) suffix; prefix
You can provide a number of optional modifiers (or specifiers) to functions.
- Modifiers alter the behavior of the function.
- Some modifiers appear before the return type (
prefix
), some appear at the end (suffix
).
Prefix modifiers
static
function
When a function is declared static at the global (file) scope, its visibility is limited to the file in which it is defined.
- It can be called only within the file and can’t be used in other files.
- Useful for helper functions that are only used within the file, and avoid name conflicts.
static
class method
In the context of a class, the static modifier is used to define static member functions.
- These functions belong to the class rather than an instance of the class.
- Recall the static variable belongs to the class, not to an instance.
- As a result, they can be called using the class name (without creating an object) and don’t have access to
this
pointer.
class MyClass {
public:
static void staticMethod();
};
::staticMethod(); // Call the static method MyClass
constexpr
The constexpr
keyword is to indicate that the function should be evaluated at compile time if possible.
- It can be used to optimize performance by avoiding runtime computation.
- The function must be a pure function
- It should not have side effects and should only depend on its input parameters.
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
Other prefix modifiers
inline
:inline
suggests that the compiler should inline the function, replacing the function call with the actual code of the function.virtual
:virtual
is used in class methods that can be overridden in derived classes.[[nodiscard]]
: The function returns a value that should be used.[[noreturn]]
: The function won’t return.
Suffix modifiers
override
and final
1
override
: Indicates that the function is intended to override a virtual function from a base class.final
: Prevents a function from being overridden in derived classes.- Another usage is to prevent a class from being inherited.
class A {
public:
virtual void my_func();
};
class B : public A {
public:
void my_func() override;
};
class C : public B {
public:
void my_func() final;
};
class D : public C {
public:
void my_func() override; // Error: my_func is final in C
};
- class
C
has afinal
methodmy_func
, so classD
cannot override it.
Variadic functions/templates
Variadic functions
Variadic functions take a variable number of arguments.
- With variadic functions, you can take any number of arguments.
- If you are a python programmer, you can think of it as
*args
.
- If you are a python programmer, you can think of it as
- The compiler matches the arguments to the parameters based on the order, any leftovers pack into the variadic parameter represented by
...
.
int sum(size_t n, ...);
// usage
int num1 = sum(1, 1);
int num2 = sum(2, 2, 2);
int num3 = sum(6, 2, 4, 6, 8, 10, 12); // 6 means there are 6 arguments
Implement a variadic function
You can’t extract elements from the variadic parameter directly.
- You access the individual element by using utility functions in
<cstdarg>
.
Function | Description |
---|---|
va_list |
A type used to hold the information needed to retrieve the additional arguments after the fixed parameters. |
va_start |
Initializes a va_list variable to retrieve the additional arguments. |
va_arg |
Retrieves the next additional argument from the va_list . |
va_end |
Cleans up the va_list when done. |
va_copy |
Copies a va_list . |
int sum(size_t n, ...){
va_list args;
(args, n); // initialize the list
va_start
int result = 0;
for (size_t i = 0; i < n; ++i) {
auto next_arg = va_arg(args, int);
+= next_arg;
result }
(args); // clear the list
va_endreturn result;
}
va_list
is initialized byva_start
, and cleaned up byva_end
.- Think about a pointer to a list of arguments,
va_list
type traverses the list.
- Think about a pointer to a list of arguments,
va_arg(va_list ap, type)
is used to get the next argument.ap
is theva_list
variable initialized byva_start
.type
is the type of the next argument.- It returns the next argument and moves the pointer forward.
Variadic templates
There’re at least two disadvantages of variadic functions:
- Variadic parameters are not type-safe, you can’t check the type of the arguments.
- The number of arguments has to be known at compile time.
Variadic templates provides a safer and more flexible alternative. To declare a variadic template, you add a special template parameter ...
called “parameter pack”.
Parameter pack
template <typename... Args>
return_type func_name(Args... args);
Args...
is the parameter pack, it’s part of the function parameter list.- You can invoke a function inside the function template with the parameter pack.
- Syntax:
func_name(args...)
. - This expands the parameter pack
args
and allows you to perform further processing on the arguments contained in the parameter pack.
- Syntax:
Programming with Parameter Packs
Unfortunately, the usage of parameter packs is not straightforward.
- You can’t directly access the elements of the parameter pack.
- A process called “compile-time recursion” is needed to process the parameter pack.
// base case
template <typename T>
void my_func(T x) {
// do something with x
}
template <typename T, typename... Args>
void my_func(T x, Args... args) {
// Use x, then recurse:
(args...);
my_func}
Revisiting the sum
function2
template <typename T>
(T x) {
T sumreturn x;
}
template <typename T, typename... Args>
(T x, Args... args) {
T sumreturn x + sum(args...);
}
// usage
int num1 = sum(1); // 1
int num2 = sum(2, 2); // 4
int num3 = sum(6, 2, 4, 6, 8, 10, 12); // 48
- The base case
template <typename T>
is needed to terminate the recursion. - The compiler will expand the parameter pack
Args...
into a series of arguments until the base case is reached.
Fold expressions
C++17 introduces fold expressions, which provide a concise way to perform operations on a parameter pack.
When you want to perform operations on a parameter pack, you can use fold expressions. The syntax is as follows:
template <typename... Args>
return_type func_name(Args... args) {
return (args op ...); // op is the operator you want to apply
}
For example, you can use fold expressions to implement the sum
function.
template <typename... Args>
auto sum(Args... args) {
return (args + ...);
}
A more complex example
Sometimes you need to perform non-binary operations on different elements of the parameter pack. In this case, you can only use compile-time recursion.
For example, you want to print a list of arguments with index.
// Base case
void print_with_index(size_t) {} // Do nothing
// Recursive case
template <typename T, typename... Args>
void print_with_index(size_t index, T first, Args... rest) {
std::cout << "Argument " << index << ": " << first << std::endl;
(index + 1, rest...); // Recur with next index
print_with_index}
int main() {
(0, "Hello", 42, 3.14, 'A');
print_with_indexreturn 0;
}
Function pointers
Function pointer
Function also occupy memory, so you can have a pointer to a function.
- Unlike other pointers, you can’t modify the pointed-to function.
- Think about it as a pointer to a
const
object.
- Think about it as a pointer to a
Declare a function pointer has a “ugly” syntax.
return_type (*pointer_name)(arg_type1, arg_type2, ...);
It has a same syntax as a function declaration, but with a *
in front of the name.
Function pointer as a parameter
Suppose you write two functions, one to add all the numbers in an array, and one to calculate the product of all the numbers in an array.
int sum_array(int* array, size_t size);
int product_array(int* array, size_t size);
- Most of the codes of these two functions are the same, except for the operation.
- And to allow more future extension, you can pass a function pointer as a parameter.
int apply_operation(int* array, size_t size, int (*operation)(int, int));
The implementation of apply_operation
is straightforward.
int apply_operation(int* array, size_t size, int (*operation)(int, int)) {
int result = array[0];
for (size_t i = 1; i < size; ++i) {
= operation(result, array[i]);
result }
return result;
}
// functions
int sum(int a, int b) { return a + b; }
int product(int a, int b) { return a * b; }
// usage
int sum_result = apply_operation(array, size, sum);
int product_result = apply_operation(array, size, product);
- When pass function pointer as a parameter, you don’t need to add
&
to the function name, it’s a pointer already.
Function pointer combining with templates
In the previous example, you can only do integer operations. To allow more flexibility, you can combine function pointer with templates.
template <typename T>
(T* array, size_t size, T (*operation)(T, T)) {
T apply_operation= array[0];
T result for (size_t i = 1; i < size; ++i) {
= operation(result, array[i]);
result }
return result;
}
template <typename T>
(T a, T b) { return a + b; }
T sum
template <typename T>
(T a, T b) { return a * b; }
T product
// usage
double array[] = {1.1, 2.2, 3.3, 4.4, 5.5};
size_t size = 5;
double sum_result = apply_operation(array, size, sum);
double product_result = apply_operation(array, size, product);
Function object (Functor)
What is a function object?
- A function object is an object that can be called like a function.
- It’s a class that overloads the
operator()
(call operator). - It’s a kind of “function wrapper”.
In the previous assignment, you have already used function objects. For example, we define a class to calculate the payoff for a European call option.
class EuropeanCallPayoff {
public:
double operator()(double spot) const;
double strike;
};
Counting the number of a certain element in an array
struct CountIf {
(char value) : value(value) {}
CountIfsize_t operator()(const char* str) const {
size_t count = 0;
for (const char* p = str; *p != '\0'; ++p) {
if (*p == value) ++count;
}
return count;
}
private:
char value;
};
- The
operator()
is overloaded to take aconst char*
argument.- The
operator()
is the method name. - The second
()
takes the argument.
- The