Post List

2015년 3월 10일 화요일

Item 33 : std::forward에 auto&&를 인자 타입으로 할땐 decltype를 사용하자.

Effective Modern C++ - Scott Meyers
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-ValueL-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-ValueL-Value 모두 std::forward<decltype(X)>로 원하는 결과를 결과를 얻을 수 있다.

Perfect-Forwarding Lambda 예제는 다음과 같다.

auto PFL = [](auto&& X) { return F(std::forward<decltype(X)>(X)); };

Perfect-forwading Lambda는 여러개의 인자를 가지는 경우도 가능하다.
C++ 14에서 LambdaVariadic(가변적으로 인자들을 가질 수 있는)으로 쓸 수 있다.

auto PFL = [](auto&&.... param) { return F(std::forward<decltype(param)>(param)...)); };


Things to Remember
*  std::forward<T>에 auto&&을 넣을 땐 decltype을 사용하자.