Item 33: Use decltype on auto&& parameters to std::forward them.
item 33. std::forward에 auto&&를 인자 타입으로 할땐 decltype를 사용하자.
인자로 auto를 사용하는 람다식을 Generic Lambda라고 한다. (C++ 14용 feature)
auto GL = [](auto X) { return F(X); }; |
위 Lambda는 내부적으로는 ( ) 연산자를 가진 template closure class로 구현된다.
class CompilerGeneratedClosureClass
{
public:
template <typename T>
auto operator() (T X) const
{
return F(X);
}
};
|
이 Code가 하는거라고는 인자 X를 받아서 F()함수로 전달하여 그 결과를 return 하는 것 밖에 없다.
만약F()가 R-Value와 L-Value를 다르게 취급한다면,
이 Lambda는 L-Value로만 F()로 전달하기 때문에 수정할 필요가 있다.
정확한 방법은 Perfect-forward X를 F()에 전달하는 것이다.
그러기 위해선 2가지를 수정해야 한다.
1. X 가 Universal Reference가 되어야 한다. (item 24)
2. std::forward를 거쳐서 F()에 전달되어야 한다. (item 25)
auto GL = [](auto&& X) { return F(std::forward<???>(X)); }; |
개념상은 위와 같게 되겠지만,
현실적으로 std::forward에 전달되어질 타입 (???)을 어떻게 써야할까 ?
일반적으로 std::forward<T> 형식으로 사용했다.
물론 T는 template type이다.
하지만 Generic Lambda에 보면 T라는 타입 인자가 없다.
T는 Lambda에 의해 생성된 template closure class의 ( ) 연산자에 있다.
하지만, Lambda로 부터 그것을 참조하는건 불가능하기 때문에
아고 의미 없다.
Universal Reference에 L-Value가 전달되면 인자 타입은 L-Value Reference가 된다.
R-Value가 전달되면 R-Value Reference가 된다. (item 28)
Lambda에서 X라는 인자의 타입이 R-Value인지 L-Value 인지 알 수 있나 ?
decltype을 사용하면 알 수 있다. (item 3)
decltype(X)에 L-Value가 들어오면 L-Value Reference를 만들어내고,
R-Value가 들어오면 R-Value Reference를 만들어낸다.
std::forward<T>는 L-Value Reference를 L-Value로 전달하고,
Non-Reference 를 R-Value로 전달한다. (Item 28)
위 예제 Lambda의 X에 L-Value가 전달되면 decltype(X)는 L-Value Reference가 된다. 규칙을 따른거다.
하지만 X에 R-Value가 전달되면 decltype(X)는 규칙대로라면 Non-Reference가 되어야 하지만 대신 R-Value Reference가 된다.
std::forward<T>의 C++14 구현을 보면, (item 28)
template<typename T>
T&& forward(remove_reference_t<T>& param)
{
return static_cast<T&&>(param);
}
|
사용자 Widget 타입의 R-Value를 Perfect-forward로 전달하면, Non-Reference Widget 타입으로 인스턴스화된다.
// Instantiation of
std::forward when T is Widget
Widget&& forward(Widget& param)
{
return static_cast<Widget&&>(param);
}
|
하지만 같은 R-Value 타입의 Widget을 Perfect-forward하기를 바라는 때,
T를 Non-Reference으로 지정하는 규칙 대신에 R-Value Reference로 지정하는 것을 한번 고려해 보자.
T가 Widget&&로 되는 경우를 한번 보자는 것이다.
std::forward<T>와 std::remove_reference_t<T>의 초기 인스턴스화 이후,
std::forward<T>의 Reference Collapsing (참조붕괴)가 되기 전에 std::forward<T>는 다음과 같을 것이다. (item 28)
// Instantiation of
std::forward when T is Widget&&
// (before
reference-collapsing)
Widget&& && forward(Widget& param)
{
return static_cast<Widget&& &&>(param);
}
|
참조 붕괴 규칙 이후 R-Value Reference를 R-Value Reference하는 Code를 한번의 R-Value Reference로 고친 인스턴스화가 된다.
// Instantiation of
std::forward when T is Widget&&
// (after reference-collapsing)
Widget&& forward(Widget& param)
{
return static_cast<Widget&& >(param);
}
|
이 인스턴스화의 경우를 이 Widget으로 된 것과 비교해본다면, 결과적으로 동일하다는 것을 알 수 있다.
즉, std::forward<T>에 R-Value Reference를 인스턴스화 한것은 Non-Reference의 인스턴스화한거와 같다.
멋진 소식이다.
왜냐면 decltype(X)는 Lambda의 인자 X로 R-Value가 전달되었을 때 R-Value Reference가 되기 때문이다.
L-Value가 Lambda로 전달되면 decltype(X)는 기존 방법대로 std::forward<T>에 전달하고,
R-Value가 전달되면 decltype(X)가 td::forward<T>로 기존 방법과듣 다르게 전달하지만, 같은 결과를 얻을 수 있다.
그래서 R-Value, L-Value 모두 std::forward<decltype(X)>로 원하는 결과를 결과를 얻을 수 있다.
Perfect-Forwarding Lambda 예제는 다음과 같다.
auto PFL = [](auto&& X) { return F(std::forward<decltype(X)>(X)); }; |
Perfect-forwading Lambda는 여러개의 인자를 가지는 경우도 가능하다.
C++ 14에서 Lambda를 Variadic(가변적으로 인자들을 가질 수 있는)으로 쓸 수 있다.
auto PFL = [](auto&&.... param) { return F(std::forward<decltype(param)>(param)...)); }; |
Things to Remember * std::forward<T>에 auto&&을 넣을 땐 decltype을 사용하자. |
댓글 없음:
댓글 쓰기