[C++] 오버로드와 템플릿

최윤서·2024년 7월 24일

Overloaded function

C++에서는 같은 이름을 가진 두 개의 함수가 존재 가능하다. 서로 다른 타입 혹은 갯수의 파라미터를 가지고 있는 한 두 개를 구분할 수 있다.

#include <iostream>
using namespace std;

int operate (int a, int b)
{
  return (a*b);
}

double operate (double a, double b)
{
  return (a/b);
}

int main ()
{
  int x=5,y=2;
  double n=5.0,m=2.0;
  cout << operate (x,y) << '\n';
  cout << operate (n,m) << '\n';
  return 0;
}

위 코드에서 두 함수는 operate라는 이름을 가지고 있다. 그러나 하나는 int타입을 파라미터로 가지고, 다른 하나는 double타입을 가지고 있다. 컴파일러는 이 경우 함수 호출 시에 넘겨진 타입을 기준으로 어떤 함수가 호출 된 것인지 구분한다.

이 예시에서, 두 함수는 서로 다른 내용을 가지고 있다는 걸 볼 수 있다. int 버전은 두 파라미터를 곱하지만 double 버전은 두 파라미터를 나눈다.

이렇게 하면 함수 이름은 같은데 기능이 달라서 매우 헷갈리기 때문에, 보통 오버로드 된 두 함수는 최소한 같은 기능을 가지고 있도록 하는 게 좋다. 가독성을 위해서라도!

이 때 오버로드 함수들끼리 리턴 타입을 기준으로 구분을 하지는 못한다. 최소한 파라미터 하나는 다른 타입을 가져야 구분할 수 있다.

Function templates

// overloaded functions
#include <iostream>
using namespace std;

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

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

int main ()
{
  cout << sum (10,20) << '\n';
  cout << sum (1.0,1.5) << '\n';
  return 0;
}

이 함수에서, 두 개의 오버로드 된 sum 함수들은 서로 다른 타입을 받지만 완전히 똑같은 몸통 부분을 가진다.

sum이라는 함수는 이름에서도 알 수 있듯, int, double, long long 뭐가 오든 똑같은 몸통을 가지게 될 가능성이 크다. 이러한 경우에 대비해서, C++은 제네릭 타입을 가진 함수를 정의할 수 있는 능력을 가지고 있다. 이것을 function templates라고 부른다.

function templates를 정의하는 것은 보통 함수를 정의하는 것이랑 똑같다. 그러나 함수를 쓰기 전에 template 키워드를 붙여주기만 하면 된다. 그리고 <> 안에 템플릿 파라미터를 써주면 된다.

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

template 파라미터는

위에서 'typename'은 typename이라는 것이 함수 정의 내에서 어디서든 사용될 수 있도록 해준다. 그냥 다른 타입들처럼, 이 typename은 파라미터로 쓰이거나, return 타입으로 쓰일 수도 있고 새로운 변수를 선언할 수도 있다. 단순히 다른 타입들을 '일반화 시켜서'사용하는 것이라고 생각하면 되고, 템플릿이 어떤 타입으로 사용될지 결정되는 순간에 결정된다고 보면 된다.

타입을 구체화시키는 것은 function template을 호출해서 특정 타입이나 값을 결정시키는 것이다. 보통 함수를 호출하는 것과 같이 호출되지만 <> 안에 있던 타입을 결정하면서 호출된다고 보면 된다.

x = sum<int>(10,20);

이 예시는 sum 함수를 호출하는데, generic, 즉 일반화 되어있던 타입을 int로 결정해서 호출하겠다는 뜻이다.

// function template
#include <iostream>
using namespace std;

template <class T>
T sum (T a, T b)
{
  T result;
  result = a + b;
  return result;
}

int main () {
  int i=5, j=6, k;
  double f=2.0, g=0.5, h;
  k=sum<int>(i,j);
  h=sum<double>(f,g);
  cout << k << '\n';
  cout << h << '\n';
  return 0;
  }

이 예시에서 T는 템플릿 파라미터의 이름이고, 우리는 sum을 두 번 호출한다.
이 때 컴파일러는 각각에 맞는 타입을 특정화 시키고 각각에 맞는 버전의 함수를 호출해서 사용했다고 볼 수 있다.

그리고 이 때 sum 함수 안에서 T는 지역변수를 선언하는 타입으로도 사용되었다. 따라서 result는 sum이 호출되는 순간 자동으로 그 타입을 특정화시켜서 갖게 된다.

제네릭 타입이 sum의 파라미터로 쓰인 경우, 컴파일러는 똑똑하게도 자동으로 무슨 타입을 쓰고 싶은지 알아볼 수 있다.

따라서,

k = sum<int> (i,j);

가 아니라

k = sum (i,j);

로 쓰기만 해도 된다. 대신 들어가는 값이 모호해지면 컴파일러가 혼란스러워하기 때문에 이 점을 주의해야 한다.

함수 템플릿 안에서는 여러개의 템플릿 매개변수가 있을 수 있고, 그럼에도 여전히 자동으로 추측은 가능하다.

#include <iostream>
using namespace std;

template <class T, class U>
bool are_equal (T a, U b)
{
  return (a==b);
}

int main ()
{
  if (are_equal(10,10.0))
    cout << "x and y are equal\n";
  else
    cout << "x and y are not equal\n";
  return 0;
}

이러한 코드가 있을 때,

are_equal(10,10.0)

are_equal<int,double>(10,10.0)

로 추론된다.

Non-type template arguments

// template arguments
#include <iostream>
using namespace std;

template <class T, int N>
T fixed_multiply (T val)
{
  return val * N;
}

int main() {
  std::cout << fixed_multiply<int,2>(10) << '\n';
  std::cout << fixed_multiply<int,3>(10) << '\n';
}

이 함수의 두 번째 매개변수 타입은 int이다. 템플릿 함수가 아니라 평범한 함수의 파라미터처럼 보이고, 실제로 그렇게 쓰인다.

그러나 한 가지 아주 큰 차이가 존재한다!
템플릿 파라미터의 값은 컴파일 될 때 구체화 되기 때문에 실행이 시작되는 런타임ㅁ까지는 int라는 것이 절대 전해지지 않는다.

main 함수에서는 두 가지 버전의 fixed_multiply가 호출되는데, 2를 곱하는 버전과 3을 곱하는 버전이다.

profile
일 잘 하고싶은 기개디자이너

0개의 댓글