다음 코드를 보자.
Somedefs.h
#pragma once
#include <iostream>
template <typename T, typename F>
T Use_f(T v, F f)
{
static int count = 0;
count++;
std::cout << "Use_f count = " << count
<< ", &count = " << &count << std::endl;
return f(v);
}
class Fp
{
private:
double z_;
public:
Fp(double z = 1.0)
: z_(z)
{}
double operator() (double p )
{
return z_ * p;
}
};
class Fq
{
private:
double z_;
public:
Fq(double z = 1.0)
:z_(z)
{}
double operator() (double q)
{
return z_ + q;
}
};
main.cpp
#include "Somedefs.h"
using namespace std;
double Dub(double x) { return 2.0 * x; }
double Square(double x) { return x * x; }
int main()
{
double y = 1.21;
cout << "함수 포인터 Dub : \n";
cout << " " << Use_f(y, Dub) << endl;
cout << "함수 포인터 Square : \n";
cout << " " << Use_f(y, Square) << endl;
cout << "함수 객체 Fp : \n";
cout << " " << Use_f(y, Fp(5.0)) << endl;
cout << "함수 객체 Fq : \n";
cout << " " << Use_f(y, Fq(5.0)) << endl;
cout << "람다 표현식 1: \n";
cout << " " << Use_f(y, [](double u) {return u * u; }) << endl;
cout << "람다 표현식 2: \n";
cout << " " << Use_f(y, [](double u) {return u + u / 2.0; }) << endl;
}
이 코드에서 Use_f 템플릿 함수는 6번 호출된다. 그럼 static 멤버 변수도 6이 될까?
실행 결과.
실행해보니 그렇지 않다. 멤버 변수의 주소를 통해 1,2번째를 제외한 나머지 static 멤버 변수의 주소가 다 다르다는 걸 알 수 있다. 왜 이렇게 된걸까? 템플릿 함수는 실행될 때 데이터형에 따라 그 함수가 정해진다. 그럼 1,2번째를 제외하고는 데이터형이 다 다르다는 것을 의미한다. 어떻게 구별될까?
일단 1,2번째는 double형 매개변수와 double형 반환값을 가진다. 3,4번째는 함수 객체를 매개 변수로 사용하기 때문에 각각 Fp, Fq 타입으로 변환된다. 마지막 두 호출은 컴파일러가 람다 표현식에서 사용한 타입으로 F를 설정한다.
이런 식으로 같은 역할을 하는데 템플릿 인스턴스를 많이 만들어 내는 것은 비효율적이다. 인스턴스를 한 번만 만드는 방법이 있을까? 예제에서 나온 함수들은 모두 double형을 매개변수로 갖고 double형을 반환한다는 공통점을 갖고 있다. 우리는 이들이 같은 호출을 하는 시그니처를 가지고 있다고 말 할 수 있다. 그리고 같은 함수 시그니처를 갖는 함수 포인터, 함수 객체, 람다를 다음과 같이 묶을 수 있다.
std::function<double(char, int)> fdci;
이렇게 같은 역할을 하는 함수들을 묶을 수 있다, 감쌀 수 있다는 의미에서 Wrapper 함수라고 부른다. 이는 <functional> 라이브러리 안에 들어있고, 저렇게 쓰면 double형을 반환, char, int형을 매개변수로 사용한다는 의미다. 이 예제에서는 double형을 매개변수로 쓰고 반환도 하니, std::function<double(double)>을 쓰면 되겠다.
std::function<double(double)> ef1 = Dub;
std::function<double(double)> ef2 = Square;
std::function<double(double)> ef3 = Fq(10.0);
std::function<double(double)> ef4 = Fp(10.0);
std::function<double(double)> ef5 = [](double u) {return u * u; };
std::function<double(double)> ef6 = [](double u) {return u + u / 2.0; };
double y = 1.21;
cout << "함수 포인터 Dub : \n";
cout << " " << Use_f(y, ef1) << endl;
cout << "함수 포인터 Square : \n";
cout << " " << Use_f(y, ef2) << endl;
cout << "함수 객체 Fp : \n";
cout << " " << Use_f(y, ef3) << endl;
cout << "함수 객체 Fq : \n";
cout << " " << Use_f(y, ef4) << endl;
cout << "람다 표현식 1: \n";
cout << " " << Use_f(y, ef5) << endl;
cout << "람다 표현식 2: \n";
cout << " " << Use_f(y, ef6) << endl;
실행 결과.
인스턴스가 하나만 만들어졌다. 6개를 만드는 것도 거추장스러워 보이니, 간략하게 나타내는 방법도 있다. 첫 번째는 다음과 같다.
typedef std::function<double(double)> fdd; // 타입 선언 간소화.
cout << Use_f(y, fdd(Dub)) << endl; // 매개 변수로 넣는다.
cout << Use_f(y, fdd(Square)) << endl;
두 번째는 템플릿 함수 자체에 변환을 주는 것이다.
Somedefs.h
#include <functional>
template <typename T, typename F>
T Use_f(T v, std::function<T(T)> f)
{
static int count = 0;
count++;
std::cout << "Use_f count = " << count
<< ", &count = " << &count << std::endl;
return f(v);
}
main.cpp
cout << Use_f<double>(y, Dub) << endl;
cout << Use_f<double>(y, Fp(5.0)) << endl;