값 범주(Value category)

값 범주(Value category)

C++에서 모든 표현식은 타입과 함께 **값 범주(value category)**라는 또 하나의 성질을 가져요. 같은 값이라도 그 값이 "이름이 있는 객체"인지, "이동해도 되는 임시 값"인지에 따라 할 수 있는 일이 달라져요. C++는 표현식을 세 가지 기본 값 범주, prvalue · xvalue · lvalue로 나누고, 이를 다시 정리한 glvalue · rvalue 범주도 있어요. 이 범주를 이해하면 이동 의미론(move semantics)과 참조 규칙이 훨씬 잘 보여요.

출처: cppreference

본문

각 C++ 표현식(연산자와 피연산자, 리터럴, 변수 이름 등)은 타입과 값 범주라는 두 독립적인 성질로 특징지어져요. 각 표현식은 어떤 비참조 타입을 가지고, 세 가지 기본 값 범주(prvalue, xvalue, lvalue) 중 정확히 하나에 속해요.

  • glvalue(“generalized” lvalue)는 평가(evaluation)가 객체나 함수의 정체성(identity)을 결정하는 표현식이에요.
  • prvalue(“pure” rvalue)는 평가가 다음 중 하나인 표현식이에요.
    • 내장 연산자의 피연산자 값을 계산하거나 (이런 prvalue는 결과 객체가 없어요), 또는
    • 객체를 초기화해요. (이런 prvalue는 결과 객체를 가진다고 말해요.)
  • xvalue(“eXpiring” value, 소멸 직전 값)는 자원을 재사용할 수 있는 객체를 가리키는 glvalue예요.
  • lvalue는 xvalue가 아닌 glvalue예요.

"lvalue"라는 이름은 역사적으로 lvalue가 대입 표현식의 왼쪽에 나타날 수 있어서 붙었어요. 그런데 항상 그런 건 아니에요.

void foo();

void baz()
{
    int a; // 표현식 `a`는 lvalue
    a = 4; // OK, 대입 표현식의 왼쪽에 나타날 수 있음

    int &b{a}; // 표현식 `b`는 lvalue
    b = 5; // OK, 대입 표현식의 왼쪽에 나타날 수 있음

    const int &c{a}; // 표현식 `c`는 lvalue
    c = 6;           // 잘못된 형태, 읽기 전용 참조에 대입

    // 표현식 `foo`는 lvalue
    // 내장 주소 연산자로 주소를 얻을 수 있음
    void (*p)() = &foo;

    foo = baz; // 잘못된 형태, 함수에 대입
}

또한,

  • rvalue는 prvalue 또는 xvalue예요.

"rvalue"라는 이름도 역사적으로 rvalue가 대입 표현식의 오른쪽에 나타날 수 있어서 붙었어요. 그런데 이것도 항상 그런 건 아니에요.

#include <iostream>

struct S
{
    S() : m{42} {}
    S(int a) : m{a} {}
    int m;
};

int main()
{
    S s;

    // 표현식 `S{}`는 prvalue
    // 대입 표현식의 오른쪽에 나타날 수 있음
    s = S{};

    std::cout << s.m << '\n';

    // 표현식 `S{}`는 prvalue
    // 왼쪽에도 쓸 수 있음
    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)};

    // 표현식 `42`는 prvalue
    static_assert(is_prvalue<decltype((42))>::value);

    // 표현식 `a`는 lvalue
    static_assert(is_lvalue<decltype((a))>::value);

    // 표현식 `b`는 lvalue
    static_assert(is_lvalue<decltype((b))>::value);

    // 표현식 `std::move(a)`는 xvalue
    static_assert(is_xvalue<decltype((std::move(a)))>::value);

    // 변수 `r`의 타입은 rvalue 참조
    static_assert(std::is_rvalue_reference<decltype(r)>::value);

    // 변수 `b`의 타입은 lvalue 참조
    static_assert(std::is_lvalue_reference<decltype(b)>::value);

    // 표현식 `r`은 lvalue
    static_assert(is_lvalue<decltype((r))>::value);
}

