LearnFrenzy
Sign in Take a free test

Control Flow, Functions and Lambdas · Lesson 8 of 33

Functions in C++: Overloading and Default Arguments

6 min read Updated Nov 7, 2026 Saurabh Samir

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;
}

Output

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;
}

Output

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;
}

Output

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;
}

Output

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

  1. Overload max_of for 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;
    }

    Output

    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.

Next lesson

Popular