모두의 코드
C++ 레퍼런스 - std::forward 함수

작성일 : 2019-09-28 이 글은 742 번 읽혔습니다.

아직 C++ 에 친숙하지 않다면 씹어먹는 C++ 은 어때요?

std::forward

<utility> 에 정의됨

// 참고로 C++ 14 이전 버전에는 constexpr 이 아니다.
template <class T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;  // (1)

template <class T>
constexpr T&& forward(std::remove_reference_t<T>&& t) noexcept;  // (2)

1 번 오버로딩의 경우 lvalueT 에 따라 lvalue 혹은 rvalue 로 전달한다.

만일 t보편적 레퍼런스(Universal reference) 라면, (1) 번 오버로딩 함수의 경우 t 를 다른 함수에 값 카테고리를 그대로 유지한채 전달하게 된다. 참고로 보편적 레퍼런스는, 템플릿 인자 T 를 인자로 받는 우측값 레퍼런스로 예를 들어서;

template <class T>
void wrapper(T&& arg) {
  // arg is always lvalue
  foo(std::forward<T>(arg));  // Forward as lvalue or as rvalue, depending on T
}

위 경우 arg 가 보편적 레퍼런스 이다. (템플릿 인자 T 의 우측값 레퍼런스 이므로). 이 때 arg 자체는 lvalue 이므로 1 번 std::forward 가 오버로딩 된다.

  • 만일 wrapper()rvalue std::string 을 전달한다면, Tstd::string 으로 추론된다 (std::string&, const std::string&, std::string&& 이 아님!) 그리고 std::forwardfoo 에 우측값 레퍼런스를 전달하게 된다.

  • 만일 wrapper() 에 상수 lvalue std::string 을 전달한다면, Tconst std::string& 으로 추론되며, std::forwardfoo 에 상수 좌측값 레퍼런스를 전달한다.

  • 만일 wrapper() 에 상수가 아닌 lvalue std::string 을 전달한다면 Tstd::string& 으로 추론되고, std::forward 는 상수가 아닌 좌측값 레퍼런스를 foo 에 전달한다.

2 번 오버로딩의 경우 rvaluervalue 로 전달하고, rvaluelvalue 로 전달되는 것을 막는다.

사실 두 번째 오버로딩의 경우 필요한 이유가 조금 복잡하다. 예를 들어서 아래와 같은 상황을 생각해보자.

std::forward<T>(u.get());

만일 사용자가 u.get()lvalue 를 리턴할지, rvalue 를 리턴할지 모른다고 해보자. 하지만 T 가 좌측값 레퍼런스 타입이 아니라면, u.get() 이 리턴한 것을 이동 시키고 싶을 것이다. 반면에 T 가 좌측값 레퍼런스 타입이라면 u.get() 이 리턴한 것을 이동하면 안된다

사실 두 번째 형태의 forward 오버로딩을 사용할 경우는 거의 없을 테므로 굳이 걱정할 필요는 없다. 두 번째 형태의 forward 가 왜 필요한지 자세한 설명은 표준 문서를 읽어보는 것을 권장한다.

인자들

  • t : 전달할 객체

리턴값

static_cast<T&&>(t)

실헹 예제

#include <iostream>
#include <memory>
#include <utility>

struct A {
  A(int&& n) { std::cout << "rvalue overload, n=" << n << "\n"; }
  A(int& n) { std::cout << "lvalue overload, n=" << n << "\n"; }
};

class B {
 public:
  template <class T1, class T2, class T3>
  B(T1&& t1, T2&& t2, T3&& t3)
      : a1_{std::forward<T1>(t1)},
        a2_{std::forward<T2>(t2)},
        a3_{std::forward<T3>(t3)} {}

 private:
  A a1_, a2_, a3_;
};

template <class T, class U>
std::unique_ptr<T> make_unique1(U&& u) {
  return std::unique_ptr<T>(new T(std::forward<U>(u)));
}

template <class T, class... U>
std::unique_ptr<T> make_unique2(U&&... u) {
  return std::unique_ptr<T>(new T(std::forward<U>(u)...));
}

int main() {
  auto p1 = make_unique1<A>(2);  // rvalue
  int i = 1;
  auto p2 = make_unique1<A>(i);  // lvalue

  std::cout << "B\n";
  auto t = make_unique2<B>(2, i, 3);
}

실행 결과

rvalue overload, n=2
lvalue overload, n=1
B
rvalue overload, n=2
lvalue overload, n=1
rvalue overload, n=3

참고 자료

  • move : 객체의 우측값 레퍼런스를 얻는다.

  • move_if_noexcept : 생성자가 예외를 던지지 않을 경우에만 우측값 레퍼런스를 얻는다.

첫 댓글을 달아주세요!
프로필 사진 없음
강좌에 관련 없이 궁금한 내용은 여기를 사용해주세요