Value categories
각 C++ expression (연산자와 피연산자, 리터럴, 변수명 등)은 두 가지 독립적인 속성으로 특징지어집니다: type 과 value category . 각 표현식은 일부 비-참조 타입을 가지며, 각 표현식은 세 가지 기본 값 범주 중 정확히 하나에 속합니다: prvalue , xvalue , 그리고 lvalue .
-
- 내장 연산자의 피연산자 값을 계산합니다(이러한 prvalue는 결과 객체 를 가지지 않음), 또는
- 객체를 초기화합니다(이러한 prvalue는 결과 객체 를 가진다고 말함).
-
결과 객체는 변수,
new-expression
으로 생성된 객체,
temporary materialization
으로 생성된 임시 객체, 또는 그 구성원일 수 있습니다. non-
void
discarded
표현식은 결과 객체(구체화된 임시 객체)를 가짐에 유의하십시오. 또한 모든 클래스 및 배열 prvalue는
decltype의 피연산자인 경우를 제외하고 결과 객체를 가집니다.
| 확장된 내용 |
|---|
|
역사적으로 lvalue가 대입 표현식의 좌변에 나타날 수 있었기 때문에 이러한 이름이 붙었습니다. 그러나 일반적으로 항상 그런 것은 아닙니다: void foo(); void baz() { int a; // Expression `a` is lvalue a = 4; // OK, could appear on the left-hand side of an assignment expression int &b{a}; // Expression `b` is lvalue b = 5; // OK, could appear on the left-hand side of an assignment expression const int &c{a}; // Expression `c` is lvalue c = 6; // ill-formed, assignment of read-only reference // Expression `foo` is lvalue // address may be taken by built-in address-of operator void (*p)() = &foo; foo = baz; // ill-formed, assignment of function } |
- rvalue 는 prvalue 또는 xvalue입니다;
| 확장된 내용 |
|---|
|
역사적으로 rvalue가 대입 표현식의 우변에 나타날 수 있어서 그러한 이름이 붙었습니다. 그러나 일반적으로 항상 그런 것은 아닙니다:
이 코드 실행
#include <iostream> struct S { S() : m{42} {} S(int a) : m{a} {} int m; }; int main() { S s; // Expression `S{}` is prvalue // May appear on the right-hand side of an assignment expression s = S{}; std::cout << s.m << '\n'; // Expression `S{}` is prvalue // Can be used on the left-hand side too std::cout << (S{} = S{7}).m << '\n'; } 출력: 42 7 |
참고: 이 분류 체계는 이전 C++ 표준 개정판을 통해 상당한 변화를 겪었으며, 자세한 내용은 아래 History 를 참조하십시오.
| 확장된 내용 |
|---|
|
이름과는 달리, 이러한 용어들은 값이 아닌 표현식을 분류합니다.
이 코드 실행
#include <type_traits> #include <utility> template <class T> struct is_prvalue : std::true_type {}; template <class T> struct is_prvalue<T&> : std::false_type {}; template <class T> struct is_prvalue<T&&> : std::false_type {}; template <class T> struct is_lvalue : std::false_type {}; template <class T> struct is_lvalue<T&> : std::true_type {}; template <class T> struct is_lvalue<T&&> : std::false_type {}; template <class T> struct is_xvalue : std::false_type {}; template <class T> struct is_xvalue<T&> : std::false_type {}; template <class T> struct is_xvalue<T&&> : std::true_type {}; int main() { int a{42}; int& b{a}; int&& r{std::move(a)}; // Expression `42` is prvalue static_assert(is_prvalue<decltype((42))>::value); // Expression `a` is lvalue static_assert(is_lvalue<decltype((a))>::value); // Expression `b` is lvalue static_assert(is_lvalue<decltype((b))>::value); // Expression `std::move(a)` is xvalue static_assert(is_xvalue<decltype((std::move(a)))>::value); // Type of variable `r` is rvalue reference static_assert(std::is_rvalue_reference<decltype(r)>::value); // Type of variable `b` is lvalue reference static_assert(std::is_lvalue_reference<decltype(b)>::value); // Expression `r` is lvalue static_assert(is_lvalue<decltype((r))>::value); } |
목차 |
주요 카테고리
lvalue
다음 표현식들은 lvalue expressions 입니다:
- 변수, 함수 , template parameter object (since C++20) , 또는 데이터 멤버의 이름(타입에 관계없이), 예를 들어 std:: cin 또는 std:: hex . 변수의 타입이 rvalue reference인 경우에도, 해당 이름으로 구성된 표현식은 lvalue 표현식입니다(단, Move-eligible expressions 참조);
| 확장된 내용 |
|---|
void foo() {} void baz() { // `foo` is lvalue // address may be taken by built-in address-of operator void (*p)() = &foo; } struct foo {}; template <foo a> void baz() { const foo* obj = &a; // `a` is an lvalue, template parameter object } |
- 반환 타입이 lvalue 참조인 함수 호출 또는 오버로드된 연산자 표현식, 예를 들어 std:: getline ( std:: cin , str ) , std:: cout << 1 , str1 = str2 , 또는 ++ it ;
| 확장된 내용 |
|---|
int& a_ref() { static int a{3}; return a; } void foo() { a_ref() = 5; // `a_ref()`는 lvalue이며, 반환 타입이 lvalue 참조인 함수 호출 } |
- a = b , a + = b , a % = b , 그리고 다른 모든 내장 대입 및 복합 대입 표현식;
- ++ a 와 -- a , 내장 전위 증가 및 전위 감소 표현식;
- * p , 내장 간접 참조 표현식;
- a [ n ] 와 p [ n ] , 내장 첨자 표현식 , 여기서 a [ n ] 의 한 피연산자가 배열 lvalue인 경우 (C++11부터) ;
-
a.
m
,
객체 멤버 접근
표현식, 단
m이 멤버 열거자나 비정적 멤버 함수인 경우, 또는 a 가 rvalue이고m이 객체 타입의 비정적 데이터 멤버인 경우는 제외;
| 확장된 내용 |
|---|
struct foo { enum bar { m // member enumerator }; }; void baz() { foo a; a.m = 42; // ill-formed, lvalue required as left operand of assignment } struct foo { void m() {} // non-static member function }; void baz() { foo a; // `a.m` is a prvalue, hence the address cannot be taken by built-in // address-of operator void (foo::*p1)() = &a.m; // ill-formed void (foo::*p2)() = &foo::m; // OK: pointer to member function } struct foo { static void m() {} // static member function }; void baz() { foo a; void (*p1)() = &a.m; // `a.m` is an lvalue void (*p2)() = &foo::m; // the same } |
-
p
-
>
m
, 내장
멤버 포인터 접근
표현식. 단,
m이 멤버 열거형이나 비정적 멤버 함수인 경우는 제외; -
a.
*
mp
,
객체의 멤버 포인터 접근
표현식. 여기서
a
는 lvalue이고
mp는 데이터 멤버 포인터; -
p
-
>
*
mp
, 내장
포인터의 멤버 포인터 접근
표현식. 여기서
mp는 데이터 멤버 포인터; - a, b , 내장 쉼표 표현식. 여기서 b 는 lvalue;
- a ? b : c , 특정 b 와 c 에 대한 삼항 조건 표현식 (예: 둘 다 동일한 타입의 lvalue인 경우, 하지만 자세한 내용은 정의 참조);
- 문자열 리터럴 , 예를 들어 "Hello, world!" ;
- lvalue 참조 타입으로의 캐스트 표현식, 예를 들어 static_cast < int & > ( x ) 또는 static_cast < void ( & ) ( int ) > ( x ) ;
- lvalue 참조 타입의 상수 템플릿 매개변수 ;
template <int& v> void set() { v = 5; // 템플릿 매개변수는 lvalue입니다 } int a{3}; // 정적 변수, 컴파일 타임에 고정 주소가 알려짐 void foo() { set<a>(); }
|
(C++11부터) |
속성:
- 아래의 glvalue 와 동일합니다.
- lvalue의 주소는 내장 주소 연산자로 취할 수 있습니다: & ++ i [1] 와 & std:: hex 는 유효한 표현식입니다.
- 수정 가능한 lvalue는 내장 대입 연산자와 복합 대입 연산자의 좌측 피연산자로 사용될 수 있습니다.
- lvalue는 lvalue 참조를 초기화 하는 데 사용될 수 있습니다; 이는 표현식으로 식별된 객체에 새로운 이름을 연관시킵니다.
prvalue
다음 표현식들은 prvalue expressions 입니다:
- 리터럴 (literal) ( 문자열 리터럴 제외), 예를 들어 42 , true 또는 nullptr ;
- 반환 타입이 비-참조형인 함수 호출 또는 오버로드된 연산자 표현식, 예를 들어 str. substr ( 1 , 2 ) , str1 + str2 , 또는 it ++ ;
- a ++ 와 a -- , 내장 후위 증가 및 후위 감소 표현식;
- a + b , a % b , a & b , a << b , 그리고 다른 모든 내장 산술 표현식;
- a && b , a || b , ! a , 내장 논리 표현식;
- a < b , a == b , a >= b , 그리고 다른 모든 내장 비교 표현식;
- & a , 내장 주소 연산자 표현식;
-
a.
m
,
객체 멤버 접근
표현식, 여기서
m은 멤버 열거자 또는 비정적 멤버 함수임 [2] ; -
p
-
>
m
, 내장
포인터 멤버 접근
표현식, 여기서
m은 멤버 열거자 또는 비정적 멤버 함수임 [2] ; -
a.
*
mp
,
객체 멤버 포인터 접근
표현식, 여기서
mp는 멤버 함수 포인터임 [2] ; -
p
-
>
*
mp
, 내장
포인터 멤버 포인터 접근
표현식, 여기서
mp는 멤버 함수 포인터임 [2] ; - a, b , 내장 쉼표 연산자 표현식, 여기서 b 는 prvalue임;
- a ? b : c , 삼항 조건 연산자 표현식 (특정 b 와 c 에 대해, 자세한 내용은 정의 참조);
- 비-참조형으로의 캐스트 표현식, 예를 들어 static_cast < double > ( x ) , std:: string { } , 또는 ( int ) 42 ;
-
this포인터; - 열거자 ;
- 스칼라 타입의 상수 템플릿 매개변수 ;
template <int v> void foo() { // lvalue가 아님, `v`는 스칼라 타입 int의 템플릿 매개변수 const int* a = &v; // 잘못된 형식 v = 3; // 잘못된 형식: 대입 연산의 왼쪽 피연산자로 lvalue가 필요함 }
|
(C++11부터) |
|
(C++20부터) |
속성:
- rvalue 와 동일합니다(아래 참조).
- prvalue는 다형적 일 수 없습니다: 이것이 나타내는 객체의 동적 타입 은 항상 표현식의 타입입니다.
- 비클래스 비배열 prvalue는 cv-qualified 될 수 없습니다 , cv-qualified 타입에 대한 참조에 바인딩 되기 위해 materialized 되는 경우를 제외하고 (C++17부터) . (참고: 함수 호출이나 캐스트 표현식은 비클래스 cv-qualified 타입의 prvalue를 결과로 낼 수 있지만, cv-qualifier는 일반적으로 즉시 제거됩니다.)
-
prvalue는
불완전 타입
을 가질 수 없습니다
(타입
void
인 경우(아래 참조)나
decltype지정자에서 사용되는 경우는 제외). - prvalue는 추상 클래스 타입 또는 그것의 배열을 가질 수 없습니다.
xvalue
다음 표현식들은 xvalue expressions 입니다:
-
a.
m
,
객체 멤버
표현식. 여기서
a
는 rvalue이고
m은 객체 타입의 비정적 데이터 멤버임; -
a.
*
mp
,
객체 멤버 포인터
표현식. 여기서
a
는 rvalue이고
mp는 데이터 멤버 포인터임; - a, b , 내장 쉼표 연산자 표현식. 여기서 b 는 xvalue임;
- a ? b : c , 삼항 조건 연산자 표현식 (특정 b 와 c 에 대해 적용됨, 자세한 내용은 정의 참조);
|
(C++11부터) |
|
(C++17부터) |
| (C++23부터) |
속성:
- rvalue(아래 참조)와 동일합니다.
- glvalue(아래 참조)와 동일합니다.
특히, 모든 rvalue와 마찬가지로 xvalue는 rvalue reference에 바인딩되며, 모든 glvalue와 마찬가지로 xvalue는 polymorphic 일 수 있고, 비클래스 xvalue는 cv-qualified 될 수 있습니다.
| 확장 콘텐츠 |
|---|
|
이 코드 실행
#include <type_traits> template <class T> struct is_prvalue : std::true_type {}; template <class T> struct is_prvalue<T&> : std::false_type {}; template <class T> struct is_prvalue<T&&> : std::false_type {}; template <class T> struct is_lvalue : std::false_type {}; template <class T> struct is_lvalue<T&> : std::true_type {}; template <class T> struct is_lvalue<T&&> : std::false_type {}; template <class T> struct is_xvalue : std::false_type {}; template <class T> struct is_xvalue<T&> : std::false_type {}; template <class T> struct is_xvalue<T&&> : std::true_type {}; // C++23 표준 예제: 7.2.1 값 범주 [basic.lval] struct A { int m; }; A&& operator+(A, A); A&& f(); int main() { A a; A&& ar = static_cast<A&&>(a); // 반환 타입이 rvalue 참조인 함수 호출은 xvalue입니다 static_assert(is_xvalue<decltype( (f()) )>::value); // 객체 표현식의 멤버, 객체가 xvalue이고 `m`은 비정적 데이터 멤버입니다 static_assert(is_xvalue<decltype( (f().m) )>::value); // rvalue 참조로의 캐스트 표현식 static_assert(is_xvalue<decltype( (static_cast<A&&>(a)) )>::value); // 연산자 표현식, 반환 타입이 객체에 대한 rvalue 참조입니다 static_assert(is_xvalue<decltype( (a + a) )>::value); // 표현식 `ar`은 lvalue이며, `&ar`은 유효합니다 static_assert(is_lvalue<decltype( (ar) )>::value); [[maybe_unused]] A* ap = &ar; } |
혼합 범주
glvalue
A glvalue expression 은 lvalue이거나 xvalue입니다.
속성:
- glvalue는 lvalue-to-rvalue, array-to-pointer, function-to-pointer 암시적 변환 을 통해 prvalue로 암시적으로 변환될 수 있습니다.
- glvalue는 다형적 일 수 있습니다: 식별하는 객체의 동적 타입 이 표현식의 정적 타입과 반드시 일치하지는 않습니다.
- glvalue는 표현식에서 허용되는 경우 불완전 타입 을 가질 수 있습니다.
rvalue
rvalue 표현식 은 prvalue이거나 xvalue입니다.
속성:
- 우측값의 주소는 내장 주소 연산자로 취할 수 없습니다: & int ( ) , & i ++ [3] , & 42 , 그리고 & std :: move ( x ) 는 유효하지 않습니다.
- 우측값은 내장 대입 연산자나 복합 대입 연산자의 좌측 피연산자로 사용될 수 없습니다.
- 우측값은 const 좌측값 참조를 초기화 하는 데 사용될 수 있으며, 이 경우 우측값으로 식별되는 임시 객체의 수명은 참조의 범위가 끝날 때까지 확장 됩니다.
|
(C++11부터) |
특수 카테고리
보류 중인 멤버 함수 호출
표현식
a.
mf
와
p
-
>
mf
(여기서
mf
는
비정적 멤버 함수
) 그리고 표현식
a.
*
pmf
와
p
-
>
*
pmf
(여기서
pmf
는
멤버 함수 포인터
)는 prvalue 표현식으로 분류되지만, 이러한 표현식들은 참조를 초기화하거나 함수 인수로 사용하거나 다른 어떤 목적으로도 사용할 수 없으며, 오직 함수 호출 연산자의 좌측 인수로만 사용될 수 있습니다. 예를 들어
(
p
-
>
*
pmf
)
(
args
)
와 같이 사용됩니다.
void 표현식
void 를 반환하는 함수 호출 표현식, void 로의 캐스트 표현식, 그리고 throw-표현식 은 prvalue 표현식으로 분류되지만, 참조를 초기화하거나 함수 인수로 사용할 수 없습니다. 이들은 폐기 값 컨텍스트(예: 단독 라인, 쉼표 연산자의 좌측 피연산자 등)와 void 를 반환하는 함수의 return 문에서 사용될 수 있습니다. 또한 throw-표현식은 조건부 연산자 ?: 의 두 번째와 세 번째 피연산자로 사용될 수 있습니다.
|
void 표현식은 result object 를 가지지 않습니다. |
(since C++17) |
비트 필드
비트 필드 를 지정하는 표현식(예: a. m , 여기서 a 는 struct A { int m : 3 ; } 타입의 lvalue임)은 glvalue 표현식입니다: 이는 대입 연산자의 좌측 피연산자로 사용될 수 있지만, 주소를 취할 수 없으며 비-const lvalue 참조는 이에 바인딩될 수 없습니다. const lvalue 참조 또는 rvalue 참조는 비트 필드 glvalue로부터 초기화될 수 있지만, 비트 필드의 임시 복사본이 생성됩니다: 직접 비트 필드에 바인딩되지 않습니다.
이동 가능 표현식변수 이름으로 구성된 표현식은 lvalue 표현식이지만, 다음과 같은 피연산자로 나타날 경우 이동 가능 표현식이 될 수 있습니다: 표현식이 이동 가능한 경우, 오버로드 해결 목적상 rvalue 또는 lvalue로 처리됩니다 (C++23 이전) rvalue로 처리됩니다 (C++23부터) (따라서 이동 생성자 를 선택할 수 있습니다). 자세한 내용은 지역 변수 및 매개변수에서의 자동 이동 을 참조하십시오. |
(C++11부터) |
역사
CPL
프로그래밍 언어 CPL 은 표현식에 대한 값 카테고리를 최초로 도입했습니다: 모든 CPL 표현식은 "우측 모드"에서 평가될 수 있지만, 특정 종류의 표현식만이 "좌측 모드"에서 의미를 가집니다. 우측 모드에서 평가될 때, 표현식은 값(우측 값, 또는 rvalue )의 계산 규칙으로 간주됩니다. 좌측 모드에서 평가될 때 표현식은 효과적으로 주소(좌측 값, 또는 lvalue )를 제공합니다. 여기서 "좌측"과 "우측"은 "할당의 좌측"과 "할당의 우측"을 의미했습니다.
C
C 프로그래밍 언어도 유사한 분류 체계를 따랐으나, 할당의 역할은 더 이상 중요하지 않았습니다: C 표현식은 "lvalue 표현식"과 그 외(함수 및 비객체 값)로 구분되며, "lvalue"는 객체를 식별하는 표현식, 즉 "위치 지정자 값"을 의미합니다 [4] .
C++98
2011년 이전 C++은 C 모델을 따랐지만, 비-좌측값 표현식에 "우측값"이라는 이름을 복원하고, 함수를 좌측값으로 만들었으며, 참조는 좌측값에 바인딩될 수 있지만 const에 대한 참조만이 우측값에 바인딩될 수 있다는 규칙을 추가했습니다. 여러 비-좌측값 C 표현식이 C++에서 좌측값 표현식이 되었습니다.
C++11
C++11에서 이동 의미론이 도입되면서, 표현식의 두 가지 독립적인 속성을 특성화하기 위해 값 범주가 재정의되었습니다 [5] :
- identity를 가짐 : 표현식이 다른 표현식과 동일한 개체를 참조하는지 여부를 확인할 수 있음 (예: 객체의 주소나 식별하는 함수의 주소를 직접 또는 간접적으로 비교하여);
- 이동 가능함 : move constructor , move assignment operator , 또는 이동 의미론을 구현하는 다른 함수 오버로드가 표현식에 바인딩될 수 있음.
C++11에서, 다음과 같은 표현식들은:
- identity를 가지고 이동될 수 없는 표현식은 lvalue 표현식이라고 합니다;
- identity를 가지고 이동될 수 있는 표현식은 xvalue 표현식이라고 합니다;
- identity를 가지지 않고 이동될 수 있는 표현식은 prvalue ("pure rvalue") 표현식이라고 합니다;
- identity를 가지지 않고 이동될 수 없는 표현식은 사용되지 않습니다 [6] .
식별자를 가지는 표현식은 "glvalue 표현식"이라고 합니다(glvalue는 "일반화된 lvalue"를 의미합니다). lvalue와 xvalue 모두 glvalue 표현식입니다.
이동될 수 있는 표현식들을 "rvalue 표현식"이라고 합니다. prvalue와 xvalue 모두 rvalue 표현식입니다.
C++17
C++17에서, copy elision 은 일부 상황에서 필수화되었으며, 이는 prvalue 표현식과 이들로 초기화된 임시 객체의 분리를 요구하게 되어 오늘날 우리가 가진 시스템으로 이어졌습니다. C++11 방식과 대조적으로, prvalue는 더 이상 이동되지 않는다는 점에 유의하십시오.
각주
- ↑ i 가 내장 타입을 가지거나 전위 증가 연산자가 오버로드 되어 lvalue 참조를 반환한다고 가정합니다.
- ↑ 2.0 2.1 2.2 2.3 특수 rvalue 카테고리, pending member function call 을 참조하십시오.
- ↑ i 가 내장 타입을 가지거나 후위 증가 연산자가 lvalue 참조를 반환하도록 오버로드 되지 않았다고 가정합니다.
- ↑ "C 커뮤니티 내에서 lvalue의 의미를 둘러싼 의견 차이가 있었는데, 한 그룹은 lvalue를 모든 종류의 객체 위치 지정자로 간주한 반면, 다른 그룹은 lvalue가 할당 연산자의 왼쪽에서 의미가 있어야 한다고 주장했습니다. C89 위원회는 lvalue를 객체 위치 지정자로 정의하는 것을 채택했습니다." -- ANSI C Rationale, 6.3.2.1/10.
- ↑ "New" Value Terminology by Bjarne Stroustrup, 2010.
-
↑
const prvalue(클래스 타입에만 허용됨)와 const xvalue는
T&&오버로드에 바인딩되지 않지만, const T && 오버로드에 바인딩됩니다. 이러한 오버로드도 표준에 의해 "move constructor"와 "move assignment operator"로 분류되며, 이 분류의 목적을 위해 "이동될 수 있음"의 정의를 충족시킵니다. 그러나 이러한 오버로드는 인자를 수정할 수 없으며 실제로 사용되지 않습니다; 이러한 오버로드가 없을 경우 const prvalue와 const xvalue는 const T & 오버로드에 바인딩됩니다.
참조문헌
- C++23 표준 (ISO/IEC 14882:2024):
-
- 7.2.1 값 범주 [basic.lval]
- C++20 표준(ISO/IEC 14882:2020):
-
- 7.2.1 값 범주 [basic.lval]
- C++17 표준(ISO/IEC 14882:2017):
-
- 6.10 Lvalues와 rvalues [basic.lval]
- C++14 표준(ISO/IEC 14882:2014):
-
- 3.10 Lvalues와 rvalues [basic.lval]
- C++11 표준(ISO/IEC 14882:2011):
-
- 3.10 Lvalues와 rvalues [basic.lval]
- C++98 표준(ISO/IEC 14882:1998):
-
- 3.10 Lvalues와 rvalues [basic.lval]
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 616 | C++11 |
rvalue의 멤버 접근 및 멤버 포인터를 통한 멤버 접근이
prvalue를 결과로 냄 |
xvalue로 재분류 |
| CWG 1059 | C++11 | 배열 prvalue는 cv-qualified될 수 없었음 | 허용됨 |
| CWG 1213 | C++11 | 배열 rvalue의 첨자 연산이 lvalue를 결과로 냄 | xvalue로 재분류 |
참고 항목
|
C documentation
for
value categories
|
외부 링크
| 1. | C++ 값 카테고리와 decltype의 이해 — David Mazières, 2021 | |
| 2. |
표현식의 값 카테고리 실험적 결정 방법
— StackOverflow
|