기본 범주(Primary categories)

lvalue

다음 표현식들은 lvalue 표현식이에요.

  • 변수, 함수, 템플릿 매개변수 객체(C++20부터), 또는 데이터 멤버의 이름. 타입은 무관해요. std::cin이나 std::endl 같은 것들. 변수의 타입이 rvalue 참조여도, 그 이름으로 이루어진 표현식은 lvalue 표현식이에요. (단, 이동 자격이 있는 표현식(Move-eligible expressions) 참고)
void foo() {}

void baz()
{
    // `foo`는 lvalue
    // 내장 주소 연산자로 주소를 얻을 수 있음
    void (*p)() = &foo;
}
struct foo {};

template <foo a>
void baz()
{
    const foo* obj = &a;  // `a`는 lvalue, 템플릿 매개변수 객체
}
  • 반환 타입이 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: 내장 간접 참조(indirection) 표현식.
  • a[n], p[n]: 내장 첨자 표현식. (a[n]에서 한 피연산자가 배열 lvalue인 경우, C++11부터)
  • a.m: 객체 멤버 표현식. 단, m이 멤버 열거자(enumerator)나 비정적 멤버 함수인 경우, 또는 a가 rvalue이고 m이 객체 타입의 비정적 데이터 멤버인 경우는 제외.
struct foo
{
    enum bar
    {
        m // 멤버 열거자
    };
};

void baz()
{
    foo a;
    a.m = 42; // 잘못된 형태, 대입의 왼쪽 피연산자로 lvalue 필요
}
struct foo
{
    void m() {} // 비정적 멤버 함수
};

void baz()
{
    foo a;

    // `a.m`은 prvalue이므로, 내장 주소 연산자로
    // 주소를 얻을 수 없음
    void (foo::*p1)() = &a.m; // 잘못된 형태

    void (foo::*p2)() = &foo::m; // OK: 멤버 함수 포인터
}
struct foo
{
    static void m() {} // 정적 멤버 함수
};

void baz()
{
    foo a;
    void (*p1)() = &a.m;     // `a.m`은 lvalue
    void (*p2)() = &foo::m;  // 동일
}
  • 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>();
}
  • 함수에 대한 rvalue 참조 타입을 반환하는 함수 호출이나 오버로드된 연산자 표현식.
  • 함수에 대한 rvalue 참조 타입으로의 캐스트 표현식, 예: static_cast<void(&&)(int)>(x). (C++11부터)

lvalue의 성질:

  • glvalue와 같아요. (아래 참고)
  • lvalue의 주소는 내장 주소 연산자로 얻을 수 있어요. &++i[1], &std::endl은 유효한 표현식이에요.
  • 수정 가능한 lvalue는 내장 대입 및 복합 대입 연산자의 왼쪽 피연산자로 쓸 수 있어요.
  • lvalue는 lvalue 참조를 초기화하는 데 쓸 수 있어요. 이는 표현식이 식별하는 객체에 새 이름을 연결해요.

prvalue

다음 표현식들은 prvalue 표현식이에요.

  • 문자열 리터럴을 제외한 리터럴, 예: 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 포인터.
  • 열거자(enumerator).
  • 스칼라 타입의 비타입 템플릿 매개변수.
template <int v>
void foo()
{
    // lvalue가 아님, `v`는 스칼라 타입 int의 템플릿 매개변수
    const int* a = &v; // 잘못된 형태

    v = 3; // 잘못된 형태: 대입의 왼쪽 피연산자로 lvalue 필요
}
  • 람다 표현식, 예: [](int x){ return x * x; }.
  • requires 표현식, 예: requires (T i) { typename T::type; }.
  • 개념(concept)의 특수화, 예: std::equality_comparable<int>. (C++11/C++20부터)

