Control Flow, Functions and Lambdas · Lesson 8 of 33
Functions in C++: Overloading and Default Arguments
C++ functions work like C functions, plus a few powerful additions: several functions can share a name (overloading), parameters can have default values, and functions can return several values neatly. This lesson covers each, with the rules that decide which function gets called.
In this lesson
- Function overloading
- Default arguments
- Returning values, including several at once
- Recursion and constexpr functions
Function overloading
#include <iostream>
#include <string>
#include <vector>
int area(int side) { return side * side; } // square
int area(int width, int height) { return width * height; } // rectangle
double area(double radius) { return 3.14159 * radius * radius; } // circle
void print(int x) { std::cout << "int: " << x << '\n'; }
void print(const std::string& s) { std::cout << "string: " << s << '\n'; }
void print(const std::vector<int>& v) {
std::cout << "vector of " << v.size() << '\n';
}
int main() {
std::cout << area(5) << ' ' << area(4, 6) << ' ' << area(2.0) << '\n';
print(42);
print("hello"); // a literal converts to std::string
print(std::vector<int>{1, 2, 3});
return 0;
}
25 24 12.5664 int: 42 string: hello vector of 3
The compiler picks the overload whose parameter list best matches the arguments – their number and types. The return type alone can't distinguish overloads: int f(int) and double f(int) can't both exist.
Ambiguous calls
With void f(long) and void f(double), the call f(5) is an error: converting the int 5 to either is equally good, so the compiler refuses to guess. Add an exact overload or cast the argument.
Default arguments
#include <iostream>
#include <string>
std::string greet(const std::string& name, const std::string& greeting = "Hello", char end = '!') {
return greeting + ", " + name + end;
}
double price_with_tax(double amount, double rate = 0.18) {
return amount * (1 + rate);
}
int main() {
std::cout << greet("Asha") << '\n';
std::cout << greet("Ravi", "Namaste") << '\n';
std::cout << greet("Meera", "Welcome", '.') << '\n';
std::cout << price_with_tax(1000) << ' ' << price_with_tax(1000, 0.05) << '\n';
return 0;
}
Hello, Asha! Namaste, Ravi! Welcome, Meera. 1180 1050
Defaults must come at the end of the parameter list, and you can only leave out arguments from the right. Put defaults in the declaration (the header), not repeated in the definition.
Returning values
#include <iostream>
#include <optional>
#include <string>
#include <utility>
#include <vector>
std::pair<int, int> min_max(const std::vector<int>& v) { // return two values
int lo = v[0], hi = v[0];
for (int x : v) { lo = std::min(lo, x); hi = std::max(hi, x); }
return {lo, hi};
}
std::optional<int> find_index(const std::vector<int>& v, int key) { // "maybe a value"
for (std::size_t i = 0; i < v.size(); i++)
if (v[i] == key) return static_cast<int>(i);
return std::nullopt;
}
std::vector<int> squares(int n) { // returning a big object by value is cheap (move/elision)
std::vector<int> out;
for (int i = 1; i <= n; i++) out.push_back(i * i);
return out;
}
int main() {
std::vector<int> data{42, 7, 93, 18};
auto [lo, hi] = min_max(data); // structured bindings
std::cout << "min " << lo << ", max " << hi << '\n';
if (auto idx = find_index(data, 93)) std::cout << "93 at index " << *idx << '\n';
if (!find_index(data, 5)) std::cout << "5 not found\n";
for (int s : squares(5)) std::cout << s << ' ';
std::cout << '\n';
return 0;
}
min 7, max 93 93 at index 2 5 not found 1 4 9 16 25
Modern return style
Return results by value – even vectors and strings – instead of filling output parameters; the compiler avoids the copy (copy elision and move semantics). Use std::pair/std::tuple or a small struct for several results, and std::optional when a result may be missing instead of a magic value like −1.
Recursion and constexpr functions
#include <iostream>
constexpr long long factorial(int n) { // usable at compile time and run time
return n <= 1 ? 1 : n * factorial(n - 1);
}
long long power(long long base, int exp) { // fast exponentiation: O(log exp)
if (exp == 0) return 1;
long long half = power(base, exp / 2);
return exp % 2 == 0 ? half * half : half * half * base;
}
int main() {
static_assert(factorial(5) == 120); // checked by the compiler
std::cout << factorial(20) << '\n';
std::cout << power(2, 40) << ' ' << power(3, 13) << '\n';
return 0;
}
2432902008176640000 1099511627776 1594323
static_assert checks a condition at compile time – if factorial were wrong, the program wouldn't compile at all. The halving trick in power (binary exponentiation) is a standard competitive-programming technique, usually combined with a modulus.
Practice
Try it yourself
Overload
max_offor two ints, three ints and a vector of ints.Show solution
#include <algorithm> #include <iostream> #include <vector> int max_of(int a, int b) { return a > b ? a : b; } int max_of(int a, int b, int c) { return max_of(max_of(a, b), c); } int max_of(const std::vector<int>& v) { return *std::max_element(v.begin(), v.end()); } int main() { std::cout << max_of(3, 9) << ' ' << max_of(4, 11, 7) << ' ' << max_of(std::vector<int>{5, 2, 8}) << '\n'; return 0; }9 11 8
Quick check
Can two overloads differ only in return type?
No – overloads must differ in their parameter lists.
Where must default arguments appear?
At the end of the parameter list (rightmost parameters).
How do you return "a value or nothing"?
std::optional<T>, returning std::nullopt for nothing.
Summary
- Overloading lets functions share a name if their parameters differ.
- Default arguments fill in trailing parameters.
- Return by value; use pair/tuple/struct for several results and optional for maybe-results.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.