Function Overloading
This lesson provides an in-depth look at function overloading in C++, covering its importance, implementation, and best practices
Polymorphism is a principle in programming where the same expression can have multiple forms, based on the context in which it is used, or the data types it is used with.
This approach can keep our code simple and readable, even as our program gets more and more complex.
Types of Polymorphism
In C++, polymorphism manifests mainly in three forms:
Overloading
This includes both operator and function overloading.
- Operator Overloading: Previously, we discussed how operator overloading allows operators to have different behaviors based on their arguments. For example, expressions like
A + B
will do different things based on the types ofA
andB
. - Function Overloading: Function overloading allows multiple functions to have the same name but different parameter lists (either in the number of parameters or their type). This enables an expression like
CalculateArea(MyObject)
to behave differently based on the type ofMyObject
.
Templates
Templates allow us to define recipes for blocks of code. The compiler can then use these recipes to generate functions or entire classes that adapt to our needs.
We cover templates in more detail in the next course.
Run Time Polymorphism
Run Time Polymorphism, which we will cover later in this chapter, interacts with the inheritance trees, pointers, and references we introduced earlier. It allows our code to behave much more dynamically, without introducing excessive complexity.
Overloading Functions
To overload functions, we simply define functions within the same scope, that have the same name, but have a different parameter list.
Below, we define a Rectangle
and Circle
type. Alongside each class, we provide standalone CalculateArea()
functions:
struct Rectangle {
float Width;
float Height;
};
float CalculateArea(const Rectangle& R){
return R.Width * R.Height;
}
struct Circle {
float Radius;
};
float CalculateArea(const Circle& C){
return 3.14 * C.Radius * C.Radius;
}
When these functions are both available, they're considered overloaded.
Elsewhere, in code that uses our classes, we can use a consistent syntax. We can just pass an object to CalculateArea(SomeObject)
, and that expression adapts based on the type of SomeObject
.
From the perspective of the consumer, it doesn't look like they're calling two different functions, rather it seems like they're calling a single, polymorphic function:
int main(){
Circle MyCircle{2};
cout << "Circle Area: "
<< CalculateArea(MyCircle);
Rectangle MyRectangle{3, 4};
cout << "\nRectangle Area: "
<< CalculateArea(MyRectangle);
}
Circle Area: 12.56
Rectangle Area: 12
Test your Knowledge
Function Overloading
After running the following code, what is the value of MyBool
?
bool IsFloat(float Number) { return true; }
bool IsFloat(int Number) { return false; }
bool MyBool { IsFloat(5) };
Overloading Member Functions
Member functions can also be overloaded in the same way. Let's imagine we have the following situation, where our Character
objects can equip weapons:
class Weapon{};
class Character {
public:
void Equip(Weapon* Weapon){
mWeapon = Weapon;
}
private:
Weapon* mWeapon{nullptr};
};
int main(){
Character Player;
Weapon WoodenSword;
Player.Equip(&WoodenSword);
}
We'd like to be able to equip other items too. Our first instinct to accommodate this might be to rename our existing Equip()
function to EquipWeapon()
, and then add new functions called EquipArmor()
, EquipShield()
, and so on.
But we don't need to - we can just overload the Equip()
function, keeping our class friendly to use:
class Weapon {};
class Shield {};
class Character {
public:
void Equip(Weapon* Weapon){
mWeapon = Weapon;
}
void Equip(Shield* Shield){
mShield = Shield;
}
private:
Weapon* mWeapon{nullptr};
Shield* mShield{nullptr};
};
int main(){
Character Player;
Weapon WoodenSword;
Player.Equip(&WoodenSword);
Shield WoodenShield;
Player.Equip(&WoodenShield);
}
Ambiguous Function Calls
When we're working with overloaded functions, we may see the compiler report errors, claiming our function calls are ambiguous.
In these scenarios, we often need to be more explicit about our data types.
An example of a simple program that falls foul of this rule is below:
void Func(int Arg){}
void Func(float Arg){}
int main(){
Func(1.5);
}
'Func': ambiguous call to overloaded function
In an earlier note covering the concepts of literals, we pointed out that a literal like 1.5
is interpreted as a double
, rather than a float
. Float literals append an f
, as in 1.5f
.
So far, we have omitted the f
to make our code easier to read for beginners, and because it generally doesn't matter - doubles can be implicitly converted to floats.
However, doubles can be implicitly converted to integers, too, which causes a problem in this context.
Without the f
, our code is ambiguous. Should the compiler convert the double
to an int
and call Func(int)
? Or should it convert it to a float
and call Func(float)
?
Rather than guessing our intentions, it asks us to clarify.
In the next lesson, we'll see how we can solve problems like this by being explicit about our conversions. For now, we can do it manually:
void Func(int Arg){}
void Func(float Arg){}
int main(){
// Call Func(int)
Func(1);
// Call Func(float)
Func(1.5f);
}
Test your Knowledge
Function Overloading
After running the following code, what is the value of MyBool
?
bool IsDouble(double Number) { return true; }
bool IsDouble(float Number) { return false; }
bool MyBool { IsDouble(5) };
Summary
In this lesson, we explored several key concepts:
- Understanding Polymorphism and Overloading: We started by exploring polymorphism and explaining the main ways we can implement it in C++.
- Function Overloading with Different Types: We learned how to overload functions by creating functions with the same name but different parameters
- Overloading Member Functions: The concept was further extended to member functions in classes, illustrating a
Character
class that can equip different items like weapons and shields. - Resolving Ambiguities in Overloaded Functions: We addressed potential ambiguities that arise with overloaded functions and how to resolve them
Static Casting
Explore the concept of static casting in C++, including examples and best practices for converting data types at compile time