C++/C++ 기초 플러스 6판(Book)

[C++] 함수의 활용

suppresswisely 2025. 3. 5. 20:39

C++ 인라인(inline)함수

일반 함수와 인라인 함수의 가장 큰 차이는 프로그래머가 코드를 어떻게 작성하느냐에 있는 것이 아니라, C++ 컴파일러가 코드를 프로그램에 어떻게 결함하느냐에 있다.

 

인라인 함수에서는 컴파일된 함수 코드가 프로그램의 다른 코드 안에 직접 삽입되어 있다. 이말은 컴파일러가 함수 호출을 그에 대응하는 함수 코드로 대체한다는 것을 의미한다. 인라인 코드를 이용하여 함수를 그 자리에서 처리하므로, 그 함수를 수행하기 위해 프로그램이 다른 주소로 점프했다가 돌아올 필요가 없다. 따라서 인라인 함수는 일반 함수보다 약간 빠르게 수행된다. 그러나 메모리 사용 측면에서는 인라인 함수가 일반 함수보다 불리하다. 어떤 프로그램에서 인라인 함수를 열 번 호출한다면 프로그램은 그 함수의 사본을 프로그램의 코드 안에 열 번이나 삽입해야 한다.

 

인라인 함수를 사용할 것인지 여부는 신중하게 결정해야 한다. 함수 코드 자체를 수행하는 데 걸리는 시간이, 함수 호출의 과정을 처리하는 데 걸리는 시간에 비해 매우 길다면, 전체적으로 절약되는 시건은 거의 없다. 함수 코드를 수행하는 데 걸리는 시간이 매우 짧을 경우에만 인라인 함수를 사용하는 것이 일반 함수를 사용하는 것보다 시간이 절약된다.

 

참조 변수의 생성

C와 C++는 변수의 주소를 나타내기 위해 & 기호를 사용한다. C++는 & 기호에 또 하나의 의미를 추가하여 참조 선언을 나타내게 하였다. 예를 들어, rodents를 변수 rats의 대용 이름으로 만들려면 다음과 같이 할 수 있다.

int rats;
int & rodents = rats; //rodents를 rats의 대용 이름으로 만든다.

 

여기에서는 &가 주소 연산자가 아니라, 데이터형 식별자의 일부로 사용된 것이다. 변수 선언에서 char *가 문자를 지시하는 포인터를 의미하는 것 처럼, int &는 int에 대한 참조를 의미한다.

 

함수 매개변수로서의 참조

참조는 주로 함수의 매개변수로 사용된다. 그것은 어떤 함수에서 사용하는 변수의 이름을 그 함수를 호출한 프로그램(호출 함수)에 있는 어떤 변수의 대용 이름으로 만든다. 이러한 방식으로 매개 변수를 전달하는 것을 참조로 전달이라 한다.

 

참조의 특성

참조 매개변수를 사용할 때에는 사용자가 반드시 알아야 할 몇 가지 흥미있는 일이 일어난다.

변수를 실제 매개변수로 사용해야 한다. 따라서 다음과 같은 코드는 잘못된 것이다.

double z = refcube(x + 3.0); // 컴파일 되지 않는다.

 

임시 변수, 참조 매개변수, const

C++는 실제 매개변수와 참조 매개변수가 일치하지 않을 때 임시 변수를 생성할 수 있다. 최근의 C++는 매개변수가 const 참조일 경우에만 이것을 허용한다. 이것은 새로운 제약이다. C++가 어떤 경우에 임시 변수를 생성하는지, 그리고 const 참조의 경우에만 임시 변수 생성을 허용하는 이유를 알아보자.

 

임시 변수는 언제 생성되는 것일까? 참조 매개변수가 const일 경우, 컴파일러는 다음과 같은 두 가지 상황에서 임시 변수를 생성한다.

  • 실제 매개변수가 올바른 데이터형이지만 lvalue가 아닐 때
  • 실제 매개변수가 잘못된 데이터형이지만 올바른 데이터형으로 변환할 수 있을떄