prvalue의 성질:

  • rvalue와 같아요. (아래 참고)
  • prvalue는 다형적(polymorphic)일 수 없어요. prvalue가 나타내는 객체의 동적 타입은 항상 표현식의 타입이에요.
  • 비클래스·비배열 prvalue는 cv-한정될 수 없어요. (참고: 함수 호출이나 캐스트 표현식은 비클래스 cv-한정 타입의 prvalue를 만들 수 있지만, cv-한정자는 즉시 제거돼요.)
  • prvalue는 불완전 타입(단 void 타입 제외, 아래 참고, 또는 decltype 지정자에서 쓰일 때 제외)을 가질 수 없어요.
  • prvalue는 추상 클래스 타입이나 그것의 배열을 가질 수 없어요.

xvalue

다음 표현식들은 xvalue 표현식이에요.

  • 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).
  • 임시 객체 물질화(temporary materialization) 후의 임시 객체를 지정하는 모든 표현식.
  • 이동 자격이 있는 표현식. (C++11/C++17/C++23부터)

xvalue의 성질:

  • rvalue와 같아요. (아래 참고)
  • glvalue와 같아요. (아래 참고)

특히, 모든 rvalue처럼 xvalue는 rvalue 참조에 바인딩되고, 모든 glvalue처럼 xvalue는 다형적일 수 있으며, 비클래스 xvalue는 cv-한정될 수 있어요.

#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;
}

혼합 범주(Mixed categories)

glvalue

glvalue 표현식은 lvalue이거나 xvalue예요.

성질:

  • glvalue는 lvalue-to-rvalue, 배열-포인터, 또는 함수-포인터 암시적 변환으로 prvalue로 암시적으로 변환될 수 있어요.
  • glvalue는 다형적일 수 있어요. glvalue가 식별하는 객체의 동적 타입은 표현식의 정적 타입과 반드시 같지 않아요.
  • glvalue는 표현식이 허용하는 곳에서 불완전 타입을 가질 수 있어요.

rvalue

rvalue 표현식은 prvalue이거나 xvalue예요.

성질:

  • rvalue의 주소는 내장 주소 연산자로 얻을 수 없어요. &int(), &i++[3], &42, &std::move(x)는 유효하지 않아요.
  • rvalue는 내장 대입 또는 복합 대입 연산자의 왼쪽 피연산자로 쓸 수 없어요.
  • rvalue는 const lvalue 참조를 초기화하는 데 쓸 수 있는데, 이 경우 rvalue가 식별하는 임시 객체의 수명은 참조의 스코프가 끝날 때까지 연장돼요.
  • rvalue는 rvalue 참조를 초기화하는 데 쓸 수 있는데, 이 경우 rvalue가 식별하는 임시 객체의 수명은 참조의 스코프가 끝날 때까지 연장돼요.
  • 함수 인자로 쓰일 때, rvalue 참조 매개변수를 받는 오버로드와 const lvalue 참조를 받는 오버로드 둘 다 있으면 rvalue는 rvalue 참조 오버로드에 바인딩돼요. (따라서 복사·이동 생성자 둘 다 있으면 rvalue 인자는 이동 생성자를 호출하고, 복사·이동 대입 연산자도 마찬가지예요.) (C++11부터)

특수 범주(Special categories)

대기 중인 멤버 함수 호출(Pending member function call)

표현식 a.mf, p->mf(mf는 비정적 멤버 함수)와 a.*pmf, p->*pmf(pmf는 멤버 함수 포인터)는 prvalue 표현식으로 분류되지만, 참조를 초기화하거나 함수 인자로 쓰거나 어떤 목적으로도 쓸 수 없어요. 함수 호출 연산자의 왼쪽 인자로만 쓸 수 있어요. 예: (p->*pmf)(args).

void 표현식(Void expressions)

void를 반환하는 함수 호출 표현식, void로의 캐스트 표현식, throw 표현식은 prvalue 표현식으로 분류되지만, 참조를 초기화하거나 함수 인자로 쓸 수 없어요. 버려진 값 문맥(discarded-value context, 예: 자신만의 줄, 쉼표 연산자의 왼쪽 피연산자 등)과 void를 반환하는 함수의 return 문에서 쓸 수 있어요. 게다가 throw 표현식은 조건부 연산자 ?:의 두 번째·세 번째 피연산자로 쓸 수 있어요.

