Practical examples of polymorphism in C++: function overloading

If you’re trying to really understand polymorphism in C++, function overloading is one of the cleanest places to start. Instead of wading through abstract theory, looking at concrete examples of polymorphism in C++: function overloading shows you how the language actually behaves in real code. By writing several functions with the same name but different parameter lists, you let the compiler pick the right version based on how you call it. In this guide, we’ll walk through multiple real-world examples, from simple math utilities to logging, geometry, and modern C++ library-style helpers. These examples of polymorphism in C++: function overloading are the kind of patterns you’ll see in production code, interviews, and open source projects. Along the way, we’ll talk about performance, readability, and how these ideas map onto the C++ standard library and current C++23/C++26 trends. By the end, you’ll have a clear, practical picture of how and when to use function overloading effectively.
Written by
Jamie
Published
Updated

Let’s skip the theory lecture and go straight to code. Here’s a small, realistic example of polymorphism in C++: function overloading used in a math utility header you might actually ship.

// math_utils.h

int clamp(int value, int min, int max) {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}

double clamp(double value, double min, double max) {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}

float clamp(float value, float min, float max) {
    if (value < min) return min;
    if (value > max) return max;
    return value;
}

Same function name, different parameter types. When you call clamp(5, 0, 10), the int version is selected; when you call clamp(3.14, 0.0, 10.0), the double version is chosen. That is one of the best examples of polymorphism in C++: function overloading resolved entirely at compile time.

From here, we can build up to more interesting and realistic patterns.


Overloaded logging functions: a practical example of compile‑time polymorphism

Logging is one of the most common real examples of polymorphism in C++: function overloading. You often want a single log() function that can handle different input types without forcing the caller to manually convert everything to std::string.

#include <iostream>
#include <string>

void log(const std::string& message) {
    std::cout << "[INFO]  " << message << '\n';
}

void log(int value) {
    std::cout << "[INFO]  int: " << value << '\n';
}

void log(double value) {
    std::cout << "[INFO]  double: " << value << '\n';
}

void log(const char* message) {
    std::cout << "[INFO]  " << message << '\n';
}

int main() {
    log("Server started");          // calls const char* overload
    log(std::string("Ready"));     // calls std::string overload
    log(42);                        // calls int overload
    log(3.14159);                   // calls double overload
}

This pattern keeps your call sites clean. You don’t need std::to_string() everywhere; you just call log() and let the compiler choose the right overload. These examples of polymorphism in C++: function overloading show how you can get a consistent API surface while still handling different types efficiently.

In modern C++ codebases (C++17 and up), you’ll often see this combined with templates and std::string_view, but the core idea is the same: multiple functions with the same name, different parameter lists, and the compiler picks the match.


Geometry and graphics: examples include overloaded area and distance functions

Graphics, simulation, and game engines are packed with examples of polymorphism in C++: function overloading. You frequently work with different shapes or coordinate types but want a single conceptual operation.

Overloaded area functions

struct Circle {
    double radius;
};

struct Rectangle {
    double width;
    double height;
};

struct Triangle {
    double base;
    double height;
};

constexpr double PI = 3.141592653589793;

double area(const Circle& c) {
    return PI * c.radius * c.radius;
}

double area(const Rectangle& r) {
    return r.width * r.height;
}

double area(const Triangle& t) {
    return 0.5 * t.base * t.height;
}

int main() {
    Circle c{2.0};
    Rectangle r{3.0, 4.0};
    Triangle t{3.0, 5.0};

    double ac = area(c);  // Circle overload
    double ar = area(r);  // Rectangle overload
    double at = area(t);  // Triangle overload
}

All three functions are called area, but each one knows how to handle a different type. This is a textbook example of polymorphism in C++: function overloading used in a domain that actually matters, like a basic geometry engine or a teaching tool for physics.

Overloaded distance for 2D and 3D

#include <cmath>

struct Vec2 { double x, y; };
struct Vec3 { double x, y, z; };

double distance(const Vec2& a, const Vec2& b) {
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return std::sqrt(dx * dx + dy * dy);
}

