24. 보편 참조와 오른값 참조(r-value ref)를 구별하라.
보편참조는 추상적이다. ( 선언 시 l-value, r-value 로 나뉘지 않음 )
일반적으로 (템플릿)T 형식에 대한 r-value reference 선언 시, T&& 표기를 사용한다.
하지만 반대로 코드에서 T&& 를 발견했을 때 이를 r-value reference 라고 단언할 수 없다.
다양한 예시)
void f(Widget && param); // r-value ref
Widget&& var1 = Widget(); // r-value ref
auto&& var2 = var1; // r-value ref 아님
template<typename T>
void f(std::vector<T>&& param); // r-value ref
template<typename T>
void f(T&& param); // r-value ref 아님
"T&&" 형식이 될 수 있는 것
- r-value reference
- r-value 인자를 사용하며, 이동의 원본이 되는 객체 지정함
- universal reference
- 추상적 ( l-value 또는 r-value reference )
- l-value, r-value 와 const, 비 const, volatile 등 거의 모든 인자에 대응할 수 있음
universal reference 는 두가지 문맥에서 나타난다.
1. 함수 템플릿 매개변수
template<typename T>
void f(T&& param);
2. auto&& 선언
auto&& var2 = val1;
위 두 케이스의 공통점은 형식 연역이 일어난다는 것이다.
보편 참조를 만드는 조건은 다음과 같다.
- 형식 연역이 관여해야함
- 참조 선언의 형태 정확해야함 (T&& 의 형태)
함수 템플릿 매개변수
형식 연역 관여되지 않는 예시1)
template<typename T>
void f(T&& param) {}
f<int>(x); // r-value ref
위 케이스와 같이 호출자에서 T 가 명시적으로 지정되는 경우, 형식 연역이 관여하지 않는다.
따라서 보편 참조가 아닌 r-value reference 로 동작한다.
형식 연역 관여되지 않는 예시2)
#include<iostream>
#include<vector>
template<typename T>
void f(std::vector<T>&& param) {
std::cout << "r-value reference" << std::endl;
}
int main() {
std::vector<int> v{10, 20};
f(v); // r-value ref 이나, l-value 인자이므로 컴파일 에러
}
위 케이스는 T&& 형태가 아닌 std::vector<T>&& 형태이다.
따라서 f 는 r-value reference 로 동작하고, l-value 넣었을 때 컴파일 에러 발생한다.
보편 참조가 되기 위한 참조 선언의 형태는 상당히 엄격하다.
예를 들어 T&& 에 const 만 붙여도 보편 참조가 되지 못한다.
또한 템플릿 안에서 T&& 형태 함수 매개변수를 사용한다고 보편 참조라고 확신할 수 없다.
형식 연역이 반드시 일어나는 보장이 없기 때문이다.
std::vector push_back 예시)
template<class T, class Allocator = allocator<T>>
class vector {
public:
void push_back(T&& x);
...
};
std::vector<Widget> v;
위 케이스에서 클래스 생성 시 T 가 Widget 으로 연역된다.
따라서 push_back 멤버 함수에는 형식 연역이 관여하지 않는다.
이러한 경우 멤버 함수는 r-value reference 로 동작한다.
반면 emplace_back 은 멤버 함수에서 형식 연역이 사용된다.
std::vector emplace_back 예시)
template<class T, class Allocator = allocator<T>>
class vector {
public:
template<class... Args>
void emplace_back(Args&&... x);
...
};
여기서 Args 는 vector 형식 매개변수 T 와 독립적이다.
Args 는 emplace_back 호출될 때마다 그 형태가 연역된다.
auto&& 선언
auto&& 형식으로 선언된 경우, 형식 연역이 일어나고 형태(T&&)가 정확하므로 보편참조이다.
C++14 이상에서는 람다표현식에서 auto&& 매개변수 선언을 사용한다.
auto&& 매개변수 사용 예시)
auto timeFuncInvoation = [] (auto&& func, auto&&... params) {
std::forward<decltype(func)>(func)(
std::forward<decltype(params)>(params)...
);
}
func, params 모두 보편 참조이므로, 거의 모든 함수의 실행시간을 확인할 수 있는 함수가 된다.
보편 참조는 기본적으로 추상적이나 유용하며, r-value ref 와 universal ref 를 구분하는 것은 도움이 된다.