C++ 문법 정리 - 6 glvalue, xvalue, prvalue, decltype, declval
문법 정리
Value Category
이전엔 lvalue, rvalue로만 값을 구분했지만 사실 C++에서 엄밀하게 정의하자면 세 가지의 값의 구분이 있다.
정체를 알 수 있냐, 이동시킬 수 있냐로 다음과 같은 표와 벤다이어그렘이 형성된다.
이동 가능 | 이동 불가 | |
---|---|---|
정체 알 수 있다 | xvalue | lvalue |
정체 알 수 없다 | prvalue | X |
- 정체를 알 수 있는것은 주소값 등을 이용하여 해당 식이 다른 식과 같은 것인지 아닌지를 구분할 수 있음을 의미한다.
- 이동시킬 수 있는것은 이동 생성자나 이동 대입 연산자등을 이용할 수 있어야 한다.
그러니까 우리가 원래 정의했던 lvalue, rvalue는 사실 rvalue란 카테고리는 이동을 할 수 있는 값(연산자)들이였고, lvalue는 glvalue는 generalized lvalue라는 개념이 도입되어 정체를 알 수 있는지 여부로 다시 분류되었다.
이름을 가진 대부분의 객체가 &&
연산자에 대입될 수 없기 때문에 이동할 수 없기 때문에 lvalue
이다.
lvalue
- 변수, 함수의 이름, 어떤 타입의 데이터 멤버 (예컨대
std::endl
, std::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 + b
,a && b
,a < 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;
}
decltype
은 xvalue
, 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())
처럼 선언해주면 가상의 객체를 만드는것처럼 하여 타입만 만들어 활용할 수 있다.
Comments