Namespaces
Variants

Value categories

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Value categories
Order of evaluation
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

각 C++ expression (연산자와 피연산자, 리터럴, 변수명 등)은 두 가지 독립적인 속성으로 특징지어집니다: type value category . 각 표현식은 일부 비-참조 타입을 가지며, 각 표현식은 세 가지 기본 값 범주 중 정확히 하나에 속합니다: prvalue , xvalue , 그리고 lvalue .

  • glvalue ("일반화된" lvalue)는 평가 시 객체나 함수의 정체성을 결정하는 표현식입니다;
  • prvalue ("순수한" rvalue)는 평가 시
  • 내장 연산자의 피연산자 값을 계산합니다(이러한 prvalue는 결과 객체 를 가지지 않음), 또는
  • 객체를 초기화합니다(이러한 prvalue는 결과 객체 를 가진다고 말함).
결과 객체는 변수, new-expression 으로 생성된 객체, temporary materialization 으로 생성된 임시 객체, 또는 그 구성원일 수 있습니다. non- void discarded 표현식은 결과 객체(구체화된 임시 객체)를 가짐에 유의하십시오. 또한 모든 클래스 및 배열 prvalue는 decltype 의 피연산자인 경우를 제외하고 결과 객체를 가집니다.
  • xvalue ("소멸 예정" 값)는 자원이 재사용될 수 있는 객체를 나타내는 glvalue입니다;
  • lvalue 는 xvalue가 아닌 glvalue입니다;
확장된 내용

역사적으로 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 입니다:

확장된 내용
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
}
template <int& v>
void set()
{
    v = 5; // 템플릿 매개변수는 lvalue입니다
}
int a{3}; // 정적 변수, 컴파일 타임에 고정 주소가 알려짐
void foo()
{
    set<a>();
}
  • 반환 타입이 함수에 대한 rvalue 참조인 함수 호출 또는 오버로드된 연산자 표현식;
  • 함수 타입에 대한 rvalue 참조로의 캐스트 표현식, 예를 들어 static_cast < void ( && ) ( int ) > ( x ) .
(C++11부터)

속성:

  • 아래의 glvalue 와 동일합니다.
  • lvalue의 주소는 내장 주소 연산자로 취할 수 있습니다: & ++ i [1] & std:: hex 는 유효한 표현식입니다.
  • 수정 가능한 lvalue는 내장 대입 연산자와 복합 대입 연산자의 좌측 피연산자로 사용될 수 있습니다.
  • lvalue는 lvalue 참조를 초기화 하는 데 사용될 수 있습니다; 이는 표현식으로 식별된 객체에 새로운 이름을 연관시킵니다.

prvalue

다음 표현식들은 prvalue expressions 입니다:

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 에 대해 적용됨, 자세한 내용은 정의 참조);
  • 객체에 대한 rvalue 참조를 반환 타입으로 갖는 함수 호출 또는 오버로드된 연산자 표현식, 예를 들어 std :: move ( x ) ;
  • a [ n ] , 내장 첨자 연산자 표현식으로서 한 피연산자가 배열 rvalue인 경우;
  • 객체 타입에 대한 rvalue 참조로의 캐스트 표현식, 예를 들어 static_cast < char && > ( x ) ;
(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 좌측값 참조를 초기화 하는 데 사용될 수 있으며, 이 경우 우측값으로 식별되는 임시 객체의 수명은 참조의 범위가 끝날 때까지 확장 됩니다.
  • rvalue는 rvalue 참조를 초기화 하는 데 사용될 수 있으며, 이 경우 rvalue로 식별되는 임시 객체의 수명은 참조의 범위가 끝날 때까지 연장 됩니다.
  • 함수 인자로 사용될 때, 그리고 함수의 두 개의 오버로드 가 사용 가능한 경우(하나는 rvalue 참조 매개변수를 취하고 다른 하나는 const에 대한 lvalue 참조 매개변수를 취함), rvalue는 rvalue 참조 오버로드에 바인딩됩니다(따라서 복사 생성자와 이동 생성자가 모두 사용 가능한 경우, rvalue 인수는 이동 생성자 를 호출하며, 복사 및 이동 할당 연산자에서도 마찬가지입니다).
(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는 더 이상 이동되지 않는다는 점에 유의하십시오.

각주

  1. i 가 내장 타입을 가지거나 전위 증가 연산자가 오버로드 되어 lvalue 참조를 반환한다고 가정합니다.
  2. 2.0 2.1 2.2 2.3 특수 rvalue 카테고리, pending member function call 을 참조하십시오.
  3. i 가 내장 타입을 가지거나 후위 증가 연산자가 lvalue 참조를 반환하도록 오버로드 되지 않았다고 가정합니다.
  4. "C 커뮤니티 내에서 lvalue의 의미를 둘러싼 의견 차이가 있었는데, 한 그룹은 lvalue를 모든 종류의 객체 위치 지정자로 간주한 반면, 다른 그룹은 lvalue가 할당 연산자의 왼쪽에서 의미가 있어야 한다고 주장했습니다. C89 위원회는 lvalue를 객체 위치 지정자로 정의하는 것을 채택했습니다." -- ANSI C Rationale, 6.3.2.1/10.
  5. "New" Value Terminology by Bjarne Stroustrup, 2010.
  6. 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