Programming/c++

[c++] Value Category ( lvalue, prvalue, xvalue ) , decltype함수

owls 2023. 10. 20. 16:16
728x90

타입과 value category는 다른 개념입니다!

 

Value Category

c++에서는 5가지의 값 카테고리가 존재합니다.

- lvalue ( left value ) : 좌측값

- rvalue (right value ) : 우측값

- prvalue ( pure rvalue ) : 순수 우측값

- xvalue ( expiring  value) : 소멸하는 값

- glvalue ( generalized lvalue) : 일반화된 좌측값

즉, 값 카테고리는 좌측값/우측값을 말합니다.

 

값 카테고리를 구분하기 위해 2가지 조건이 있습니다.

- 정체를 알 수 있는가?

 : 해당 식이 다른 식과 같은지 다른지 구분할 수 있다.

   일반적인 변수라면 주소값을 취해서 구분할 수 있고, 함수의 경우라면 이름만 확인하면 된다.

 

- 이동 시킬 수 있는가?

 : 해당 식을 다른 곳으로 안전하게 이동할 수 있는지 여부.

   해당 식을 받는 이동 생성자, 이동 대입 연산자 등을 사용할 수 있어야 한다.

  이동 시킬 수 있다 이동 시킬 수 없다
정체를 알 수 있다 xvalue lvalue
정체를 알 수 없다 prvalue 쓸모 없음

정체를 알 수 있는 모든 식 : glvalue

이동 시킬 수 있는 모든 식 : rvalue

 

lvalue

이름을 가진 대부분의 객체들은 모두 lvalue입니다. 해당 객체의 주소값을 취할 수 있기 때문입니다.

1. 변수, 함수의 이름, 어떤 타입의 데이터 멤버 : std::endl, std::cin 등등
2. 좌측값 레퍼런스를 리턴하는 함수의 호출식 : std::cout << l, ++it 등등
3. 복합 대입 연산자 식 : a = b, a+= b, a *= b
4. 전위 증감 연산자 식 : ++a, --a 
5. 멤버를 참조할 때 : 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임

lvalue들은 주소값 연산자(&)를 통해 해당 식의 주소값을 알아 낼 수 있습니다. 

또한, 좌측값 레퍼런스를 초기화하는데 사용할 수 있습니다.

 

 

prvalue

정체를 알 수 없기에 주소값을 취할 수 없습니다.

&a++, &42는 모두 오류입니다.

prvalue는 좌측에 올 수 없습니다. 

prvalue는 우측값 레퍼런스와 상수 좌측값 레퍼런스를 초기화하는데 사용할 수 있습니다.

1. 문자열 리터럴을 제외한 모든 리터럴들 : 42, true, nullptr 등등
2. 레퍼런스가 아닌 것을 리턴하는 함수의 호출식 : str.substr(1, 2), str1 + str1
3. 후위 증감 연산자 식 : a++, a--
4. 산술 연산자, 논리 연산자 : a + b, a && b, a < b 
5. 주소값 연산자 식 : &a
6. 멤버 참조 : a.m, p->m ( m은 enum 값이거나 static 이 아닌 멤버 함수)
7. this
8. enum 값
9. 람다식 : []() {return 0;};

 

xvalue

좌측값처럼 정체가 있지만 이동도 시킬 수 있는 것

우측값 레퍼런스를 리턴하는 함수의 호출식 : std::move(x)

 

예제

int i = 40;
auto && ri = i;		// ri는 lvalue, lvalue인 i가 들어왔기 때문. int& ri 
auto && ri2 = 42;	// ri2는 rvalue. int&& ri2
auto && ri3 = std::move(i); // ri3는 rvalue.  int && ri3

 

decltype

식의 구체적인 타입 그대로 전달하는(추출하는) 키워드

- 식의 값 종류가 xvalue 라면 decltype 는 T&& 입니다.
- 식의 값 종류가 lvalue 라면 decltype 는 T& 입니다.
- 식의 값 종류가 prvalue 라면 decltype는 T 입니다.

 

컴파일 타임에 auto가 타입을 추론하기에 역부족일 때 주로 사용.

런타임이 아닌 컴파일 타임에 결정된다.

 

decltype : 변수가 선언된 타입 그대로 가져온다. const, & 까지

decltype((x)) : 괄호가 두개면 래퍼런스(&)를 추가로 붙여준다.

typedef decltype((x)) cx_typee; //&을 추가로 붙여서 int &
typedef decltype((cx)) cx_with_parens_type; //&을 추가로 붙여서 const int &
typedef decltype((crx)) crx_with_parens_type; //const int &
					    //원래 &가 있는 상태면 그대로 & 유지함.
typedef decltype((p->m_x)) m_x_with_parens_type;
	//&가 추가로 붙는데 그냥 int &가 되는게 아닌 const 까지 따라와서 const int &가 된다.
	//p는 const이기 때문에 간접참조로 멤버의 값을 바꾸면 안되기 때문에 & 레퍼런스가 되니 값을 바꾸면 안된다는 const까지 따라 온다.

 

 

 

참고 레퍼런스

https://modoocode.com/294

 

https://ansohxxn.github.io/cpp/chapter19-8/#%EC%98%88%EC%A0%9C-2%EF%B8%8F%E2%83%A3-decltype%EC%9D%80-%EB%A6%AC%ED%84%B4-%ED%83%80%EC%9E%85%EC%97%90%EB%8F%84-%EC%82%AC%EC%9A%A9-%EA%B0%80%EB%8A%A5-%ED%95%98%EB%8B%A4

 

728x90