Item 33 - auto&&를 std::forward할 땐 decltype
마지막 수정 시각: 2020-10-26 22:03:12
C++ 14에 추가된 generic lambda라는 기능은 람다의 매개변수 정의에 auto를 쓸 수 있게 해주는 굉장히 흥미로운 기능이다. 이 기능의 구현은 람다의 클로져 클래스 내부 멤버 함수 operator()를 템플릿으로 만드는 것으로 이루어진다.
auto f = [](auto x){ return func(normalize(x)); };
//내부 클로져 클래스의 구현
class Closure
{
public:
template<typename T>
auto operator()(T x) const
{ return func(normalize(x)); }
}
위 예제에서 람다는 단순히 인자 x를 normalize로 전달해주는 역할만을 한다. 이 때 normalize 함수의 인자가 lvalue일 때와 rvalue일 때 서로 다르게 동작하도록 작성되어있다면, 위 코드는 적절하게 작성되었다고 말하기 힘들어진다. x는 무조건 lvalue로 취급되어 lvalue 함수만 호출될 것이기 때문이다. 이 경우 앞선 항목들에서 다뤘던 perfect forwarding을 쓰는게 적합하다.
auto f = [](auto&& x)
{ return func(normalize(std::forward<???>(x))); };
하지만 여기서 문제가 생겼다. forward를 하려면 타입 T를 넘겨야하는데, x의 타입 T가 뭔지 알 수가 없다. 람다에서는 auto로 표기를 하므로 타입을 가리킬 마땅한 명칭이 없는 것이다. 이 경우 Item 3 - decltype에서 소개한 decltype을 쓸 수 있다. decltype(x)를 쓰면 x의 타입을 얻을 수 있으니, 이 걸 쓰면 되지 않을까? x가 lvalue일 경우 decltype(x)는 T&가 되므로 x가 lvalue일 경우는 잘 동작할 것이다. 반면 x가 rvalue인 경우 decltype(x)는 T&&이 된다. 원래는 타입 T를 넘겼으니 조금 차이가 생기는 것이다. 그럼 x가 rvalue일 때 std::forward가 어떻게 동작할지 살펴보자.
template<typename T>
T&& forward<remove_reference_t<T>& param)
{
return static_cast<T&&>(param);
}
위 forward 코드에 Widget타입의 rvalue를 포워딩했다고 하자. decltype에 의해 추론된 타입을 집어넣으면 아래와 같은 형태가 될 것이다.
Widget&& && forward(Widget& param)
{
return static_cast<Widget&& &&>(param);
}
여기서 reference collapsing 룰을 적용하면 최종 형태는 다음과 같이 된다.
Widget&& forward(Widget& param)
{
return static_cast<Widget&&>(param);
}
이로부터 decltype을 써도 우리가 원하는 결과를 얻을 수 있다는 결론을 얻을 수 있다. 이 결론을 앞의 람다 표현식에 적용시켜보자.
auto f =
[](auto&& param)
{
return func(normalize(std::forward<decltype(param)>(param)));
};
이 경우는 인자가 하나인 경우지만, C++14의 람다는 variadic까지도 지원한다. variadic의 경우 아래 코드와 같은 방식으로 작성하면 된다(pack 들어가는 것 말곤 차이도 없다).
auto f =
[](auto&&... params)
{
return func(normalize(std::forward<decltype(params)>(params)...));
};