25.06.05 (2) - 템플릿

김영하·2025년 6월 5일

C++

목록 보기
20/32

템플릿

어떻게 하면 프로그래밍을 좀 더 쉽게 할 수 있는가.

가능한 한 현실 세계와 동일한 수준으로 구현하는 것.
세부사항을 과하게 고민하지 않을 수 있게 만드는 것.

예를 들어 자료형(data type)에 관해 생각해 보면,
간단한 계산기가 있다고 생각했을 때
코드에서는 데이터타입이 존재하기 때문에
int 간의 연산과 float,double 간의 연산이 서로 다르게 처리된다.

하지만 후술할 "템플릿" 문법을 사용하면:
다양한 자료형에 유연하게 대응하는
"일반화된 코드"를 작성할 수 있다.


함수 오버로딩(function overloading)

간단하게 설명하면
'이름이 같은 함수'가 있을 수 있다는 개념인데,

C++ 에서는 함수들을
이름과 매개변수, (반환타입) 정도로 구분하는데

단, 함수의 반환형 만으로는 오버로딩이 성립하지 않음!

위 3가지 정도의 판단재료를 통해
내부적으로 고유한 이름을 만들어 함수를 구분한다 = "네임 맹글링"

따라서 이러한 점만 유의한다면 동일한 이름의 함수를 복수 정의할 수 있다.

매개변수에 신경을 써준다면.

함수 오버로딩을 제대로 적용하려면
이름이 같아도 각 함수가 '명확히' 구분되어야 한다:
1) 매개변수 타입이 다른 경우 ex) void func(int a); void func(double b);
2) 매개변수의 개수가 다른 경우 ex) int func(x, y); int func(x,y,z);


함수 오버로딩 호출

앞서 말했듯 "오버로딩"이 적용되려면
각 함수들의 구분이 "명확해야" 하는데
이 명확성이 부족하면 컴파일러에서 애매모호성 오류가 발생한다:

1) 타입변환이 가능한 파라미터로 인해
두개 이상의 오버로딩된 함수가 호출 후보가 되는 경우

// 목적: 타입 변환 가능한 매개변수로 인해 애매모호성이 발생하는 예시
#include <iostream>
using namespace std;

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

void print(long a) {
    cout << "long: " << a << endl;
}

int main() {
    // print(10);  // int는 long과 double 모두로 변환 가능하므로 애매모호
    return 0;
}

/*
출력결과 (컴파일 에러):
error: call of overloaded 'print(int)' is ambiguous
*/

2) 디폴트 매개변수로 인해 함수 호출 형태가 중복되는 경우

// 목적: 디폴트 매개변수로 인해 호출 형태가 중복되는 경우
#include <iostream>
using namespace std;

void display(int a, int b = 5) { // b값을 원줘도 5로 기본세팅
    cout << a << ", " << b << endl;
}

void display(int a) {
    cout << a << endl;
}

int main() {
    // display(10); // 디폴트 매개변수로 인해 두 함수 모두 호출 가능하므로 애매모호
    // display(a 10) | display(a 10 , b=5) 둘다 가능
    return 0;
}

/*
출력결과 (컴파일 에러):
error: call of overloaded 'display(int)' is ambiguous
*/

3) 매개변수의 타입만
'포인터'와 '배열'로 다른 경우

// 목적: 매개변수의 타입이 포인터와 배열일 때 애매모호성 발생 예시
#include <iostream>
using namespace std;

void print(int* arr) {
    cout << "포인터 호출됨" << endl;
}

void print(int arr[]) {
    cout << "배열 호출됨" << endl;
}

int main() {
    int data[3] = {1, 2, 3};
    // print(data); // (포인터와 배열은 같은 타입으로 취급되어 애매모호)
    // 배열의 이름 data는 data 배열의 주소값을 가져기 때문에
    // 컴파일러가 구분을 못하는 게 이유 아닐까
    return 0;
}

/*
출력결과 (컴파일 에러):
error: redefinition of 'void print(int*)'
배열과 포인터는 매개변수로 구분되지 않음
*/

4) 함수의 반환 타입만 다른 경우

// 목적: 반환 타입만 다른 함수 오버로딩으로 애매모호성 발생
#include <iostream>
using namespace std;