lvalue란 무엇인가? lavlue 매개변수는 참조가 가능한 데이터 객체이다. 예를 들어 변수, 배열의 원소, 구조체의 멤버, 참조 또는 역참조 포인터는 lvalue이다. 일반 상수(주소에 의해 표시되는 인용 구문과는 별개로)와 여러 개의 항으로 이루어진 표현식은 lvalue가 아니다. C에서 lvalue는 본래 대입문 왼편에 나타날 수 있는 독립체들을 의미했다. 그러나 그것은 const 변수가 소개되기 전의 일이다. 지금 일반 변수와 const 변수 모두 주소에 의해 접근이 가능하기 때문에 lvalue로 고려될 수 있다.

 

규칙이 지금만큼 까다롭지 않았던 초창기의 C++에서, 다음과 같이 했다면 무슨일이 일어날까?

void swapr(int & a, int & b)
{
    int temp;
    
    temp = a;
    a = b;
    b = temp;
}

long a = 3, b = 5;
swapr(a, b);

 

데이터형이 일치하지 않으므로, 컴파일러는 두 개의 int형 임시 변수를 만들고, 그것들을 3과 5로 각각 초기화한 후 임시 변수의 내용을 서로 교환한다. 그러므로 여기서 a와 b는 변경되지 않는다.

 

간단히 말해서, 참조 매개변수를 가진 함수의 목적이 매개변수로 전달되는 변수를 변경하는 것이라면, 임시 변수의 생성은 그 목적을 방해한다. 이것을 해결하는 방법은 이러한 상황에서는 임시 변수의 생성을 허용하지 않는 것이다. 최근의 C++ 표준은 그렇게 하고 있다.

 

참조가 const로 선언되어 있으면, C++는 필요할 때 임시 변수를 생성한다. 기본적으로, 형식 매개변수가 const 참조로 되어 있는 함수는, 데이터형이 일치하지 않는 실제 매개변수를 전달받았을 때, 정보를 값으로 전달받는 전통적인 방식을 따른다. 즉 임시 변수를 사용하므로 원본 데이터를 변경하지 않는다.

 

C++11은 rvalue 참조라고 부르는 두 번쨰 종류의 참조를 소개하고 있는데, rvalue를 참조할 때 사용할 수 있으며 &&를 사용하여 다음과 같이 선언한다.

double && rref = std::sgrt(36.00); // double &는허용되지 않는다.
double j = 15.0;

 

rvalue 참조는 라이브러리 디자이너로 하여금 특정 기능을 더욱 효율적으로 구현할 수 있도록 돕기 위하여 소개되었다.

 

가능하면 const를 사용하자

참조 매개변수를 상수 데이터에 대한 참조로 선언하는 이유는 다음과 같은 세 가지 이점이 있기 때문이다.

  • const를 사용하면, 실수로 데이터 변경을 일으키는 프로그래밍 에러를 막을 수 있다.
  • 원형에 const를 사용하면, 함수가 const와 const가 아닌 실제 매개변수를 모두 처리할 수 있지만, 원형에 const를 생략한 함수는 const가 아닌 데이터만 처리할 수 있다.
  • const 참조를 사용하면, 함수가 자신의 필요에 따라 임시 변수를 생성하여 사용할 수 있다.

따라서 가능하면 참조 형식 매개변수를 const로 선언하는 것이 좋다.

 

구조체에 대한 참조

참조는 C++의 사용자 정의 데이터형인 구조체나 클래스를 다루는 데 아주 유용하게 사용된다. 참조는 기본 built-in 데이터 타입보다는 주로 사용자 정의 데이터형에 사용하기 위해 도입된 것이다.

 

struct free_throws
{
    std::string name;
    int made;
    int attemps;
    float percent;
};

void set_pc(free_throws & ft); // 구조체에 대하여 참조를 사용한다.
void display(const free_throws & ft); // 구조체에 대하여 변경을 허용하지 않는다.

 

참조를 리턴할 때 주의할 점

참조를 리턴할 때 기억해야 할 가장 중요한 것은, 함수가 종료할 때 수명이 함께 끝나는 메모리 위치에 대한 참조를 리턴하지 않도록 조심하는 것이다. 다음과 같은 코드를 사용하지 말아야 한다.

const free_throws & clone2(free_throws & ft)
{
    free_throws newguy;
    newguy =ft;
    return newguy;
}

 

이 코드는 함수가 종료할 때 함께 사라질 운명인 임시 변수(newguy)에 대한 참조를 리턴하는 불행한 실수를 저지르고 있다. 마찬가지로, 그러한 임시 변수를 차지하는 포인터를 리턴하는 것도 피해야 한다.

 

