[CPP] Function overloading, templates

1-Default Arguments

Default arguments

  • A feature in C++ (not C)
  • To call a function without providing one or more trailing arguments

default-argument.cpp

#include <iostream>
#include <cmath>
using namespace std;

float norm(float x, float y, float z);
float norm(float x, float y, float z = 0);
float norm(float x, float y = 0, float z);

int main()
{<!-- -->
    cout << norm(3.0f) << endl;
    cout << norm(3.0f, 4.0f) << endl;
    cout << norm(3.0f, 4.0f, 5.0f) << endl;
    return 0;
}

float norm(float x, float y, float z)
{<!-- -->
    return sqrt(x * x + y * y + z * z);
}

#include <iostream>
#include <cmath>
using namespace std;

float norm(float x, float y, float z);
float norm(float x, float y = 0, float z);

int main()
{<!-- -->
    return 0;
}

float norm(float x, float y, float z)
{<!-- -->
    return sqrt(x * x + y * y + z * z);
}


Missing default parameters

Default parameters should be defined starting from the end

Repeatedly defining default parameters will also report an error:

#include <iostream>
#include <cmath>
using namespace std;

float norm(float x, float y, float z);
float norm(float x, float y, float z = 0);
float norm(float x, float y = 0, float z = 4);

int main()
{<!-- -->
    return 0;
}

float norm(float x, float y, float z)
{<!-- -->
    return sqrt(x * x + y * y + z * z);
}

2-Function-Overloading

Why to overload?

  • C99
<math.h>

double round (double x);
float roundf (float x);
long double roundl (long double x);
  • C++11
<cmath>

double round (double x);
float round (float x);
long double round (long double x);
  • which one do you prefer?

Function overloading

  • Which function to choose? The compiler will perform name lookup
  • Argument-dependent lookup, also known as ADL
  • The return type will not be considered in name lookup
#include <iostream>

using namespace std;

int sum(int x, int y)
{<!-- -->
    cout << "sum(int, int) is called" << endl;
    return x + y;
}
float sum(float x, float y)
{<!-- -->
    cout << "sum(float, float) is called" << endl;
    return x + y;
}
double sum(double x, double y)
{<!-- -->
    cout << "sum(double, double) is called" << endl;
    return x + y;
}

// //Is the following definition correct?
// double sum(int x, int y)
// {<!-- -->
// cout << "sum(int, int) is called" << endl;
// return x + y;
// }

int main()
{<!-- -->

    cout << "sum = " << sum(1, 2) << endl;
    cout << "sum = " << sum(1.1f, 2.2f) << endl;
    cout << "sum = " << sum(1.1, 2.2) << endl;

    //which function will be called?
    //cout << "sum = " << sum(1, 2.2) << endl;

    return 0;
}

//Is the following definition correct?
double sum(int x, int y)
{<!-- -->
    cout << "sum(int, int) is called" << endl;
    return x + y;
}

Two functions cannot be overloaded if they only have different return values.

#include <iostream>

using namespace std;

int sum(int x, int y)
{<!-- -->
    cout << "sum(int, int) is called" << endl;
    return x + y;
}
float sum(float x, float y)
{<!-- -->
    cout << "sum(float, float) is called" << endl;
    return x + y;
}
double sum(double x, double y)
{<!-- -->
    cout << "sum(double, double) is called" << endl;
    return x + y;
}
int main()
{<!-- -->

    //which function will be called?
    cout << "sum = " << sum(1, 2.2) << endl;

    return 0;
}

Parameters can match multiple functions. If there is ambiguity, an error will be reported.

#include <iostream>

using namespace std;

int sum(int x, int y)
{<!-- -->
    cout << "sum(int, int) is called" << endl;
    return x + y;
}

int main()
{<!-- -->

    //which function will be called?
    cout << "sum = " << sum(1, 2.2) << endl;

    return 0;
}

warning: implicit conversion from 'double' to 'int' changes value from 2.2 to 2 [-Wliteral-conversion]
    cout << "sum = " << sum(1, 2.2) << endl;
                        ~~~ ^~~
1 warning generated.

implicit type conversion

