튜플<tuple>
함수가 여러 결과를 계산할 때, 값들을 변경 가능한 레퍼런스 인수로 전달한다.
void lu(const matrix& A, matirx& LU, vector& p){
행렬 A에 피벗(pivoting)을 이용해 분해된 행렬 LU와 순열 벡터 p를 반환하는 LU의 경우(2개의 배열을 반환)
하나는 return하고, 하나는 레퍼런스로 전달할 수 있다.
이와 같이 여러 결과를 반환하기 위해서 튜플(Tuple)로 묶을 수 있다.
튜플은 컨테이너와 달리 서로 다른 타입을 허용한다.
대신 대부분의 컨파일러와 달리 개체의 개수를 컴파일 타임에 알아야 함
tuple<matrix, vector> lu(const matirx& A){
matix LU(A);
vector p(n);
return tuple<matix, vector>(LU, p);
}
make_tuple 헬퍼함수는 내부적으로 타입매개변수를 스스로 추론한다.
tuple<matrix, vector> lu(const matrix& A){
return make_tuple(LU, p);
}
auto 와 함께 사용할 수 있다.
auto t=make_tuple(LU, p, 7.3, 9, LU*p, 2.0+9.0i);
get()
lu 함수 호출자는 이후, get함수를 통해 튜플에 있던 행렬과 벡터를 추출할 수 있다.
tuple<matrix, vector> t=lu(A);
matrix LU=get<0>(t);
vector p=get<1>(t);
auto t=lu(A);
auto LU=get<0>(t);
auto p=get<1>(t);
get() 함수는 두 개의 인수, 즉 튜플과 그 위치를 입력 받는다.
위치 값은 컴파일 타임 매개변수이다.
인덱스가 너무 크면, 컴파일하는 동안 오류 발생 가능
auto t=lu(A);
auto am_i_stupid=get<2>(t);
C++14에서는 튜플 항목의 타입이 명확한 경우, 타입별로 해당 튜플 항목에 접근 가능하다.
auto t=lu(A);
auto LU=get<matrix>(t);
auto p=get<vector>(t);
tie()
tie()를 이용하면, 튜플의 항목을 분리할 수 있다.
matrix LU;
vector p;
tie(LU, p)=lu(A);
tie()는 내부적으로 함수 인수에 대한 레퍼런스를 갖는 개체를 생성하고,개체에 튜플을 할당하면, 각 튜플 멤버를 해당 레퍼런스에 할당한다.
get()를 사용하는 것보다 내부적으로, tie()가 성능상 더 유리하다.
함수 lu의 결과(tuple)를 tie에 직접 전달할 때, Rvalue이므로 항목을 이동시킬 수 있다.
중간 변수를 사용하면(get) Lvalue가 되어 항목을 복사하여야 한다.
복사를 피하기 위해서 명시적으로 튜플에 명식적으로 move를 호출할 수 있다.
auto t=lu(A);
auto LU=get<0>(move(t));
auto p=get<1>(move(t));
위의 코드에서 get을 통해 튜플의 0, 1인덱스의 데이터에 한번만 접근할 때는 문제가 없다.
하지만, 다시 접근할 경우 코드가 올바르지 않게 수행될 수 있다.
move(t) 연산을 하게 되면, t를 Rvalue로 변환시킨다.
유의해서 코드 작성할 것
tuple<matrix, vector> lu(const matrix& A){
return make_tuple(move(LU), move(p));
}
위와 같이 함수내부에서 make_tuple인자를 move()를 통해 Rvalue로 전달할 경우, 복사를 피할 수 있다.어차피 함수가 종료되면, 내부의 지역변수들이 파기되기 때문에 무방
class pairpair는 C++03부터 존재하고 있는 클래스로 '두 개의 인수를 가지는 튜플'과 동일하다.
pair는 tuple간의 변환을 지원하므로, pair와 두 개의 인수를 갖는 tuple을 서로 교환, 혼합할 수 있다.
pair에서는 tuple의 get<0>(t) 대신, t.first, get<1>(t)대신, t.second를 사용할 수 있다.
Boost::FusionBoost::Fusion은 고전적인 (런타임) 프로그래밍과 메타프로그래밍을 융합하기 위해 고안된 라이브러리이다.
Boost::Fusion을 이용해서 튜플을 반복하는 코드 작성 가능
struct printer{
template <typename T>
void operator()(const T& x) const {
std::cout<<"Entry is "<<x<<std::endl;
}
};
int main(void){
auto t=std::make_tuple(3, 7u, "Hallo,", std::string("Hi"), std::complex<float>(3, 7));
boost::fusion::for_each(t, printer{});
}
라이브러리는 이질적인 타입의 합성을 순회하며 변형하는 더욱 강력한 기능을 제공한다.
function#include <functional>
클래스 템플릿 function은 일반화된 함수 포인터이다.
(함수 타입은 아래와 같이 템플릿으로 전달)
double add(double x, double y){
return x+y;
}
int main(void){
using big_fun=function<double(double, double)>;
big_fun f=&add;
cout<<"f(6, 3)="<<f(6, 3)<<endl;
return 0;
}
함수 래퍼는 동일한 리턴 타입 및 동일한 매개변수 타입의 목록으로
여러 종류의 함수 엔티티(entiity)를 보유할 수 있다.
vector<big_fun> functions;
functions.push_back(&add);
함수를 인수로 전달할 때, 내부적으로 해당 주소를 자동으로 가져온다.(암시적 변환)
함수는 함수 포인터로 붕괴되기 때문에 주소 연산자 &를 생략 가능
functions.push_back(add);
함수가 inline으로 선언되는 경우, 호출하는 부분에 코드를 삽입해야 한다.
그럼에도 주소로 전달할 경우, function 개체로 저장할 수 있는 고유한 주소를 받는다.
(배열이 암시적으로 포인터로 붕괴하는 것과 동일)
inline double sub(double x, double y){
return x-y;
}
functions.push_back(&sub);
펑터도 저장가능
struct mult{
double operator()(double x, double y) const {return x*y; }
};
functions.push_back(&multi{});
클래스 템플릿은 타입이 아니기 때문에 클래스 템플릿을 만들 수는 없다.
template <typename Value>
struct power{
Value operator()(Value x, Value y) const { return pow(x, y); }
};
functions.push_back(&power());
functions.push_back(&power<double>{});
반면에 함수 템플릿이 포함된 클래스에서는 개체를 만들 수 있다.
struct greater_t{
template <typename Value>
Value operator()(Value x, Value y) const { return x>y; }
} greater_than;
functions.push_back(greater_than);
위 코드에서 템플릿 호출 연산자는 함수 타입으로 인스턴스화가 가능해야 한다.
아래의 코드는 다른 인수를 갖는 함수이기 때문에 함수로 인스턴스화 할 수 없어 컴파일할 수 없다.
function<double(float, double)> ff=greater_than;
lambda를 function 개체로 저장 가능하다.
functions.push_back([](double x, double y){ return x/y; });
컨터이너의 항목 호출
for(auto &x: functions)
cout<<"f(6, 3) = "<<f(6, 3)<<endl;
function 함수 포인터는 유연성과 명확성 측면에서 함수 포인터보다 더 유리하다.(오버헤드로 인한 약간의 비용이 소모됨)
레퍼런스 래퍼위의 function을 이용해서 큰 벡터나 행렬에 대한 목록을 포인터를 이용해서 하나의 컨테이너에 담을 수 있었다.
레퍼런스의 컨테이너를 직접 만들 수는 없다.
<functional>의 reference_wrapper를 사용해서 C++11에서 구현가능하다.
내부적으로 레퍼런스를 담지는 않지만, 비슷한 타입을 가진다.
vector<reference_wrapper<vector<int>> vv;
vector<int> v1={2, 3, 4}, v2={5, 6}, v3={7, 8};
vv.push_back(v1);
vv.push_back(v2);
vv.push_back(v3);
위 벡터는 레퍼런스 래퍼로 암시적으로 변환된다.
reference_wrapper<T>는 explicit가 아닌 T&의 생성자가 포함되어 있다.
get()
클래스에서 실제 개체를 가르키는 레퍼런스를 가져오는 get 메서드가 포함되어 있어 벡터를 출력할 수 있다.
for(const auto& vr: vv){
copy(begin(vr.get()), end(vr.get()), ostream_iterator<int>(cout, ", "));
cout<<endl;
}
for(const vector<int>& vr: vv){
copy(begin(vr), end(vr), ostream_iterator<int>(cout, ", "));
cout<<endl;
}
첫 번째 코드에서 auto를 통해 얻은 vr은 const reference_wrapper<vector<int>>& 타입이다.
이 때는 reference_wrapper가 가르키는 레퍼런스를 가져오기 위해서 get() 메서드 사용
두 번째 코드에서 vr의 타입을 vector<int>&로 명시하였다.
reference_wrapper는 기본 레퍼런스 타입 T&를 위한 암시적 변환을 제공함
ref & cref()ref는 T타입의 Lvalue를 reference_wrapper<T> 타입의 개체로 넘겨준다.
(ref의 인수가 이미 refference_wrapper<T>인 경우, 단순 복사)
cref는 reference_wrapper<const T> 타입의 개체로 넘겨준다.
map<int, reference_wrapper<vector<int>>> mv;
map<int, decltype(ref(v1))> mv;
map에서 일반적인 대괄호 표기법은 더 이상 사용 불가능하다.
래퍼에서 할당을 수행하기 전에 표현식 mv[4]에 내부적으로 호출되는 디폴트 생성자가 없음
(map의 괄호연산자가 컴파일 되지 않음)
대괄호 표기법 대신에, insert나 emplace 함수를 사용해야 함
mv.emplace(make_pair(4, ref(v1)));
mv.emplace(make_pair(7, ref(v2)));
mv.insert(make_pair(8, ref(v3)));
mv.insert(make_pair(9, ref(v2)));
map의 괄호연산자가 컴파일되지 않았기 때문에, find를 사용해 특정 항목을 검색해야 한다.
find는 해당 키에 대한 레퍼런스를 넘겨준다.
auto& e7=mv.find(7)->second;
항목 순회
for(const auto& vr: mv){
cout<<vr.first<<": ";
for(int i:vr.second.get()) cout<i<<<<", ";
cout<<endl;
}