이 문제를 피하는 가장 간단한 방법은, 함수에 매개변수로 전달된 참조를 리턴하는 것이다. 참조 매개변수는 호출 함수가 사용하는 데이터를 참조한다. 그러므로 리턴되는 참조도 동일한 그 데이터를 참조한다.

 

새로운 저장소를 생성하기 위해 new를 사용하는 두 번째 방법은 new를 사용하여 새로운 기억 공간을 만드는 것이다. new로 문자열을 저장할 공간을 만들고, 함수가 그 공간을 지시하는 포인터를 리턴하는 예제는 앞에서 많이 살펴보았다. 참조를 사용하여 이와 비슷한 일을 처리하는 방법은 다음과 같다.

const free_throws & clone(free_throws & ft)
{
    free_throw * pt;
    *pt = ft;
    return *pt;
}

 

그러면 이제 이 함수르 다음과 같은 방법으로 사용할 수 있다.

free_throws & jolly = clone(three);

 

이 구문은 jolly를 새로 만드는 구조체에 대한 참조를 만든다. 그러나 이러한 접근 방법에는 한 가지 문제가 있다. 메모리가 더 이쌍 필요 없게 되었을 때 new에 의해 대입된 메모리를 delete로 만드시 삭제해 주어야 한다. 그러나 clone() 호출이 new 호출을 안에 감추고 있으므로, 나중에 delete를 사용하는 것이 잊어버릴 가능성이 높다.

 

참조를 리턴할 때 왜 const를 사용하는가?

free_throws & accumulate(free_throws & target, const free_thros & source);
accumulate(dup, five) = four;

 

본인이 참조 리턴값을 사용하고 싶어하지만, accumulate()에 값을 대입하는 등의 행위를 허용하는 것은 원하지 않는다고 가정해 보자. 리턴형을 상수 참조로 만들어보자

const free_throws & accumulate(free_throws & target, const free_thros & source);

 

리턴형이 const이므로 변경이 불가능한 lvalue이다. 그러므로 대입이 허용되지 않는다.

accumulate(dup, five) = four; // const 참조 리턴을 허용하지 않음

 

이 프로그램에서 다른 함수를 부르는 것은 어떠한가? const 참조 리턴형을 통해서 다음과 같은 구문이 허용된다.

void display(const free_throws & ft);
display(accumulate(team,two));

 

이것은 display()에 대한 공식 매개변수가 const free_throw &이기 때문이다. 그러나 다음 구문은 accumulate()을 위한 첫 번째 공식 매개변수가 const가 아니기 때문에 허용되지 않는다.

accumulate(accumulate(team, three), five);

 

const를 생략하여 간단하게 코드를 작성할 수 있지만, 더 모호한 코드가 작성되는 문제가 있다.

일반적으로 모호한 코드는 에러를 일으킬 확률을 높이기 때문에 설계할 때 모호한 코드를 추가하지 않도록 하는 것이 좋다. 리턴형을 const 참조로 만드는 것은 프로그래머가 모호한 에러를 만들지 않는 좋은 방법이다. 그러나 경우에 따라서 const를 쓰지 않는 것이 옳을 때도 있다.

 

함수 오버로딩

함수의 다형(polymorphism)은 C++에 새로 추가된 기능이다. 함수 오버로딩(function overloading)이라고도 부르는 함수의 다형은, 서로 다른 여러 개의 함수가 하나의 이름을 공유하는 것이다. 여기서 '다형'이라는 말은 다양한 형태를 가진다는 뜻이다.

함수 오버로딩의 열쇠는 함수의 매개변수 리스트이다. 이것을 함수 시그내처(function signature)라고 한다. 
이들 시그내처는 매개변수의 개수와 종류가 다르다. 예를 들어, print() 함수의 계열을 아래와 같이 그 원형을 정의할 수 있다.

void print(const char * str, int width); // #1
void print(double d, int width); // #2
void print(long l, int width); // #3
void print(int i, int width); // #4
void print(const char * str); // #5


오버로딩된 함수를 사용할 때에는, 함수 호출에서 올바른 데이터형의 매개변수를 사용하는지 확인해야 한다. 예를 들어, 다음과 같은 구문을 생각해보자.

unsigned int year = 3210;
print(year, 6);


print() 함수의 이러한 호출은 대응하는 원형이 없다. 대응하는 원형이 없을 경우에, C++는 표준적인 데이터형 변환을 시도하여 어떻게든 대응이 이루어지도록 노력하기 때문에, 여러 원형 중에서 어느 것이 사용될지 장담할 수 없다.