3-Function Templates

Why function templates

  • The definitions of some overloaded functions may be similar

Explicit Instantiation

  • A function template is not a type, or a function, or any other entity
  • No code is generated from a source file that contains only template definitions
  • The template arguments must be determined, then the compiler can generate an actual function

Instantiate

template<typename T>
T sum(T x, T y)
{<!-- -->
    cout << "The input type is " << typeid(T).name() << endl;
    return x +
     y;
}
// Explicitly instantiate
template double sum<double>(double, double);

// instantiates sum<char>(char, char), template argument deduced
template char sum<>(char, char);

// instantiates sum<int>(int, int), template argument deduced
template int sum(int, int);

template1.cpp

#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{<!-- -->
    cout << "The input type is " << typeid(T).name() << endl;
    return x +
     y;
}
// Explicitly instantiate
template double sum<double>(double, double);

int main()
{<!-- -->
    auto val = sum(4.1, 5.2);
    cout << val << endl;
    return 0;
}

The input type is d
9.3
#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{<!-- -->
    cout << "The input type is " << typeid(T).name() << endl;
    return x +
     y;
}
// Explicitly instantiate
template float sum<float>(float, float);

int main()
{<!-- -->
    auto val = sum(4.1, 5.2);
    cout << val << endl;
    return 0;
}

The input type is d
9.3

Why is the function of type double still called?

Why can it still compile if I delete the function?

In fact, it is because functions can be instantiated implicitly

Implicit Instantiation

  • Implicit instantiation occurs when a function template is not explicitly instantiated

implicit instantiation

template<typename T>
T sum(T x, T y)
{<!-- -->
    cout << "The input type is " << typeid(T).name() << endl;
    return x + y;
}
// Implicitly instantiates product<int>(int, int)
cout << "sum = " << sum<int>(2.2f, 3.0f) << endl;
// Implicitly instantiates product<float>(float, float)
cout << "sum = " << sum(2.2f, 3.0f) << endl;

template2.cpp

#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{<!-- -->
    cout << "The input type is " << typeid(T).name() << endl;
    return x + y;
}

int main()
{<!-- -->
    // Implicitly instantiates product<int>(int, int)
    cout << "sum = " << sum<int>(2.2f, 3.0f) << endl;
    // Implicitly instantiates product<float>(float, float)
    cout << "sum = " << sum(2.2f, 3.0f) << endl;

    return 0;
}

sum = The input type is i
5
sum = The input type is f
5.2

Function template specialization

  • We have a function template
template<typename T> T sum(T x, T y)
  • If the input type is Point
struct Point
{<!-- -->
  int x;
  int y;
};
  • But no + operator for Point;
  • We need to give a special definition for this case

specialization.cpp

#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{<!-- -->
    cout << "The input type is " << typeid(T).name() << endl;
    return x + y;
}

struct Point
{<!-- -->
    int x;
    int y;
};


int main()
{<!-- -->
    //Explicit instantiated functions
    cout << "sum = " << sum(1, 2) << endl;
    cout << "sum = " << sum(1.1, 2.2) << endl;

    Point pt1 {<!-- -->1, 2};
    Point pt2 {<!-- -->2, 3};
    Point pt = sum(pt1, pt2);
    cout << "pt = (" << pt.x << ", " << pt.y << ")" << endl;
    return 0;
}
specialization.cpp:9:14: error: invalid operands to binary expression ('Point' and 'Point')
    return x + y;
           ~ ^ ~
specialization.cpp:38:16: note: in instantiation of function template specialization 'sum<Point>' requested here
    Point pt = sum(pt1, pt2);

Illegal operation

Specifically define the Point structure template

#include <iostream>
#include <typeinfo>
using namespace std;

template<typename T>
T sum(T x, T y)
{<!-- -->
    cout << "The input type is " << typeid(T).name() << endl;
    return x + y;
}

struct Point
{<!-- -->
    int x;
    int y;
};

// Specialization for Point + Point operation
template<>
Point sum<Point>(Point pt1, Point pt2)
{<!-- -->
    cout << "The input type is " << typeid(pt1).name() << endl;
    Point pt;
    pt.x = pt1.x + pt2.x;
    pt.y = pt1.y + pt2.y;
    return pt;
}


