본문 바로가기

프로그래밍 언어/C++

value category 이해하는 첫 단추

반응형

개요

C++ 언어의 statement는 크게 declaration statement과 expression statement로 구분한다.

declaration statement는 새로운 name를 도입하고, 해당 이름을 어떻게 앞으로 해석해야 할지 명시하는 구문이다.

expression statement는 앞서 선언한 name를 사용해서 일반적으로 computation를 명시하는 statement다.

lambda expression는 명시적으로 declaration statement 구문없이 expression statement를 구성한다. declaration statement 과정이 없음으로 새로운 name을 추가하지 않고 곧바로 expression statement를 구성한다. 프로그래머에게 type declaration를 숨기는 효과를 갖는다. lambda expression으로 숨긴 타입을 closure type이라 한다.

제약 조건을 명시하는 constraint expression를 C++20 스펙에서 추가했다. constraint를 meet하는 여부를 boolean 타입 결과로 반환한다.

결국 expression으로 표현하려는 대상이 무엇이냐에 따라 다양한 형태의 expression statement를 명시할 수 있다.

value category라는 용어는 순수하게 computation expression 영역에서 정의한 용어다. computation expression 결과는 value과 side effect로 크게 나눈다. 결국 computation expression 연산 과정에서 참여할 때 요구되는 value가 있고, 결론 요구되는 value가 있다.

discarded value 구문은 결과로 산출된 value를 사용할 의도가 없음을 컴파일러에게 알려줌으로써 컴파일러는가 side effect 마저 없다면 전체 computation expression를 소거할 수 있다.(예를 들어 void 타입으로 변환하는 허위 코딩이 앞에 존재하고, 실제 의도된 코딩이 뒤에 있는 경우, 또는 반대로)

non-discard value 구문은 역으로 결과 value를 필히 프로그래머가 제어하도록 요구한다.

int a, b;
/* ... */
a = a + b + 5;

int a, b;는 declaration 구문이다. 따라서 해당 구문에서 value category를 논할 수 없다. declaration 과정에 사용되는 computation expression는 declaration 구문의 sub expession로 분류한다.

a = a + b + 5;는 computation expression 구문이다. C++에서 연산자 =, + 는 builtin-operator function를 호출한다고 언급하고 있고, 대응하는 연산자마다 피연산자에 요구하는 value category 속성을 builtin-operator function 별도 항목으로 설명한다.

참여하는 피연산자에게 연산자가 요구하는 value 유형이 value category다. C++ 스펙은 연산자가 요구하는 value category 유형을 아주 정확하게 언급하고 있다. 같은 연산자에 대한 C++ 언어과 C 언어가 value category 유형이 서로 다를 수 있다.

int a, b;
/* ... */
( (a>b) ? a : b ) *= 10;

예를 들어, C 언어에서는 삼항 연산자의 computation expression에 대한 결과를 prvalue로 명시하고 있다. *= 연산자의 왼쪽 피연산자는 lvalue만을 요구한다. 따라서 두 연산자를 중심으로 서로 다른 value category를 요구하고 있고, 또한 prvalue에서 lvalue로 변환 규칙을 정의하고 있지 않다. 따라서 컴파일 오류가 발생한다.

그에 비해 C++ 언어에서는 삼항 연산자로 사용된 두 연산자가 lvalue를 가질 수 있다면 computation expression에 대한 결과를 common type의 lvalue로 명시하고 있고, *= 연산자에 대한 요구 사항은 C 언어과 같다. 따라서 컴파일 오류가 발생하지 않는다. 또한 특정 조건을 만족하면 prvalue에서 lvalue로 temporary materialization conversion를 지원한다.

결국 value category를 논할 수 있는 영역은 computation expression의 연산자과 피연산자 관계에서, 연산자가 피연산자에게 요구하는 value 유형이고, 피연산자만으로 구성된 computation expession는 괄호 연산자가 피연산자를 감싼 것처럼 취급해, 괄호 연산자가 요구하는 value 유형으로 결정한다.

value category 종류

computation expression에 참여하는 모든 value에 대한 유형은 lvalue, xvalue, prvalue로 구분된다. lvalue는 프로그래머가 제어하는 value를 의미하고, xvalue는 프로그래머가 제어권을 놓기 바로 직전의 value를 의미하고, prvalue는 전적으로 컴파일러가 제어할 수 있는 value를 의미한다.

3종류의 value 유형이 연산자과 맞물려 설명될 때, 3 종류의 value를 일일히 나열해 설명하기 보다는 glvalue과 prvalue, 또는 rvalue과 lvalue로 구분해 설명한다.
어떤 피연산자에 대해 glvalue를 언급하면, 뒤에 prvalue 설명이 있겠구나. 또는 어떤 연산자에 대해 rvalue를 언급하면, 뒤에 lvalue 설명이 있겠구나. 라고 어림짐작할 수 있다.

lvalue과 xvalue는 제어하는 시점에 프로그래머가 제어하는 값이기 때문에, 컴파일러는 해당 value를 prvalue로 변경한 후, 안전하게 사용하는 경향이 있다. 예를 들어 function이나 array 타입을 컴파일러가 제어할 때 function pointer나 array pointer로 제어하는데, 이는 프로그래머가 쥐고 있는 value를 안전하게 보존하려는 의도가 숨어 있다. 실제 value category 변환 과정에서 가장 우선시 하는 변환이 glvalue-to-prvalue 변환이다.

이에 대한 설명은 value category가 변환에서 다시 언급하도록 하자.

value category 정확하게 이해하는 방법

value category 자체가 computation expression에 사용하는 연산자, 피연산자 사이에서 정의되기 때문에, 연산자 특성을 이해하고, 해당 연산자가 요구하는 value category를 파악하고, 요구한 value category과 sub expresion의 결과 value category가 다를 때, value category간 conversion 규칙이 무엇인지 알아야 한다.

728x90
반응형