표현식
표현식 (Expressions)
표현식은 계산을 지정하는 연산자와 피연산자의 시퀀스예요. 표현식 평가는 결과를 만들 수 있고(2 + 2를 평가하면 결과 4가 나오죠), 부수 효과도 만들 수 있어요(std::printf("%d", 4)를 평가하면 표준 출력에 문자 '4'가 출력돼요).
출처: cppreference
본문
각 C++ 표현식은 두 가지 독립적인 성질로 특징지어져요: **타입(type)**과 값 범주(value category).
일반
- 값 범주 (lvalue, rvalue, glvalue, prvalue, xvalue — C++11 이후)는 표현식을 그 값으로 분류해요.
- 인자와 하위 표현식의 평가 순서는 중간 결과가 얻어지는 순서를 지정해요.
연산자 (Operators)
일반 연산자
| 할당 | 증가, 감소 | 산술 | 논리 | 비교 | 멤버 접근 | 기타 |
|---|---|---|---|---|---|---|
a = ba += ba -= ba *= ba /= ba %= ba &= ba |= ba ^= ba <<= ba >>= b |
++a--aa++a-- |
+a-aa + ba - ba * ba / ba % b~aa & ba | ba ^ ba << ba >> b |
!aa && ba || b |
a == ba != ba < ba > ba <= ba >= ba <=> b |
a[...]*a&aa->ba.ba->*ba.*b |
함수 호출 a(...)콤마 a, b조건 a ? b : c |
특수 연산자
-
static_cast— 한 타입을 다른 연관 타입으로 변환 -
dynamic_cast— 상속 계층 안에서 변환 -
const_cast— cv 한정자 추가/제거 -
reinterpret_cast— 무관한 타입으로 변환 -
C 스타일 캐스트 —
static_cast,const_cast,reinterpret_cast의 혼합으로 변환 -
new— 동적 저장 기간을 가진 객체 생성 -
delete—new표현식으로 만든 객체를 파괴하고 메모리 영역 해제 -
sizeof— 타입의 크기 조회 -
sizeof...— 팩의 크기 조회 (C++11) -
typeid— 타입의 타입 정보 조회 -
noexcept— 표현식이 예외를 던질 수 있는지 검사 (C++11) -
alignof— 타입의 정렬 요구 사항 조회 (C++11) -
^^— 문법 구조체에서 리플렉션 값을 생성 (C++26) -
연산자 우선순위는 연산자가 인자에 결합되는 순서를 정의해요.
-
**대안 표현(alternative representations)**은 일부 연산자의 다른 철자예요.
-
연산자 오버로딩은 사용자 정의 클래스로 연산자의 동작을 지정할 수 있게 해요.
변환 (Conversions)
- 표준 변환 — 한 타입에서 다른 타입으로의 암시적 변환
- const_cast 변환
- static_cast 변환
- dynamic_cast 변환
- reinterpret_cast 변환
- 명시적 캐스트 — C 스타일 캐스트 표기와 함수 스타일 표기를 사용한 변환
- 사용자 정의 변환 — 사용자 정의 클래스로부터의 변환을 지정 가능
메모리 할당 (Memory allocation)
- new 표현식 — 메모리를 동적으로 할당
- delete 표현식 — 메모리를 동적으로 해제
기타 (Other)
- 상수 표현식 — 컴파일 타임에 평가될 수 있고 컴파일 타임 맥락(템플릿 인자, 배열 크기 등)에서 사용
- sizeof
- alignof
- typeid
- throw 표현식
- 람다 표현식
1차 표현식 (Primary expressions)
어떤 연산자의 피연산자는 다른 표현식이거나 1차 표현식일 수 있어요 (예: 1 + 2 * 3에서 operator+의 피연산자는 하위 표현식 2 * 3과 1차 표현식 1이에요).
1차 표현식은 다음 중 하나예요:
this- 리터럴 (예:
2또는"Hello, world") - 식별자 표현식, 다음을 포함해요:
- 적절히 선언된 비한정 식별자 (예:
n또는cout) - 적절히 선언된 한정 식별자 (예:
std::string::npos) - 선언자에서 선언될 식별자
- 적절히 선언된 비한정 식별자 (예:
- 팩 인덱싱 표현식 (C++26)
- 람다 표현식 (C++11)
- 폴드 표현식 (C++17)
- requires 표현식 (C++20)
- splice 표현식 (C++26)
괄호로 묶인 어떤 표현식도 1차 표현식으로 분류돼요. 이렇게 하면 괄호가 어떤 연산자보다 우선순위가 높다는 것이 보장돼요. 괄호는 값, 타입, 값 범주를 보존해요.
리터럴 (Literals)
리터럴은 소스 코드에 내장된 상수 값을 나타내는 C++ 프로그램의 토큰이에요.
- 정수 리터럴 — 정수 타입의 10진수, 8진수, 16진수 또는 2진수.
- 문자 리터럴 — 타입이
char또는wchar_t(C++11 이후char16_t또는char32_t, C++20 이후char8_t)인 개별 문자. - 부동소수점 리터럴 —
float,double, 또는long double타입의 값. - 문자열 리터럴 — 타입이
const char[]또는const wchar_t[](C++11 이후const char16_t[]또는const char32_t[], C++20 이후const char8_t[])인 문자 시퀀스. - 부울 리터럴 —
bool타입의 값, 즉true와false. - nullptr — 널 포인터 값을 지정하는 포인터 리터럴.
- 사용자 정의 리터럴 — 사용자 지정 타입의 상수 값 (C++11).
전체 표현식 (Full-expressions)
다음 표현식은 **전체 표현식(full-expression)**이에요:
- 평가되지 않은 피연산자
- 상수 표현식
- (C++20) 즉시 호출(immediate invocations)
- 단순 선언이나 멤버 초기화자의 선언자, 초기화자의 구성 표현식을 포함해
- 수명이 연장되지 않은 임시 객체 외의 객체의 수명 끝에서 생성되는 소멸자 호출
- (C++26) 계약 단언의 술어
- 다른 어떤 표현식의 하위 표현식도 아니고 다른 방식으로 어떤 전체 표현식의 일부도 아닌 표현식
언어 구조체가 함수의 암시적 호출을 만드는 것으로 정의되면, 그 언어 구조체의 사용은 이 정의 목적상 표현식으로 간주돼요. 표현식이 나타나는 언어 구조체의 요구 사항을 충족시키기 위해 표현식의 결과에 적용되는 변환도 전체 표현식의 일부로 간주돼요.
초기화자의 경우, (애그리게이트의 기본 멤버 초기화자 평가를 포함해)(C++14) 개체의 초기화를 수행하는 것도 전체 표현식의 일부로 간주돼요.
표현식 E의 하위 표현식은 E의 즉시 하위 표현식이거나 E의 즉시 하위 표현식의 하위 표현식이에요. 람다 표현식의 "함수 본문"에 나타나는 표현식은 람다 표현식의 하위 표현식이 아니라는 점에 주의해요. (C++11)
표현식 E의 즉시 하위 표현식은 다음과 같아요:
- E의 피연산자의 구성 표현식
- (C++14) E가 애그리게이트 객체를 만들면, 초기화에 사용된 각 기본 멤버 초기화자의 구성 표현식
- (C++11) E가 람다 표현식이면, 복사로 잡힌 개체의 초기화와 캡처 초기화자의 구성 표현식
- E가 암시적으로 호출하는 어떤 함수 호출
- E가 함수 호출이거나 함수를 암시적으로 호출하면, 호출에 사용된 각 기본 인자의 구성 표현식
**구성 표현식(constituent expression)**은 다음과 같이 정의돼요:
- 표현식의 구성 표현식은 그 표현식 자체예요.
- 중괄호로 묶인 초기화 목록이나 (가능하면 괄호로 묶인) 표현식 목록의 구성 표현식은 각 목록의 요소들의 구성 표현식이에요.
=로 시작하는 초기화자의 구성 표현식은 초기화 절의 구성 표현식이에요.
int num1 = 0;
num1 += 1; // Case 1: the constituent expression of "num += 1" is "num += 1"
int arr2[2] = {2, 22} // Case 2: the constituent expressions
// of "{2, 22}" are "2" and "22"
// Case 3: the constituent expressions of "= {2, 22}"
// are the constituent expressions of "{2, 22}"
// (i.e. also "2" and "22")
잠재적으로 평가되는 표현식 (Potentially-evaluated expressions)
(C++11 이전) 다음이 아니라면 표현식은 잠재적으로 평가돼요:
sizeof연산자의 피연산자이거나- 다형성 클래스 타입의 lvalue를 지정하지 않는
typeid연산자의 피연산자.
다음 피연산자는 **평가되지 않는 피연산자(unevaluated operand)**로, 평가되지 않아요:
typeid연산자가 적용되는 표현식 (다형성 클래스 타입의 glvalue 제외)sizeof연산자의 피연산자noexcept연산자의 피연산자decltype지정자의 피연산자- (C++20) 개념 정의의 constraint-expression
- (C++20) requires 절의
requires키워드 뒤의 표현식 - (C++20) requires 표현식의 requirement-seq에 나타나는 표현식
- (C++26) 리플렉션 연산자(
^^)의 피연산자
(C++11 이후) 다음이 아니라면 표현식은 잠재적으로 평가돼요:
- 평가되지 않는 피연산자이거나
- 평가되지 않는 피연산자의 하위 표현식.
잠재적으로 평가되는 표현식은 ODR-use예요.
참고: 이 절은 불완전해요. 이유: 평가되지 않는 피연산자의 예제.
버려진 값 표현식 (Discarded-value expressions)
**버려진 값 표현식(discarded-value expression)**은 부수 효과를 위해서만 사용되는 표현식이에요. 그런 표현식에서 계산된 값은 버려져요. 그런 표현식에는 어떤 표현식 문장의 전체 표현식, 내장 콤마 연산자의 왼쪽 피연산자, 또는 void 타입으로 캐스팅하는 캐스트 표현식의 피연산자가 포함돼요.
배열-포인터와 함수-포인터 변환은 버려진 값 표현식이 계산한 값에 결코 적용되지 않아요. lvalue-to-rvalue 변환은 표현식이 volatile 한정 glvalue이고 다음 형태 중 하나일 때만 적용돼요 (내장 의미 필요, 괄호 붙을 수 있음):
- id-expression
- (C++26) splice 표현식
- 배열 첨자 표현식
- 클래스 멤버 접근 표현식
- 역참조(indirection)
- 포인터-멤버 연산
- 두 번째와 세 번째 피연산자가 모두 이 중 하나인 조건 표현식
- 오른쪽 피연산자가 이 중 하나인 콤마 표현식
게다가 lvalue가 volatile 한정 클래스 타입이면, 결과적인 rvalue 임시 객체를 초기화하려면 volatile 복사 생성자가 필요해요.
(C++17 이후) 표현식이 (일어났을 수 있는 lvalue-to-rvalue 변환 후의) 비-void prvalue이면, **임시 객체 실체화(temporary materialization)**가 일어나요. 컴파일러는 void로의 캐스트가 아닌 표현식이 [[nodiscard]]로 선언된 값을 버리면 경고를 발행할 수 있어요.
표현식 동등성 (Expression-equivalence)
표현식 e1, e2, ..., eN은 다음 조건을 모두 만족하면 **표현식-동등(expression-equivalent)**이에요:
- 같은 효과를 가진다.
- 모두 상수 하위 표현식이거나, 모두 그렇지 않다.
- 모두 noexcept이거나, 모두 그렇지 않다.
e1은 e1과 e2가 표현식-동등일 때만 e2에 대해 표현식-동등이에요 (즉 e2도 e1에 대해 표현식-동등이라는 뜻). (C++20 이후)
결함 보고 (Defect reports)
다음의 동작 변경 결함 보고는 이전에 발표된 C++ 표준에 소급 적용됐어요.
| DR | 적용 대상 | 발표 당시 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 1054 | C++98 | 할당 결과에 적용되는 lvalue-to-rvalue 변환 때문에 volatile 변수에 값 할당이 불필요한 읽기를 만들 수 있었음 | 버려진 값 표현식을 도입하고 변환이 필요한 경우 목록에서 이 경우 제외 |
| CWG 1343 | C++98 | 애그리게이트 초기화에서 소멸자 호출의 순서가 불충분하게 지정되었음 | 애그리게이트 초기화의 전체 표현식이 잘 지정됨 |
| CWG 1383 | C++98 | 버려진 값 표현식에 lvalue-to-rvalue 변환이 적용되는 표현식 목록이 오버로드된 연산자도 덮었음 | 내장 의미를 가진 연산자만 덮음 |
| CWG 1576 | C++11 | lvalue-to-rvalue 변환이 버려진 값 volatile xvalue 표현식에 적용되지 않았음 | 이 경우 변환 적용 |
| CWG 2249 | C++98 | 선언자에서 선언될 식별자가 id-expression이 아니었음 | 그것들임 |
| CWG 2431 | C++11 | 참조에 묶인 임시 객체의 소멸자 호출이 전체 표현식이 아니었음 | 그것들임 |