void 표현식은 결과 객체가 없어요. (C++17부터)

비트 필드(Bit-fields)

비트 필드를 지정하는 표현식(예: a.m, 여기서 astruct A { int m: 3; } 타입의 lvalue)은 glvalue 표현식이에요. 대입 연산자의 왼쪽 피연산자로 쓸 수 있지만, 주소를 얻을 수 없고 비const lvalue 참조를 바인딩할 수 없어요. const lvalue 참조나 rvalue 참조는 비트 필드 glvalue로 초기화될 수 있지만, 비트 필드의 임시 복사본이 만들어질 거예요. 비트 필드에 직접 바인딩되지는 않아요.

이동 자격이 있는 표현식(Move-eligible expressions)

어떤 변수의 이름으로 이루어진 표현식은 항상 lvalue 표현식이지만, 다음의 피연산자로 나타나면 이동 자격이 있을 수 있어요.

  • return
  • co_return 문 (C++20부터)
  • throw 표현식 (C++17부터)

표현식이 이동 자격이 있으면, 오버로드 해석을 위해 (C++23부터는 rvalue로, until C++23은 rvalue 또는 lvalue로) 취급돼요. 그래서 이동 생성자를 선택할 수 있어요. 자세한 내용은 로컬 변수와 매개변수의 자동 이동을 참고해요. (C++11부터)

역사(History)

CPL

프로그래밍 언어 CPL이 처음으로 표현식에 값 범주를 도입했어요. 모든 CPL 표현식은 "오른쪽 모드(right-hand mode)"로 평가될 수 있지만, 특정 종류의 표현식만 "왼쪽 모드(left-hand mode)"에서 의미가 있어요. 오른쪽 모드로 평가되면, 표현식은 값(오른쪽 값, rvalue)의 계산 규칙으로 간주돼요. 왼쪽 모드로 평가되면 표현식은 사실상 주소(왼쪽 값, lvalue)를 제공해요. 여기서 "왼쪽"과 "오른쪽"은 "대입의 왼쪽"과 "대입의 오른쪽"을 뜻했어요.

C

C 프로그래밍 언어도 비슷한 분류를 따랐는데, 단지 대입의 역할이 더 이상 중요하지 않았다는 점이 달라요. C 표현식은 "lvalue 표현식"과 그 외(함수와 비객체 값)로 나뉘며, "lvalue"는 객체를 식별하는 표현식, 즉 "위치 값(locator value)"을 의미해요[4].

C++98

2011년 이전의 C++은 C 모델을 따랐지만, 비lvalue 표현식에 "rvalue"라는 이름을 되살리고, 함수를 lvalue로 만들었으며, 참조는 lvalue에만 바인딩될 수 있고 const 참조만 rvalue에 바인딩될 수 있다는 규칙을 추가했어요. C의 여러 비lvalue 표현식이 C++에서 lvalue 표현식이 됐어요.

C++11

C++11에서 이동 의미론이 도입되면서, 값 범주는 표현식의 두 독립적인 성질을 특징짓도록 재정의됐어요[5].

  • 정체성이 있다: 표현식이 다른 표현식과 같은 실체를 가리키는지 판별할 수 있다. 예를 들어, 그들이 식별하는 객체나 함수의 (직접 또는 간접적으로 얻은) 주소를 비교하는 방식으로.
  • 이동해 갈 수 있다: 이동 생성자, 이동 대입 연산자, 또는 이동 의미론을 구현하는 다른 함수 오버로드가 표현식에 바인딩될 수 있다.

C++11에서,

  • 정체성이 있고 이동해 갈 수 없는 표현식을 lvalue 표현식이라 불러요.
  • 정체성이 있고 이동해 갈 수 있는 표현식을 xvalue 표현식이라 불러요.
  • 정체성이 없고 이동해 갈 수 있는 표현식을 prvalue(“pure rvalue”) 표현식이라 불러요.
  • 정체성이 없고 이동해 갈 수 없는 표현식은 쓰이지 않아요[6].

