Eat Study Love

먹고 공부하고 사랑하라

SW 만학도/C++ & Algorithm

Function Overloading and Templates

eatplaylove 2025. 2. 1. 16:14

금번 C++ 공부에선, Function Overloading 과 Templates 부분을 공부하고 관련 내용을 실습해보겠다.

실습자료는 아래와 같다.

 

9_exercise.h
0.01MB
9_test_cases.cpp
0.01MB

 

기본적으로 Function의 parameter에는 defulat 값을 넣을 수 있다.

int divide( int a, int b = 2 ) {
	return a/b;
}

 

Function의 Overloading이란, Function들끼리 같은 이름을 Share하는데 Parameter type에 따라서 Return이 달라지는 것이다. 이 때, Return type만 가지고는 Function들 끼리 overload 할 수 없다.

 

다음은, Function Overload의 예시다.

 

보면, mySwap이라는 함수가 있는데, 이름이 다 같아도 input data의 type에 따라서 동작이 다 다르다.

 

이것이 가능한 이유는, Compiler가 함수들의 이름을 Managing 할 때, 각 function의 parameter type, 개수까지 고려해서 각 각을 Unique하게 관리하기 때문이다.

 

하지만, 위의 예시를 보면 같은 Functionality에 대해 data type별로 Function을 implement 해 놓았다.

 

이는 비효율적이며, 코드 구현/유지에 적잖은 에러를 초래한다.

 

그래서 등장한 개념이 Function Templates 다.

Template을 이용하면 어떤 Data type과도 function은 호응할 수 있다.

template <typename T>
void function(T &x, T&y ){
	T temp = x;
    x = y;
    y = temp;
}
// SWAP 함수 using Template

 

Template이 사용된 Function을 Call할 때는, Function 에 < > 꺽쇠를 이용해서 Data type을 명시해줘야 한다.

int a = 5;
int b = 6;
mySwap<int>(a,b); // 이런식으로 < int > 명시

 

그런데, 위와 같은 경우는 명확히 a, b가 int이기 때문에 그냥 mySwap(a,b)를 써도 Compiler가 알아서 추론하여 동작을 시킬 수 있다.

 

다만, Compiler가 알아서 추론하기 힘든 Case의 경우 당연히 Template argument를 특정해줘야 한다.

 

그리고, Function은 복수의 Template을 받을 수도 있다.

 

Template을 Function의 Argument처럼 쓰는 경우도 있는데, 이를 Non-type template argument라고 한다.

 

template <int N>
int power(int base){
	int ans = 1;
    for(int i=0;i<N;i++){
    	ans *= base;
    }
    return ans;
}

int main(){
	std::cout << " 2 ^ 3 = " << power<3>(2) << std::endl;
    // 이런 식으로 Template의 N이 argument로 작용
}

 

이것의 장점은, Compiler가 Value를 compile time에 알 수 있어서 code 최적화 및 Error 탐색을 빨리 할 수 있다.

이것의 단점은, Template Value(위에서 N)을 사용할 때마다 특정해줘야 하고, 이 value로 인해 compiled binary크기가 커진다.

 

지금까지 배운 내용을 정리하면 아래와 같다.

 

이제 이 부분을 실습해보자!

 

본 실습을 통해서 Template 개념을 이해할 수 있다.

# exercise.h 파일
#ifndef EXERCISE_H
#define EXERCISE_H
#include <vector>
#include <stdexcept>
#include <iostream>
#include <string>
#include <sstream>

using namespace std;

/*=============================================== TODO 1 ===============================================
    1. Instruction:
        - Implement a function `addVectors`.
    2. Function Definition:
        - A template function that performs element-wise addition of two vectors of type T.
        - The function should be able to take vectors of any data type (e.g., int, float, double)
    3. Parameters:
        - First parameter: A constant reference to v1 of type vector<T>. This input vector remains unmodified.
        - Second parameter: A constant reference to v2 of type vector<T>. This input vector remains unmodified.
    4. Return:
        - It returns a new vector of type vector<T> containing the element-wise sum of `v1` and `v2`.
           Each element of the resulting vector is the sum of the corresponding elements from the input vectors.
    5. Preconditions:
        - Both input vectors `v1` and `v2` must be of the same size.
        - Hint: Use `throw invalid_argument("Vectors must have the same size.");`
=======================================================================================================*/
// Your code here:
template<typename T>
vector<T> addVectors(const vector<T> &v1,const vector<T> &v2){
    // Throw 적용
    if(v1.size()!=v2.size()){
        throw invalid_argument("Vectors must have the same size~~");
    }
    int n = v1.size();
    vector<T> temp;
    for(int i=0;i<n;i++){
        temp.push_back(v1[i]+v2[i]);
    }
    return temp;
}