double distance(const Vec3& a, const Vec3& b) {
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    double dz = a.z - b.z;
    return std::sqrt(dx * dx + dy * dy + dz * dz);
}

Here, distance is overloaded for 2D and 3D vectors. Callers don’t care which dimension they’re working in; they just call distance(p1, p2) and let function overloading handle the details.


String utilities: best examples of overloads for flexibility

String handling is another area where examples of polymorphism in C++: function overloading show up constantly, especially in modern code that uses std::string_view.

#include <string>
#include <string_view>

bool starts_with(std::string_view text, std::string_view prefix) {
    return text.size() >= prefix.size() &&
           text.substr(0, prefix.size()) == prefix;
}

bool starts_with(const std::string& text, char prefix) {
    return !text.empty() && text.front() == prefix;
}

bool starts_with(const char* text, const char* prefix) {
    return std::string_view(text).substr(0, std::strlen(prefix)) == prefix;
}

Same function name, different parameter types and semantics. These overloaded starts_with functions are real examples of polymorphism in C++: function overloading that keep your API ergonomic while still being type-aware.

The C++ standard library itself uses similar patterns. For instance, std::to_string is overloaded for multiple numeric types, and many container constructors are overloaded to accept different iterator ranges or counts. You can explore those overloads in the C++ reference at cppreference.com, which is widely used in both industry and academia despite not being a .gov or .edu domain.


Numeric utilities: overloaded functions for different numeric types

Numeric code often needs to work with int, long long, float, double, and sometimes long double. One of the best examples of polymorphism in C++: function overloading in this space is a family of average functions.

#include <vector>

double average(const std::vector<int>& values) {
    if (values.empty()) return 0.0;
    long long sum = 0;
    for (int v : values) sum += v;
    return static_cast<double>(sum) / values.size();
}

double average(const std::vector<double>& values) {
    if (values.empty()) return 0.0;
    double sum = 0.0;
    for (double v : values) sum += v;
    return sum / values.size();
}

// Overload for raw arrays

double average(const double* data, std::size_t size) {
    if (size == 0) return 0.0;
    double sum = 0.0;
    for (std::size_t i = 0; i < size; ++i) sum += data[i];
    return sum / size;
}

Call sites stay simple:

std::vector<int> counts = {1, 2, 3};
std::vector<double> temps = {72.5, 73.0, 71.8};

double avgCount = average(counts);   // int overload

double avgTemp  = average(temps);    // double overload

The examples of polymorphism in C++: function overloading here are intentionally straightforward, but they scale up directly to numeric libraries and analytics code.


Constructors and operator overloading as examples of polymorphism in C++

Function overloading isn’t limited to free functions. Constructors and operators are also overloaded in most serious C++ codebases.

Overloaded constructors

#include <string>

class User {
public:
    User(std::string name)
        : name_(std::move(name)), age_(0) {}

    User(std::string name, int age)
        : name_(std::move(name)), age_(age) {}

    User(const char* name)
        : name_(name), age_(0) {}

private:
    std::string name_;
    int age_;
};

int main() {
    User u1("Alice");          // const char* overload
    User u2(std::string("Bob")); // std::string overload
    User u3("Carol", 30);      // (std::string, int) overload
}

All three constructors are named User. This is another clean example of polymorphism in C++: function overloading that makes object creation flexible without forcing callers into awkward conversions.

Overloaded operators

struct Vec2 {
    double x, y;
};

Vec2 operator+(const Vec2& a, const Vec2& b) {
    return {a.x + b.x, a.y + b.y};
}

Vec2 operator+(const Vec2& v, double s) {
    return {v.x + s, v.y + s};
}

Vec2 operator+(double s, const Vec2& v) {
    return v + s; // reuse the other overload
}

Here, operator+ is overloaded to handle vector–vector addition and vector–scalar addition. This is still function overloading under the hood, just with operator syntax.


2024–2025 perspective: overloading with modern C++ features

Modern C++ (C++20, C++23, and the upcoming C++26) hasn’t made function overloading obsolete; it’s made it more powerful when combined with concepts and constexpr.