int main()
{<!-- -->
    //Explicit instantiated functions
    cout << "sum = " << sum(1, 2) << endl;
    cout << "sum = " << sum(1.1, 2.2) << endl;

    Point pt1 {<!-- -->1, 2};
    Point pt2 {<!-- -->2, 3};
    Point pt = sum(pt1, pt2);
    cout << "pt = (" << pt.x << ", " << pt.y << ")" << endl;
    return 0;
}

sum = The input type is i
3
sum = The input type is d
3.3
The input type is 5Point
pt = (3, 5)

If only template is instantiated, if template<> is added, it is specialization.

4- Function Pointers and References

Function pointers

  • norm_ptr is a pointer, a function pointer
  • The function should have two float parameters, and returns float
#include <iostream>
#include <cmath>
using namespace std;

float norm_l1(float x, float y); //declaration
float norm_l2(float x, float y); //declaration
float (*norm_ptr)(float x, float y); //norm_ptr is a function pointer

int main()
{<!-- -->
    norm_ptr = norm_l1; //Pointer norm_ptr is pointing to norm_l1
    cout << "L1 norm of (-3, 4) = " << norm_ptr(-3.0f, 4.0f) << endl;

    norm_ptr = & amp;norm_l2; //Pointer norm_ptr is pointing to norm_l2
    cout << "L2 norm of (-3, 4) = " << (*norm_ptr)(-3.0f, 4.0f) << endl;

    return 0;
}

float norm_l1(float x, float y)
{<!-- -->
    return fabs(x) + fabs(y);
}

float norm_l2(float x, float y)
{<!-- -->
    return sqrt(x * x + y * y);
}
L1 norm of (-3, 4) = 7
L2 norm of (-3, 4) = 5

Function pointer: A pointer to a function. The function pointed to must be of the same type as the pointer. That is to say, the function pointed to by the pointer should have two parameters, and the types of both parameters should be float, the return value is also float

  • A function pointer can be an argument and pass to a function
<stdlib.h>

void qsort(void *ptr, size_t count, size_t size, int(*comp)(const void*, const void*));
  • To sort some customized types, such as
struct Point
struct Person

Function references

function-reference.cpp

#include <iostream>
#include <cmath>
using namespace std;

float norm_l1(float x, float y); //declaration
float norm_l2(float x, float y); //declaration
float ( & amp;norm_ref)(float x, float y) = norm_l1; //norm_ref is a function reference

int main()
{<!-- -->
    cout << "L1 norm of (-3, 4) = " << norm_ref(-3, 4) << endl;
    return 0;
}

float norm_l1(float x, float y)
{<!-- -->
    return fabs(x) + fabs(y);
}

float norm_l2(float x, float y)
{<!-- -->
    return sqrt(x * x + y * y);
}
L1 norm of (-3, 4) = 7

5- Recursive Functions

Recursive Functions

  • A simple example

recursion.cpp

#include <iostream>
using namespace std;

void div2(double val);

int main()
{<!-- -->
    div2(1024.); // call the recursive function
    return 0;
}

void div2(double val)
{<!-- -->

    cout << "Entering val = " << val << endl;
    if (val > 1.0)
        div2( val / 2); // function calls itself
    else
        cout << "--------------------------" << endl;
    
    cout << "Leaving val = " << val << endl;
}

Entering val = 1024
Entering val = 512
Entering val = 256
Entering val = 128
Entering val = 64
Entering val = 32
Entering val = 16
Entering val = 8
Entering val = 4
Entering val = 2
Entering val = 1
--------------------------
Leaving val = 1
Leaving val = 2
Leaving val = 4
Leaving val = 8
Leaving val = 16
Leaving val = 32
Leaving val = 64
Leaving val = 128
Leaving val = 256
Leaving val = 512
Leaving val = 1024
  • Pros:
  1. Good at tree traversal
  2. Less lines of source code
  • Cons:
  1. Consume more stack memory
  2. May be slow
  3. Difficult to implement and debug