/*=============================================== TODO 2 ===============================================
    1. Instruction:
        - Implement a function `dotProduct'.
    2. Function Definition:
        - A template function that computes the dot product of two vectors of type T.
        - The function should take vectors of any data type (e.g., int, float, double)
    3. Parameters:
        - First parameter: A constant reference to `v1` of type vector<T>. This is the first input vector, and it remains unmodified.
        - Second parameter: A constant reference to `v2` of type vector<T>. This is the second input vector, and it remains unmodified.
    4. Return:
        - It returns a value of type T, representing the dot product of the two input vectors.
          The dot product is computed by summing the products of corresponding elements from `v1` and `v2`.
    5. Preconditions:
        - Both input vectors `v1` and `v2` must be of the same size.
=======================================================================================================*/
// Your code here:
template<typename A>
A dotProduct(vector<A> &v1,vector<A> &v2){
    if(v1.size()!=v2.size()){
        throw invalid_argument("Vectors must have the same size!!");
    }
    int n = v1.size();
    A temp = 0;
    for(int i=0;i<n;i++){
        temp += (v1[i]*v2[i]);
    }
    return temp;
}

/*==================================== Do not modify this section =====================================*/
template<typename T>
vector<T> inputVector(const string& vectorName) {
    int size;
    vector<T> vec;
    cout << "Please enter the size of " << vectorName << ": ";
    cin >> size;
    if (size <= 0) {
        throw invalid_argument("The size of vector must be greater than 0");
    }
    cout << "Please enter the elements of " << vectorName << " (separate the elements with spaces and press Enter when you are done)." << endl;
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
    string line;
    getline(cin, line);
    istringstream stream(line);
    
    for (int i = 0; i < size; ++i) {
        string valueStr;
        if (!(stream >> valueStr)) {
            throw invalid_argument("Invalid input detected! Expected type does not match.");
        }

        if constexpr (is_same<T, int>::value) {
            if (valueStr.find('.') != string::npos) {
                throw invalid_argument("Invalid input detected! Expected an integer, but received a floating-point number.");
            }

            char* end;
            long value = strtol(valueStr.c_str(), &end, 10);
            if (*end != '\0' || value > numeric_limits<int>::max() || value < numeric_limits<int>::min()) {
                throw invalid_argument("Invalid input detected! Expected an integer.");
            }

            vec.push_back(static_cast<int>(value));
        } else {
            istringstream valueStream(valueStr);
            T value;
            if (!(valueStream >> value) || !valueStream.eof()) {
                throw invalid_argument("Invalid input detected! Expected type does not match.");
            }
            vec.push_back(value);
        }
    }
    T extra;
    if (stream >> extra) {
        throw invalid_argument("Too many elements entered for " + vectorName + "! Expected " + to_string(size) + " values.");
    }
    return vec;
}
/*=====================================================================================================*/


template<typename T>
void processVectors() {
    vector<T> v1 = inputVector<T>("vector1");
    vector<T> v2 = inputVector<T>("vector2");

    /*=============================================== TODO 3 ===============================================
        1. Instruction:
            - Use the `addVectors` function to compute the element-wise sum of `v1` and `v2`.
            - Assign the result to a variable of type `vector<T>` named `result`.
            - Ensure that the return type of `addVectors` matches the type `vector<T>`.
    ======================================================================================================*/
    // your code here:
    vector<T> result = addVectors(v1,v2);

    /*==================================== Do not modify this section =====================================*/
    cout << "\nResult of `addVectors`: ";
    for (int i = 0; i < v1.size(); ++i) {
        cout << result[i] << " ";
    }
    cout << endl;

    /*============================================= TODO 4 =============================================
        1. Instruction:
            - Use the `dotProduct` function to compute the dot product of `v1` and `v2`.
            - Display the result of the `dotProduct` function using `cout` in the following format: "Result of `dotProduct`: ".
    ===================================================================================================*/
    // your code here:
    // T result2 = dotProduct(v1,v2);
    cout << "Result of 'dotProduct': "<< dotProduct(v1,v2) <<endl;
    
}

#endif

 

# test case 파일
#include <iostream>
#include <vector>
#include <cmath>
#include "9_exercise.h"

using namespace std;