Overloading and concepts

Concepts let you constrain template overloads in a way that feels like strongly typed function overloading.

#include <concepts>
#include <string>

void print_value(std::integral auto value) {
    std::cout << "integral: " << value << '\n';
}

void print_value(std::floating_point auto value) {
    std::cout << "floating: " << value << '\n';
}

void print_value(const std::string& value) {
    std::cout << "string: " << value << '\n';
}

The compiler still performs overload resolution, but now the overloads themselves are constrained by concepts. This is a modern extension of the same idea you’ve seen in earlier examples of polymorphism in C++: function overloading.

Overloading and constexpr

constexpr functions can also be overloaded, giving you compile-time polymorphism that works in constant expressions.

constexpr int square(int x) { return x * x; }
constexpr double square(double x) { return x * x; }

constexpr int a = square(5);      // uses int overload at compile time
constexpr double b = square(2.5); // uses double overload at compile time

The C++ standardization process, documented by the ISO committee and summarized in places like the C++ Core Guidelines, continues to encourage clear, type-safe APIs. Well-designed function overloading fits neatly into that picture.


Common pitfalls when using function overloading

All these examples of polymorphism in C++: function overloading are great, but there are traps that bite even experienced developers.

Ambiguous overloads

If two overloads are equally good matches, you get a compile-time error.

void foo(int x);
void foo(long x);

foo(10); // might be ambiguous depending on platform

On some platforms, int and long may be the same size, making this call ambiguous. The fix is usually to avoid “near-duplicate” overloads or to provide an exact match.

Overloading vs. default parameters

Default parameters and overloading can interact in surprising ways.

void connect(const std::string& host, int port = 80);
void connect(const std::string& url);

connect("example.com"); // which one?

Depending on how the compiler parses this, you can get ambiguity. In practice, you want your examples of polymorphism in C++: function overloading to be obvious to both humans and compilers. If a call site looks confusing, refactor.

Overloading on const and references

Overloading on const and reference qualifiers can be powerful, but it’s easy to overdo it:

void process(int& x);
void process(const int& x);

These two overloads behave very differently in terms of what they can modify. Use such patterns sparingly and document them well.

For deeper background on type systems and overloading, university course notes from institutions like MIT OpenCourseWare and Harvard’s CS courses provide solid conceptual grounding, even though they aren’t C++-only resources.


FAQ: short answers and more examples

What are some real-world examples of polymorphism in C++: function overloading?

Real examples include overloaded logging functions (log(int), log(double), log(const std::string&)), geometry helpers (area(const Circle&), area(const Rectangle&)), numeric utilities (average(const std::vector<int>&), average(const std::vector<double>&)), overloaded constructors (like the User example), and math helpers such as clamp or square for different numeric types.

How is function overloading different from virtual polymorphism?

Function overloading is resolved at compile time based on the static types of arguments. Virtual polymorphism (using virtual functions and inheritance) is resolved at runtime based on the dynamic type of the object. Both are forms of polymorphism in C++, but the examples of polymorphism in C++: function overloading you’ve seen here are all compile-time decisions.

Can I overload functions across different namespaces or classes?

Yes. Overloads must share the same scope to participate in overload resolution together. Overloads in different namespaces or as member vs. non-member functions can coexist, but the set of candidates depends on lookup rules. That’s why you often see free function overloads like begin() and end() in the std namespace, complementing member functions.

Yes, when used thoughtfully. Modern C++ emphasizes clear, type-safe interfaces, and many of the best examples of polymorphism in C++: function overloading come from the standard library itself. Just avoid ambiguous overloads, document behavior clearly, and consider concepts and templates when you need more generic behavior.

Do templates replace the need for function overloading?

Not really. Templates and overloading often work together. You might have a primary template and a few non-template overloads for performance-critical or special-case behavior. The examples of polymorphism in C++: function overloading we walked through can all be extended with templates if you need more generality, but the core idea—multiple functions with the same name and different parameter lists—remains the same.

Explore More C++ Code Snippets

Discover more examples and insights in this category.

View All C++ Code Snippets