double cube(double x);
double cube(double & x);


여기에서 함수 오버로딩을 사용할 수 있으리라고 잘못 생각할 수 있다. 왜냐하면 함수 시그내처가 서로 달라 보이기 때문이다. 그러나 모든 것을 컴파일러 입장에서 생각해야 한다. 다음과 같은 코드가 있다고 가정해 보자.

cout << cube(x);


매개변수 x는 double x를 사용하는 원형이나 double &x를 사용하는 원형에 둘다 일치한다. 컴파일러는 어느 함수를 사용해야 할지 알지 못한다.

대응하는 함수를 찾는 과정에서 const와 const가 아닌 변수는 구별 된다. 

void dribble(char * bits);
void dribble(const char * cbits);

const char p1[] = "hello";
char p2[] = "world";

dribble(p1);
dribble(p2);


함수 오버로딩을 가능하게 하는 것은 함수의 데이터형이 아니라 시그내처라는 사실을 항상 명심하라. 예를 들어, 다음 두 선언은 함께 공존할 수 없다.

long gronk(int n, float m); // 같은 시그내처이므로
double gronk(int n, float m);// 공존할 수 없다.


함수 오버로딩은 언제 사용하는가?

함수 오버로딩이 매혹적인 것은 사실이지만 과용하면 안 된다. 함수 오버로딩은 서로 다른 데이터형을 대상으로 하지만 기본적으로는 같은 작업을 수행하는 함수들에만 사용하는 것이 바람직하다. 또한 독자는 디폴트 매개변수를 사용하여 같은 목적을 수행할 수 있는지 확인하는 것이 좋다. 예를 들어, 다음과 같은 두 개의 오버로딩된 함수를 디폴트 매개변수를 사용하는 left()라는 하나의 문자열 처리 함수로 대체할 수 있다.

char * left(const char * str, unsigned n);
char * left(const char * str);

 

디폴트 매개변수를 사용하는 하나의 함수를 사용하는 것이 더 간단하다. 함수를 단지 하나만 작성하면 되고, 프로그램도 하나의 함수를 저장하기 위한 메모리만 요구한다. 그리고 함수를 수정할 필요가 생겼을 때에도 하나의 함수만 수정하면 된다. 그러나 서로 다른 데이터형의 매개변수를 요구하고, 디폴트 매개변수가 수용이 없을 때에는 함수 오버로딩을 사용해야 한다.

 

함수 템플릿

템플릿은 구체적인 데이터형 대신에 일반형으로 프로그래밍 하게 되므로, 이것을 일반화 프로그래(generic programming)이라고 한다. 데이터형이 매개변수에 의해 표현되므로, 템플릿을 때로는 매개변수화 데이터형(parameterized type)이라고 한다.

다양한 데이터형에 대한 동일한 알고리즘을 적용해야 하는 함수가 필요하다면, 템플릿을 사용해야 한다. 역호환성(backward compatibility)을 별로 걱정하지 않고, 긴 단어를 입력하는 수고도 받아들일 수 있다면, 데이터형 매개변수를 선언할 때 키워드로 class 대신 typename을 사용해야 한다.

 

템플릿 오버로딩

모든 데이터형이 항상 같은 알고리즘을 사용할 리는 없다. 이러한 가능성을 처리하기 위해, 일반적인 오버로딩을 하듯이, 템플릿 정의를 오버로딩할 수 있다. 일반적인 오버로디오가 마찬가지로, 템플릿을 오버로딩할 때에도 확실하게 구분되는 함수 시그내터를 사용해야 한다. 예를 들어 원본 템플릿은 (T &, T&)라는 시그내처를 사용하지만, 새로운 템프릿은 (T [], T [], int)라는 시그내처를 사용한다. 마지막 매개변수는 일반형이 아니라 구체형(int)이다. 모든 템플릿 매개변수가 템플릿 매개변수형일 필요는 없다.

 

템플릿 제한다음과 같은 템플릿 함수가 있다고 가정해 보자

template <class T>
void f(T a, T b)

 

종종 코드에서 형에 따라 어떠한 연산이 가능할 것인지에 대한 가정을 한다. 예를 들면, 다음 구문처럼 대입이 정의되고 있고 T형이 built-in 배열형이라면 해당 구문은 참이 아닐 것이다.