template<typename T>
bool compareVectors(const vector<T>& v1, const vector<T>& v2, T tolerance = static_cast<T>(1e-9)) {
    if (v1.size() != v2.size()) return false;
    for (size_t i = 0; i < v1.size(); ++i) {
        if constexpr (is_floating_point<T>::value) {
            if (abs(v1[i] - v2[i]) > tolerance) return false;
        } else {
            if (v1[i] != v2[i]) return false;
        }
    }
    return true;
}

bool runTests() {
    bool all_tests_passed = true;

    // Test case 1: Vectors of type int
    vector<int> v1_int = {1, 2, 3};
    vector<int> v2_int = {4, 5, 6};
    vector<int> expected_sum_int = {5, 7, 9};
    int expected_dot_product_int = 32;

    cout << "Test Case 1: int vectors\n";
    vector<int> result_int = addVectors(v1_int, v2_int);
    int dot_product_int = dotProduct(v1_int, v2_int);

    if (!compareVectors(result_int, expected_sum_int)) {
        cout << "Test Case 1 Failed: Incorrect vector sum.\n";
        all_tests_passed = false;
    } else if (dot_product_int != expected_dot_product_int) {
        cout << "Test Case 1 Failed: Incorrect dot product.\n";
        all_tests_passed = false;
    } else {
        cout << "Test Case 1 Passed.\n";
    }

    // Test case 2: Vectors of type float
    vector<float> v1_float = {1.1f, 2.2f, 3.3f};
    vector<float> v2_float = {4.4f, 5.5f, 6.6f};
    vector<float> expected_sum_float = {5.5f, 7.7f, 9.9f};
    float expected_dot_product_float = 38.72f;

    cout << "\nTest Case 2: float vectors\n";
    vector<float> result_float = addVectors(v1_float, v2_float);
    float dot_product_float = dotProduct(v1_float, v2_float);

    if (!compareVectors(result_float, expected_sum_float)) {
        cout << "Test Case 2 Failed: Incorrect vector sum.\n";
        all_tests_passed = false;
    } else if (abs(dot_product_float - expected_dot_product_float) > 1e-5) {
        cout << "Test Case 2 Failed: Incorrect dot product.\n";
        all_tests_passed = false;
    } else {
        cout << "Test Case 2 Passed.\n";
    }

    // Test case 3: Vectors of type double
    vector<double> v1_double = {1.1, 2.2, 3.3};
    vector<double> v2_double = {4.4, 5.5, 6.6};
    vector<double> expected_sum_double = {5.5, 7.7, 9.9};
    double expected_dot_product_double = 38.72;

    cout << "\nTest Case 3: double vectors\n";
    vector<double> result_double = addVectors(v1_double, v2_double);
    double dot_product_double = dotProduct(v1_double, v2_double);

    if (!compareVectors(result_double, expected_sum_double)) {
        cout << "Test Case 3 Failed: Incorrect vector sum.\n";
        all_tests_passed = false;
    } else if (abs(dot_product_double - expected_dot_product_double) > 1e-9) {
        cout << "Test Case 3 Failed: Incorrect dot product.\n";
        all_tests_passed = false;
    } else {
        cout << "Test Case 3 Passed.\n";
    }

    // Final test result summary
    if (all_tests_passed) {
        cout << "\nAll test cases passed.\n";
    } else {
        cout << "\nSome test cases failed.\n";
    }
    return all_tests_passed;
}


int main() {
    if (runTests()) {
        try {
            int type;

            cout << "\nDo the test yourself"<<endl;
            cout << "Choose type of the vectors (1: int, 2: float, 3: double): ";
            cin >> type;
            

            switch (type) {
                case 1:
                    /*============================================= TODO 5 =============================================
                        1. Instruction:
                        - Call the `processVectors` function with `int` as the template argument.
                    ====================================================================================================*/
                    // Your code here:
                    processVectors<int>();

                    break;
                case 2:
                    /*============================================= TODO 6 =============================================
                        1. Instruction:
                        - Call the `processVectors` function with `float` as the template argument.
                    ====================================================================================================*/
                    // Your code here:
                    processVectors<float>();
                    

                    break;
                case 3:
                    /*============================================= TODO 7 =============================================
                        1. Instruction:
                        - Call the `processVectors` function with `double` as the template argument.
                    ====================================================================================================*/
                    // Your code here:
                    processVectors<double>();
                    
                    
                    break;
                default:
                    cout << "잘못된 타입을 입력하셨습니다." << endl;
                    return 0;
            }
            } catch (const std::invalid_argument& e) {
            std::cerr << "Error: " << e.what() << std::endl;
            return 1;
        }
    } else {
        return 0;
    }
}