1편, 2편, 3편, 4편, 5편에서 이어집니다.

문법 정리

Value Category

이전엔 lvalue, rvalue로만 값을 구분했지만 사실 C++에서 엄밀하게 정의하자면 세 가지의 값의 구분이 있다.

정체를 알 수 있냐, 이동시킬 수 있냐로 다음과 같은 표와 벤다이어그렘이 형성된다.

  이동 가능 이동 불가
정체 알 수 있다 xvalue lvalue
정체 알 수 없다 prvalue X

image.png

  • 정체를 알 수 있는것은 주소값 등을 이용하여 해당 식이 다른 식과 같은 것인지 아닌지를 구분할 수 있음을 의미한다.
  • 이동시킬 수 있는것은 이동 생성자나 이동 대입 연산자등을 이용할 수 있어야 한다.

그러니까 우리가 원래 정의했던 lvalue, rvalue는 사실 rvalue란 카테고리는 이동을 할 수 있는 값(연산자)들이였고, lvalue는 glvalue는 generalized lvalue라는 개념이 도입되어 정체를 알 수 있는지 여부로 다시 분류되었다.

이름을 가진 대부분의 객체가 && 연산자에 대입될 수 없기 때문에 이동할 수 없기 때문에 lvalue이다.

lvalue

  • 변수, 함수의 이름, 어떤 타입의 데이터 멤버 (예컨대 std::endlstd::cin) 등등
  • 좌측값 레퍼런스를 리턴하는 함수의 호출식. std::cout << 1 이나 ++it 같은 것들
  • a = b, a += b, a *= b 같이 복합 대입 연산자 식들
  • ++a, --a 같은 전위 증감 연산자 식들
  • a.m, p->m 과 같이 멤버를 참조할 때. 이 때 m 은 enum 값이거나 static 이 아닌 멤버 함수인 경우 제외. (아래 설명 참조)
class A {
  int f();         // static 이 아닌 멤버 함수
  static int g();  // static 인 멤버 함수
}

A a;
a.g;  // <-- lvalue
a.f;  // <-- lvalue 아님 (아래 나올 prvalue)
  • a[n] 과 같은 배열 참조 식들
  • 문자열 리터럴 "hi"

prvalue

prvalue는 pure rvalue의 준말이므로 순수한 이동가능한 값이라고 생각할 수 있다.

이친구들은 정체를 알 수 없지만 이동만 가능한 친구들이다.

당연히 & 연산자를 쓰거나 참조자로 얻어올 수는 없지만 && 로 rvalue 참조자로 받거나 const & 는 가능하다.

const int& r = 42;
int&& rr = 42;
// int& rrr = 42; <-- 불가능
  • 문자열 리터럴을 제외 한 모든 리터럴들. 42, true, nullptr 같은 애들
  • 레퍼런스가 아닌 것을 리턴하는 함수의 호출식. 예를 들어서 str.substr(1, 2), str1 + str2
  • 후위 증감 연산자 식. a++a--
  • 산술 연산자, 논리 연산자 식들. a + ba && ba < b 같은 것들을 말합니다. 물론, 이들은 연산자 오버로딩 된 경우들 말고 디폴트로 제공되는 것들을 말합니다.
  • 주소값 연산자 식 &a
  • a.m, p->m 과 같이 멤버를 참조할 때. 이 때 m 은 enum 값이거나 static 이 아닌 멤버 함수여야함.
  • this
  • enum 값
  • 람다식 []() { return 0;}; 과 같은 애들.

xvalue

std::move의 반환식 같은 경우 다음과 같은 정의를 가지며, lvalue reference 초기화, rvalue references초기화에 모두 가능하므로 xvalue라고 봐야한다.

template <class T>
constexpr typename std::remove_reference<T>::type&& move(T&& t) noexcept;

딱 lvalue, prvalue라고 구분지을 수 없이 모든 조건을 충족하기 때문이다.

decltype

decltype은 c++11에 추가된 키워드로 특정 expression의 타입을 알 수 있다.

int main() {  
   int a = 1;  
   decltype(a) b = 2;  
   cout << b;  
   return 0;  
}

decltypexvalue, prvalue, lvalue에 따라 반환값이 다르다.

  • xvalue: T&&
  • lvalue: T&
  • prvalue: T

decltype은 알고리즘을 할때 std::set의 comparator를 선언하면서 몇 번 써봤는데 좀 더 자세히 알아보아서 좋았다.

함수에서 다음과 같이 쓸 수도 있다.

template <typename T, typename U>
void add(T t, U u, decltype(t + u)* result) {
  *result = t + u;
}

그런데 다음과 같은 코드는 에러가 난다. t, u 를 아직 뭔지 컴파일러가 모르기 때문이다.

template <typename T, typename U>
decltype(t + u) add(T t, U u) {
  return t + u;
}

이것을 해결하려면 c++14에 추가된 -> 문법을 쓸 수 있다.

template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
  return t + u;
}

declval

declval은 c++11에 등장한 타입 관련 유틸리티로, 타입 T의 멤버 함수 f의 반환값을 나타내기 위해 decltype(T().f()) 처럼 쓸 수도 있지만 T가 항상 기본 생성자를 가지고는게 아니라면 이건 컴파일 에러가 난다.

이럴 때, decltype(declval<T>().f()) 처럼 선언해주면 가상의 객체를 만드는것처럼 하여 타입만 만들어 활용할 수 있다.


Categories:

Updated:

Comments