a = b;

 

이와 유사하게 >가 정의되어있다고 가정하는데, T가 일반 구조체라면 참이 아니다.

어떤 형을 다루지 않는 템플릿 함수를 사용하는 것은 쉬운 작업이다. 한편 C++ 구문이 허용하지 않을지라도 템플릿 함수를 일반화하는 것이 때론 이치에 맞다. 예를 들면, + 연산자가 구조체에는 적용되지 않더라도 구조체를 재편성(position coordinates)하도록 연산자를 재정의하는 것은 됀찮을 수 있다.

 

명시적 특수화

다음과 같은 구조체를 정의한다고 가정하자.

struct job
{
    char name[40];
    double salary;
    int floor;
}

 

이러한 구조체 두 개를 만들고, 두 구조체의 내용을 교환하려고 한다. 원본 템프릿은 다음과 같은 코드를 사용하여 내용을 교환한다.

temp = a;
a = b;
b = temp;

 

C++에서는 하나의 구조체를 다른 하나의 구조체에 대입할 수 있기 때문에, Any 형이 job 구조체인 경우에도 위의 코드는 잘 작동한다. 그러나 salary와 floor 멤버만 교환하고, name 멤버는 그대로 두고 싶다고 가정해 보자. 이 경우에는 이제 코드가 달라저야 한다. 그러나 Swap()에 넘겨주는 매개변수는 첫 번째 경우(두개의 job구조체에 대한 참조)와 같아야 한다. 그렇다면 새로운 코드에 템플릿 오버로딩을 사용할 수 없다.

그러나 명시적 특수화(explicit specialization)라는 특수화된 함수 정의를, 필요한 코드와 함께 제공할 수 있다.

 

3세대 특수화(ISO/ANSI C++ 표준)

다음은 job형 구조체를 교환하는 세 가지 형식의 함수 원형이다.

// 템플릿이 아닌 함수형
void Swap(job &, job &);

// 템플릿 원형
template <typename T>
void Swap(T &,T &);

// job형을 위한 명시적 특수화
template <>void Swap<job>(job &, job &);

 

명시적 특수화를 통해 특정 멤버만을 교환 할 수 있다.

template <typename T>
void Swap(T &a, T &b) // 일반 버전
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}

template <> void Swap<job>(job & j1, job &j2) // 특수화 버전
{
    double t1;
    int t2;
    t1 = j1.salary;
    j1.salary = j2.salary;
    j2.salary = t1;
    t2 = j1.floor;
    j1.floor = j2.floor;
    j2.floor = t2;
}

 

오래된 특수화 형식

위의 코드를 제대로 컴파일하지 못한다면, 오래된 특수화 형식을 사용해야한다. 이것을 해결하는 가장 간단한 방법은 처리하기를 원하는 특정 데이터에 맞게 정의된 일반 함수를 제공하는 것이다.

// 명시적 특수화
template <> void Swap<job>(job &, job &);

// 오래된 특수화 형식
void Swap<job>(job &, job &);

 

구체화와 특수화

템플릿을 더 잘 이해햐기 위해 구체화와 특수화라는 용어를 살펴보자.

 

컴파일러가 특정 데이터형에 맞는 함수의 정의를 생성하기 위해 템플릿을 사용할 때, 그 결과를 템플릿의 구체화(instantiation)라 한다.

 

예를 들어 Swap(i, j) 함수 호출은 컴파일러가 int형을 사용하는 Swap()을 구체화하게 만든다.

템플릿은 함수의 정의가 아니다. int형을 사용하는 특정 구체화가 함수의 정의다. 이러한 방식의 구체화를 암시적 구체화(implicit instantiation)라 한다. 그 이유는 프로그램이 int형 매개변수를 요구하는 Swap() 함수를 사용한다는 사실을 컴파일러에게 알림으로써, 그에 맞는 함수 정의를 만들 필요가 있다는 것을 컴파일러가 암시적으로 인식하기 때문이다.

 

처음에는 암시적 구체화를 사용하는 것이, 컴파일러가 템플릿으로부터 함수 정의를 생성하는 유일한 방법이었다. 지금은 C++가 명시적 구체화를 허용한다.

 

명시적 구체화를 선언하는 문법은, <> 표기를 사용하여 데이터형을 나타내고, 선언 앞에 키워드 template을 사용한다.

