33. std::forward 를 통해서 전달할 auto&& 매개변수에는 decltype 을 사용하라
C++14 에서 가장 exciting 한 기능은 매개변수에 auto를 사용할 수 있는 general lambda 기능이다.
람다 클로저 클래스의 operator 를 템플릿 함수로 만들어서 임의로 구현할 수 있다.
예시)
auto f = [](auto x){ return normalize(x); };
// 위 람다함수 auto 구현하는 법
class 컴파일러가_만든_이름 {
public:
template<typename T>
auto operator()(T x) const {
return normalize(x);
}
...
};
이제 위 케이스에서 x 가 l-value 또는 r-value 인자로 전달된다고 가정하자.
매개변수 x 는 operator 에서 항상 l-value 이므로, 완벽 전달이 구현되지 않는다.
완벽 전달을 구현하기 위해, 람다 표현식에서 수정할 부분은 두가지이다.
- x 가 보편 참조 형태여야함
- x 를 std::forward 를 사용해서 함수에 전달해야함
예시)
auto f = [](auto&& x) { return normalize(std::forward<???>(x)); };
보편 참조 형태에 std::forward 를 사용했으나, std::forward 에 지정할 형식이 명확하지 않다.
일단 x 는 그대로 사용할 수는 없는데 매개변수는 항상 l-value 이기 때문이다
또한 람다 표현식에서는 형식 매개변수 T 가 없으므로, 템플릿 함수처럼 <T> 와 같이 사용할 수도 없다.
decltype 을 사용해서 매개변수 형식이 l-value 인지, r-value 인지 구분할 수 있다.
이는 T 대신 추론된 타입을 제공한다.
decltype 을 사용할 수 있는 이유를 알아보자.
Widget 타입에 대해, auto 기반 보편 참조를 하는 경우 x는 Widget&, Widget&& 의 형태가 될 수 있다.
l-value 전달 - x : Widget&
r-value 전달 - x : Widget&&
( r-value 를 비참조 형식 (Widget)으로 지정하는 관례를 따르지 않음 )
std::forward 의 구현 예시)
template<typename T>
T&& forward(remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
l-value 가 전달되는 경우 인스턴스 예시)
Widget& && forward(Widget& param) {
return static_cast<Widget& &&>(param);
}
// 참조 축약 적용
Widget& forward(Widget& param) {
return static_cast<Widget&>(param);
}
l-value 는 완벽 전달된다.
r-value 가 전달되는 경우 인스턴스 예시)
Widget&& && forward(Widget& param) {
return static_cast<Widget&& &&>(param);
}
// 참조 축약 적용
Widget&& forward(Widget& param) {
return static_cast<Widget &&>(param);
}
r-value 도 완벽 전달된다.
따라서 람다 매개변수 x 인자가 l-value 또는 r-value 일 때, decltype(x) 도 l-value 또는 r-value 참조 형식을 산출한다.
결국 decltype 을 사용하여 x 의 선언된 타입 그대로 반환하는 완벽 전달을 구현할 수 있다.
일반적인 완벽 전달 람다 예시)
auto f = [](auto&&... xs) { return normalize(std::forward<decltype(x)s>(xs)...); };
'dev > C++' 카테고리의 다른 글
Effective Modern C++ #35 스레드 기반 프로그래밍보다 과제 기반 프로그래밍을 선호하라 (1) | 2025.07.02 |
---|---|
Effective Modern C++ #34 std::bind 보다 람다를 선호하라 (0) | 2025.06.30 |
Effective Modern C++ #32 객체를 클로저 안으로 이동하려면 초기화 캡쳐 (갈무리)를 사용하라 (0) | 2025.06.30 |
Effective Modern C++ #31 기본 Capture mode를 피해라 (0) | 2025.06.30 |
Effective Modern C++ #30 완벽 전달이 실패하는 경우들을 잘 알아두라 (0) | 2025.06.30 |