C++ Function Overloading: Rules, Ambiguity, and Name Mangling

C++ Function Overloading: Rules, Ambiguity, and Name Mangling

이 글의 핵심

Same name, different parameters: how overload resolution works, why return type alone cannot overload, and how to avoid ambiguous calls.

What is function overloading?

Same name, different parameters—multiple functions share an identifier.

int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

int add(int a, int b, int c) {
    return a + b + c;
}

int main() {
    std::cout << add(1, 2) << std::endl;        // 3 (int)
    std::cout << add(1.5, 2.5) << std::endl;    // 4.0 (double)
    std::cout << add(1, 2, 3) << std::endl;     // 6 (three args)
}

Overloading rules

// ✅ Different arity
void func(int x);
void func(int x, int y);

// ✅ Different parameter types
void func(int x);
void func(double x);

// ✅ const differences (pointer/reference)
void func(int* ptr);
void func(const int* ptr);

// ❌ Return type only (not allowed)
int func(int x);
// double func(int x);  // error

Practical examples

Example 1: Print overloads

#include <iostream>
#include <vector>

void print(int x) {
    std::cout << "int: " << x << std::endl;
}

void print(double x) {
    std::cout << "double: " << x << std::endl;
}

void print(const std::string& s) {
    std::cout << "string: " << s << std::endl;
}

void print(const std::vector<int>& vec) {
    std::cout << "vector: ";
    for (int x : vec) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
}

int main() {
    print(42);
    print(3.14);
    print(std::string("Hello"));
    print(std::vector<int>{1, 2, 3});
}

Example 2: Max functions

int max(int a, int b) {
    return a > b ? a : b;
}

double max(double a, double b) {
    return a > b ? a : b;
}

int max(int a, int b, int c) {
    return max(max(a, b), c);
}

template<typename T>
T max(const std::vector<T>& vec) {
    if (vec.empty()) {
        throw std::invalid_argument("empty vector");
    }
    
    T maxVal = vec[0];
    for (const auto& val : vec) {
        if (val > maxVal) {
            maxVal = val;
        }
    }
    return maxVal;
}

int main() {
    std::cout << max(10, 20) << std::endl;
    std::cout << max(1.5, 2.5) << std::endl;
    std::cout << max(1, 2, 3) << std::endl;
    
    std::vector<int> nums = {5, 2, 8, 1, 9};
    std::cout << max(nums) << std::endl;
}

Example 3: Constructor overloading

class String {
private:
    char* data;
    size_t length;
    
public:
    String() : data(nullptr), length(0) {}
    
    String(const char* str) {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
    }
    
    String(char c, size_t count) {
        length = count;
        data = new char[length + 1];
        memset(data, c, length);
        data[length] = '\0';
    }
    
    String(const String& other) {
        length = other.length;
        data = new char[length + 1];
        strcpy(data, other.data);
    }
    
    ~String() {
        delete[] data;
    }
};

int main() {
    String s1;
    String s2("Hello");
    String s3('*', 10);
    String s4(s2);
}

Example 4: Search overloads

#include <vector>
#include <string>

int find(const std::vector<int>& vec, int target) {
    for (size_t i = 0; i < vec.size(); i++) {
        if (vec[i] == target) {
            return static_cast<int>(i);
        }
    }
    return -1;
}

int find(const std::vector<std::string>& vec, const std::string& target) {
    for (size_t i = 0; i < vec.size(); i++) {
        if (vec[i] == target) {
            return static_cast<int>(i);
        }
    }
    return -1;
}

template<typename T, typename Predicate>
int find(const std::vector<T>& vec, Predicate pred) {
    for (size_t i = 0; i < vec.size(); i++) {
        if (pred(vec[i])) {
            return static_cast<int>(i);
        }
    }
    return -1;
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::cout << find(numbers, 3) << std::endl;
    
    std::vector<std::string> words = {"apple", "banana", "cherry"};
    std::cout << find(words, std::string("banana")) << std::endl;
    
    std::cout << find(numbers, [](int x) { return x > 3; }) << std::endl;
}

const overloading

class Array {
private:
    int data[10];
    
public:
    int& operator[](size_t index) {
        return data[index];
    }
    
    const int& operator[](size_t index) const {
        return data[index];
    }
};

int main() {
    Array arr;
    arr[0] = 10;
    
    const Array& constArr = arr;
    int x = constArr[0];
}

Common pitfalls

Pitfall 1: Ambiguous calls

void func(int x) {
    std::cout << "int" << std::endl;
}

void func(double x) {
    std::cout << "double" << std::endl;
}

int main() {
    func(10);
    func(3.14);
    // func(10.0f);  // ambiguous: float -> int or double?
    
    func(static_cast<int>(10.0f));
    func(static_cast<double>(10.0f));
}

Pitfall 2: Default arguments vs overloads

// ❌ Ambiguous
void func(int x) {
    std::cout << "one arg" << std::endl;
}

void func(int x, int y = 0) {
    std::cout << "two args" << std::endl;
}

int main() {
    // func(10);  // error: ambiguous
    func(10, 20);
}

// ✅ Different names
void func1(int x);
void func2(int x, int y = 0);

Pitfall 3: Pointer vs array parameters

void func(int* ptr) {
    std::cout << "pointer" << std::endl;
}

void func(int arr[]) {
    std::cout << "array" << std::endl;
}

// Error: same signature (array decays to pointer)

Pitfall 4: Return type only

int getValue() {
    return 42;
}

// double getValue() { return 3.14; }  // error

int getIntValue() { return 42; }
double getDoubleValue() { return 3.14; }

Overload resolution sketch

void func(int x) { std::cout << "int" << std::endl; }
void func(double x) { std::cout << "double" << std::endl; }
void func(const std::string& s) { std::cout << "string" << std::endl; }

int main() {
    func(10);
    func(3.14);
    func("hello");
    
    char c = 'A';
    func(c);
    
    short s = 10;
    func(s);
}

FAQ

Q1: When to use overloading?

A:

  • Same operation, different types
  • Different arity
  • Convenience overloads

Q2: Overloading vs templates?

A:

  • Overloading: type-specific implementations
  • Templates: same logic, generic

Q3: Performance impact?

A: None at runtime; resolved at compile time.

Q4: Overload by return type?

A: Not allowed; distinguish by parameters.

Q5: Ambiguous calls?

A: Use explicit casts or different names.

Q6: Learning resources?

A:

  • Effective C++
  • cppreference.com
  • C++ Primer

  • C++ Default Arguments
  • C++ Functions: Complete Beginner Guide
  • C++ Name Mangling

Practical tips

Debugging

  • Compiler warnings first.
  • Minimal repro cases.

Performance

  • Profile, don’t guess.

Code review

  • Team conventions.

Practical checklist

Before coding

  • Right fit?
  • Maintainable?
  • Performance?

While coding

  • Warnings?
  • Edge cases?
  • Errors?

During review

  • Clear?
  • Tests?
  • Docs?

C++, overloading, function, resolution, templates


  • C++ name hiding