template void Swap<int>(job &, job &); // 명시적 구체화

 

이 기능을 구현하는 컴파일러는, 이 선언을 발견했을 때, Swap() 템플릿을 사용하여 int형에 맞는 구체화를 생성한다. 즉, 이 선언은 "Swap() 템플릿을 사용하여 int형에 맞는 함수 정의를 생성하라"는 의미이다.

template <> void Swap<int>(job &, job &); // 명시적 특수화
template <> void Swap(job &, job &); // 명시적 특수화

 

명시적 특수화 선언은 키워드 template 뒤에 <>를 가지는 반변에, 명시적 구체화는 <>를 생략한다.

 

암시적 구체화, 명시적 구체화, 명시적 특수화를 모두 특수화(specialization)라고 한다. 이들의 공통점은 이들이 일반화 서술을 나타내는 함수의 정의가 아니라, 모두 구체적인 데이터형을 사용하는 함수 정의를 나타낸다는 것이다.

다음 코드 단편은 지금까지 설명한 개념들을 요약한다.

template <class T>
void Swap(T &, T &); // 템플릿 원형

template <> void Swap<job>(job &, job &); // job을 위한 명시적 특수화

int main(void)
{
    template void Swap<char>(char &, char &); //char를 위한 명시적 구체화

    short a, b;
    Swap(a,b); // short를 위한 암시적 템플릿 구체화를 사용한다.

    job n, m;
    Swap(n, m); // job을 위한 명시적 특수화를 사용한다.

    char g, h;
    Swap(g, h); // char를 위한 명시적 템프릿 구체화를 사용한다.
}

 

그 타입이 무엇인가?

프로그래머가 템플릿 함수를 쓰려고 할 때 한 가지 문제점은, C++98에서 선언을 할 때에 어떤 타입을 사용해야 하는지 알아내는 방법이 항상 가능한 것은 아니라는 점이다. 다음의 부분적 예제를 생각해 보자.

template<class T1, class T2>
void ft(T1 x, T2 y>
{
    ?type? xpy = x + y;
}

int가 될 수 있는데 이럴 경우, 합계의 타입은 double이 된다. 또는, T1이 short가 되고 T2가 int가 될 수도 있는데, 이러한 경우에는 합계의 타입이 int가 된다.

 

decltype 키워드(C++11)

C++11 솔루션은 새로운 키워드 decltype이다. 이 키워드는 다음과 같이 사용할 수 있다.

int x;
decltype(x) y; // x와 동일한 타입의 y를 만들어라

 

 decltype을 위한 구문은 ft() 에문에서와 같이 사용할 수 있는데, 여기서 우리는 아래 코드를 사용해 보도록 한다.

decltype(x + y) xpy;
xpy = x + y;

 

아니면, 이러한 두 가지 구문을 하나의 초기화로 혼합할 수 있다.

decltype(x + y) xpy = x + y;

 

따라서, 우린 다음과 같이 ft() 템플릿을 수정할 수 있다.

template<class T1, class T2>
void ft(T1 x, T2 y>
{
    decltype xpy = x + y;
}

 

만약, 한 가지 이상을 선언하고자 할 경우에는 typedef를 decltype와 함께 사용할 수 있다.

template<class T1, class T2>
void ft(T1 x, T2 y)
{
    typedef decltype(x+y) xytype;
    xytype xpy = x + y;
    xytype arr[10];
    xytype & rxy = arr[2];
}

 

대체할 수 있는 함수구문(C++11의 Trailing Return Type)

decltype 메커니즘은 그 자체로 유관된 문제를 풀지 못하게 한다. 

template<class T1, class T2>
?type? gt(T1 x, T2 y)
{
    return x + y;
}

 

이를 위해서 C++11은 함수를 선언하고 정의하는 새로운 구문을 허용한다. 여기 내재(built-in)타입을 사용하는 것이 어떻게 가능한지 보여준다.

 

double h(int x, float y)의 원형은 다음의 구문으로 대체될 수 있다.

auto h(int x, float y) -> double
{/*function body*/}

 

 

이 구문을 decltype와 혼합하게 된다면, 다음의 gt()를 위한 리턴 타입을 다음과 같은 솔루션을 얻을 수 있다.

template<class T1, class T2>
auto gt(T1 x, T2 y) -> decltype(x+y)
{
    return x+y;
}