// int getValue() {
//     return 10;
// }

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

int main() {
    // cout << getValue(); // 반환 타입만으로는 함수를 구별할 수 없으므로 애매모호
    // 반환 타입은 '결과'인거고
    // 과정에서 둘 중에 뭘 불러야 할지 알 방법이 없으니까
    return 0;
}

/*
출력결과 (컴파일 에러):
error: functions that differ only in their return type cannot be overloaded
// 반환타입만 달라서는 오버로딩 안된다는 친절한 메시지
*/

함수 오버로딩 순서

그러면 실제로 이렇게
오버로딩이 적용된 함수들 중에서
어떤 함수가 먼저 선택되는가?

1) 매개변수 타입이 정확히 일치

// 목적: 정확한 매개변수 타입이 우선적으로 선택됨을 학습
#include <iostream>
using namespace std;

void print(int a) {
    cout << "정확한 타입(int) 호출됨" << endl;
}

void print(double a) {
    cout << "double 타입 호출됨" << endl;
}

int main() {
    print(10); // 정확한 int 타입 일치
    return 0;
}

// 출력결과:
// 정확한 타입(int) 호출됨

2) 값이 손실되지 않는 방향으로 타입을 변환 = "승격"

  • char or shortint
  • floatdouble
  • boolint
// 목적: 정확한 타입이 없으면 타입 승격 변환이 우선적으로 선택됨을 학습
#include <iostream>
using namespace std;

void print(int a) {
    cout << "int 타입 호출됨" << endl;
}

void print(char a) {
    cout << "char 타입 호출됨" << endl;
}

int main() {
    short s = 10;
    print(s);  // short에서 int로 승격 변환
    return 0;
}

// 출력결과:
// int 타입 호출됨

short 는 2바이트 크기로,
int 보다 작은 범위 정수 (2^16, -32768~32767 범위)

3) 표준 타입 변환
: 승격보다 좀더 광범위한 변환으로, 값이 손실될 수 있음

// 목적: double 값을 오버로딩된 int 또는 string 함수에 전달할 때, 변환 우선순위 확인
#include <iostream>
#include <string>
using namespace std;

void print(int a) {
    cout << "int 버전 호출됨: " << a << endl;
}

void print(string a) {
    cout << "string 버전 호출됨: " << a << endl;
}

int main() {
    double num = 10.99;
    
    print(num); // double → int 변환이 발생하여 int 버전 호출
    return 0;
}

// 출력결과:
// int 버전 호출됨: 10 // 0.99 는 손실

4) 사용자 정의 타입 변환:
클래스 타입의 변환 함수나 생성자 등을 통해 이뤄지는 변환
이 경우는 조금 복잡해서 예시 코드 정도만.

// 목적: 표준 변환이 불가능하면 사용자 정의 변환이 호출됨을 학습
#include <iostream>
using namespace std;

class MyNumber {
public:
    operator int() const { return 42; }
};

void print(int a) {
    cout << "int 타입 호출됨, 값: " << a << endl;
}

void print(double a) {
    cout << "double 타입 호출됨, 값: " << a << endl;
}

int main() {
    MyNumber num;
    print(num);  // 사용자 정의 변환을 통해 MyNumber→int로 변환됨
    return 0;
}

// 출력결과:
// int 타입 호출됨, 값: 42

operator int() const
클래스 객체를 int 타입으로 변환하는 사용자 정의 변환 연산자.
const는 이 함수가 객체의 상태를 변경하지 않는다는 것



템플릿

타입에 관계없이 "일반화된 코드"를 만들기 위한 문법
template <typename T> 해주고
그다음 함수들을 정의

template <typename T> 이 기본 구문의 의미는
나중에 어떤 타입이 올지 모르겠으나 그 타입을 T 라고 부르겠다는 뜻이다
ex) 두 수를 더하는 add 함수
add( a, b) => T add(T a, T b)

// 목적: 함수 템플릿을 이용해 두 값을 더하는 일반화된 함수 작성하기
#include <iostream>
using namespace std;

template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    cout << add(3, 5) << endl;        // 정수 더하기
    cout << add(2.5, 4.1) << endl;    // 실수 더하기
    return 0;
}