정체성이 있는 표현식을 glvalue 표현식이라 불러요. (glvalue는 "generalized lvalue"를 뜻해요.) lvalue와 xvalue는 모두 glvalue 표현식이에요.

이동해 갈 수 있는 표현식을 rvalue 표현식이라 불러요. prvalue와 xvalue는 모두 rvalue 표현식이에요.

C++17

C++17에서 복사 생략(copy elision)이 어떤 상황에서 필수가 됐고, 그 때문에 prvalue 표현식을 그것이 초기화하는 임시 객체로부터 분리해야 했어요. 그 결과가 오늘날의 체계예요. C++11 체계와 달리, prvalue는 더 이상 이동되지 않는다는 점을 주의해요.

각주(Footnotes)

  • i가 내장 타입이거나 전위 증가 연산자가 lvalue 참조로 반환하도록 오버로드되었다고 가정.
  • ↑ 2.0 2.1 2.2 2.3 특수 rvalue 범주, 대기 중인 멤버 함수 호출 참고.
  • i가 내장 타입이거나 후위 증가 연산자가 lvalue 참조로 반환하도록 오버로드되지 않았다고 가정.
  • ↑ "C 커뮤니티 내에서는 lvalue의 의미에 대한 의견 차이가 있었는데, 한 그룹은 lvalue를 어떤 종류의 객체 위치 값으로 보았고, 다른 그룹은 lvalue가 대입 연산자의 왼쪽에서 의미가 있다고 보았어요. C89 위원회는 lvalue를 객체 위치 값으로 정의했어요." — ANSI C 합리화(Rationale), 6.3.2.1/10.
  • ↑ Bjarne Stroustrup, "'New' Value Terminology", 2010.
  • ↑ const prvalue(클래스 타입에만 허용)와 const xvalue는 T&& 오버로드에 바인딩되지 않지만, const T&& 오버로드에는 바인딩돼요. const T&&도 표준에 따르면 "move constructor"와 "move assignment operator"로 분류되어 이 분류를 위한 "이동해 갈 수 있다"의 정의를 만족해요. 하지만 그런 오버로드는 인자를 수정할 수 없고 실제로 쓰이지 않아요. 그런 오버로드가 없으면 const prvalue와 const xvalue는 const T& 오버로드에 바인딩돼요.

참고 자료(References)

  • C++23 표준 (ISO/IEC 14882:2024): 7.2.1 Value category [basic.lval]
  • C++20 표준 (ISO/IEC 14882:2020): 7.2.1 Value category [basic.lval]
  • C++17 표준 (ISO/IEC 14882:2017): 6.10 Lvalues and rvalues [basic.lval]
  • C++14 표준 (ISO/IEC 14882:2014): 3.10 Lvalues and rvalues [basic.lval]
  • C++11 표준 (ISO/IEC 14882:2011): 3.10 Lvalues and rvalues [basic.lval]
  • C++98 표준 (ISO/IEC 14882:1998): 3.10 Lvalues and rvalues [basic.lval]

결함 보고(Defect reports)

이전에 발표된 C++ 표준들에 소급 적용된 동작 변경 결함 보고들이 있어요.

DR 적용 대상 발표 당시 동작 올바른 동작
CWG 616 C++11 rvalue의 멤버 접근과 포인터-멤버 접근이 prvalue였음 xvalue로 재분류
CWG 1059 C++11 배열 prvalue는 cv-한정될 수 없었음 허용됨
CWG 1213 C++11 배열 rvalue의 첨자 표현이 lvalue였음 xvalue로 재분류

더 알아보기

  • std::move: lvalue를 xvalue로 만들어 이동을 가능하게 하는 함수.
  • 이동 의미론: rvalue 참조와 이동 생성자/대입 연산자의 개념.
  • 평가 순서(Order of evaluation): 표현식의 부분들이 평가되는 순서.
  • 참조 초기화: rvalue를 참조에 바인딩할 때의 수명 규칙.