// 출력결과:
// 8
// 6.6
// 목적: 함수 템플릿을 이용하여 일반화된 최대값 찾기
#include <iostream>
#include <string>
using namespace std;

template <typename T>
T getMax(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << getMax(10, 20) << endl;          // 정수 비교
    cout << getMax(3.5, 2.7) << endl;        // 실수 비교
    cout << getMax(string("apple"), string("banana")) << endl; // 문자열 비교
    return 0;
}

// 출력결과:
// 20
// 3.5
// banana
// 목적: 함수 템플릿으로 배열 원소 출력 일반화하기
#include <iostream>
using namespace std;

template <typename T>
void printArray(T arr[], int size) { 
// 일반화하고 싶은 부분만 T로 쓴다
// void 부분이랑 int size 부분은 명확해서 T로 쓸 필요가 없음
    for (int i = 0; i < size; i++)
        cout << arr[i] << " ";
    cout << endl;
}

int main() {
    int intArr[3] = {1, 2, 3};
    char charArr[4] = {'A', 'B', 'C', 'D'};

    printArray(intArr, 3);   // 정수 배열 출력
    printArray(charArr, 4);  // 문자 배열 출력
    return 0;
}

// 출력결과:
// 1 2 3
// A B C D
#include <iostream>
using namespace std;

template <typename T>
void swapValues(T &a, T &b) { 
				// 실제로 바꿔야 하기 때문에 & 사용
                // 기본적으로 함수의 인자로 들어가면 복사본이 들어가는 거라 원본은 안바뀜
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    swapValues(x, y);
    cout << "Swapped int values: x = " << x << ", y = " << y << endl;

    double p = 3.14, q = 2.71;
    swapValues(p, q);
    cout << "Swapped double values: p = " << p << ", q = " << q << endl;

    return 0;
}

#include <iostream>
using namespace std;

template <typename T>
bool isEqual(T a, T b) {
    return a == b;
}

int main() {
    cout << boolalpha; // true/false 출력 활성화
    cout << "Are 10 and 10 equal? " << isEqual(10, 10) << endl;       
    // 정수 비교
    cout << "Are 3.5 and 2.7 equal? " << isEqual(3.5, 2.7) << endl;   
    // 실수 비교
    cout << "Are 'a' and 'b' equal? " << isEqual('a', 'b') << endl;   
    // 문자 비교

    return 0;
}

템플릿 클래스

위에서 설명한 함수들 뿐만 아니라,
클래스도 마찬가지로 이 "템플릿"을 활용해 일반화할 수 있다

예를 들어, 배열에 원소를 추가/삭제 하는 기능을 가진 클래스를 정의할 때,
템플릿을 사용해 다양한 데이터타입의 배열에 대응하도록 일반화해줄 수 있다

// 목적: 클래스 템플릿으로 배열을 일반화하여 원소 추가 및 삭제 기능 구현하기
#include <iostream>
using namespace std;

template <typename T> // 템플릿 적용
class Array {
    T data[100]; // 일반화 배열
    int size;
public:
    Array() : size(0) {}

    void add(const T& element) {
        if(size < 100)
            data[size++] = element;
    }

    void remove() {
        if(size > 0)
            size--; // size만 빼주면 나중에 추가할 때 덮어쓰기도 알아서 될 거고
    }

    void print() {
        for(int i = 0; i < size; i++) // 여기서 size 크기까지만 출력하기 때문에
        // 출력도 안됨
            cout << data[i] << " ";
        cout << endl;
    }
};

int main() {
    Array<int> arr; // 정수형 배열 생성
    arr.add(10);
    arr.add(20);
    arr.add(30);
    arr.print();

    arr.remove();
    arr.print();
    return 0;
}

// 출력결과:
// 10 20 30
// 10 20

const T& element :
불특정 타입(T) 의 element 값을 참조(&), 변경할 수 없게함(const)

추후에 C++ 에서 "STL" 이란 걸 배우게 될텐데
이게 = 표준 '템플릿' 라이브러리 를 의미한다!
나중에 STL 을 잘 쓰기 위한 기초 공부

profile
내일배움캠프 Unreal 3기

0개의 댓글