Constant expressions
컴파일 타임에 평가될 수 있는 expression 을 정의합니다.
이러한 표현식은 상수 템플릿 인수, 배열 크기 및 기타 상수 표현식이 필요한 상황에서 사용할 수 있습니다, 예를 들어
int n = 1; std::array<int, n> a1; // 오류: "n"은 상수 표현식이 아닙니다 const int cn = 2; std::array<int, cn> a2; // 정상: "cn"은 상수 표현식입니다
목차 |
정의
|
아래에 나열된 상수 표현식 범주에 속하는 표현식은 상수 표현식 입니다.
|
(C++11 이전) | ||
|
다음 표현식들을 총칭하여 상수 표현식(constant expressions) 이라고 합니다:
|
(C++11부터)
(C++14까지) |
||
|
다음 개체들은 상수 표현식의 허용된 결과 입니다: 상수 표현식 은 상수 표현식의 허용된 결과인 개체를 참조하는 glvalue 핵심 상수 표현식 이거나, 다음 제약 조건을 만족하는 prvalue 핵심 상수 표현식입니다:
|
(since C++14)
(until C++26) |
||
|
상수 표현식(constant expression) 은 객체나 비- 즉시 함수(immediate function) 을 참조하는 glvalue 핵심 상수 표현식(core constant expression) 이거나, 다음 제약 조건을 만족하는 값을 가지는 prvalue 핵심 상수 표현식입니다:
|
(C++26부터) |
어떤 표현식이 상수 표현식인지 판단할 때, copy elision 이 수행되지 않는 것으로 가정합니다.
C++98 상수 표현식 정의는 접기 상자 내에 완전히 포함되어 있습니다. 다음 설명은 C++11 및 이후 C++ 버전에 적용됩니다.
리터럴 타입
다음 유형들을 통칭하여 literal types 라고 합니다:
-
- 이것은 trivial destructor (until C++20) constexpr destructor (since C++20) 를 가집니다.
- 모든 비정적(non-static) 비변이(non-variant) 데이터 멤버와 기본 클래스가 비휘발성(non-volatile) 리터럴 타입입니다.
- 다음 타입 중 하나입니다:
| (since C++17) |
-
-
- 다음 조건 중 하나를 만족하는 집계(aggregate) 유니온(union) 타입:
-
- variant member 가 없는 경우
- non-volatile literal type의 variant member를 하나 이상 가진 경우
- 비유니온(non-union) 집계 타입이며, 각 익명 유니온(anonymous union) 멤버가 다음 조건 중 하나를 만족하는 경우:
-
- variant member가 없는 경우
- non-volatile literal type의 variant member를 하나 이상 가진 경우
- 복사 또는 이동 생성자가 아닌 constexpr 생성자(템플릿)를 하나 이상 가진 타입
-
리터럴 타입의 객체만 상수 표현식 내에서 생성될 수 있습니다.
코어 상수 표현식
핵심 상수 표현식 은 다음 언어 구성 요소 중 어떠한 것도 평가하지 않는 표현식을 의미합니다:
| 언어 구성 | 버전 | 문서 |
|---|---|---|
this
포인터, 단
constexpr
함수
내에서 표현식의 일부로 평가 중이거나, 암시적 또는 명시적 클래스 멤버 접근 표현식에 나타나는 경우는 제외
|
N2235 | |
| 정적 또는 스레드 저장 기간 을 가지며 상수 표현식에서 사용 불가능한 블록 변수 선언을 통과하는 제어 흐름 | (C++23부터) | P2242R3 |
|
이 섹션은 불완전합니다
이유: 아래의 raw-HTML 순서 목록 내용을 위의 위키테이블로 옮기면서, 해당 항목을 표준에 도입한 논문/CWG 이슈를 추가하세요. 미니 예제들은 보존되지 않으며, 이 페이지 하단에 큰 예제를 구성하는 데 결합할 수 있습니다. |
-
a function call expression that calls a function (or a constructor) that is not declared
constexpr
constexpr int n = std::numeric_limits<int>::max(); // 정상: max()는 constexpr입니다 constexpr int m = std::time(nullptr); // 오류: std::time()은 constexpr이 아닙니다
- a function call to a constexpr function which is declared, but not defined
- a function call to a constexpr function/constructor template instantiation where the instantiation fails to satisfy constexpr 함수/생성자 requirements.
- a function call to a constexpr virtual function, invoked on an object whose dynamic type is constexpr-unknown
- an expression that would exceed the implementation-defined limits
-
an expression whose evaluation leads to any form of core language
정의되지 않음
또는 오류가 있는
(C++26부터)
behavior, except for any potential undefined behavior introduced by
표준 속성
.
constexpr double d1 = 2.0 / 1.0; // 정상 constexpr double d2 = 2.0 / 0.0; // 오류: 정의되지 않음 constexpr int n = std::numeric_limits<int>::max() + 1; // 오류: 오버플로우 int x, y, z[30]; constexpr auto e1 = &y - &x; // 오류: 정의되지 않음 constexpr auto e2 = &z[20] - &z[3]; // 정상 constexpr std::bitset<2> a; constexpr bool b = a[2]; // UB, 하지만 탐지 여부는 명시되지 않음
- (C++17 이전) a 람다 표현식
-
an lvalue-to-rvalue
암시적 변환
unless applied to...
- (cv 한정자가 있을 수 있는) 타입의 glvalue std::nullptr_t
-
상수 표현식에서 사용 가능한 객체를 지정하는 비휘발성 리터럴 타입 glvalue
usable in constant expressions
int main() { const std::size_t tabsize = 50; int tab[tabsize]; // OK: tabsize is a constant expression // because tabsize is usable in constant expressions // because it has const-qualified integral type, and // its initializer is a constant initializer std::size_t n = 50; const std::size_t sz = n; int tab2[sz]; // Error: sz is not a constant expression // because sz is not usable in constant expressions // because its initializer was not a constant initializer }
- 이 표현식의 평가 내에서 수명이 시작된 비휘발성 객체를 참조하는 비휘발성 리터럴 타입 glvalue
T*
포인터가 널 포인터 값을 보유하거나 해당 객체의 타입이
유사한
T
를 가리키지 않는 한
(C++26부터)
dynamic_cast
피연산자가 constexpr-unknown 동적 타입을 가진 객체를 참조하는 glvalue인
(C++20부터)
reinterpret_cast
constexpr int incr(int& n) { return ++n; } constexpr int g(int k) { constexpr int x = incr(k); // 오류: incr(k)는 코어 상수 표현식이 아님 // k의 수명이 incr(k) 표현식 외부에서 // 시작되었기 때문 return x; } constexpr int h(int k) { int x = incr(k); // OK: x는 코어 상수 표현식으로 초기화될 // 필요가 없음 return x; } constexpr int y = h(1); // OK: y를 값 2로 초기화 // h(1)은 코어 상수 표현식임 // k의 수명이 h(1) 표현식 내부에서 시작되기 때문
typeid
expression applied to a glvalue of polymorphic type
그리고 그 glvalue가 동적 타입이 constexpr-unknown인 객체를 참조하는 경우
(C++20부터)
|
(since C++20) |
|
(since C++26) |
constexpr void check(int i) { if (i < 0) throw i; } constexpr bool is_ok(int i) { try { check(i); } catch (...) { return false; } return true; } constexpr bool always_throw() { throw 12; return true; } static_assert(is_ok(5)); // OK static_assert(!is_ok(-1)); // C++26부터 OK static_assert(always_throw()); // 오류: 처리되지 않은 예외
goto
문
dynamic_cast
또는
typeid
표현식
또는
new
표현식
(C++26 이후)
예외 타입의 정의가 도달 가능하지 않은 경우
(C++26 이후)
void g() { const int n = 0; constexpr int j = *&n; // OK: 람다 표현식 외부 [=] { constexpr int i = n; // OK: 'n'은 odr-used되지 않으며 여기서 캡처되지 않음 constexpr int j = *&n; // 잘못된 형식: '&n'은 'n'의 odr-use가 됨 }; }
|
odr-use가 클로저에 대한 함수 호출에서 발생하는 경우, 이는 this 또는 둘러싸는 변수를 참조하지 않습니다. 클로저의 데이터 멤버에 접근하기 때문입니다 // OK: 'v'와 'm'은 odr-used되지만 중첩된 람다 내의 상수 표현식에서 발생하지 않음 // 상수 표현식 평가 중 생성된 자동 객체에 대한 캡처를 가질 수 있음 auto monad = [](auto v){ return [=]{ return v; }; }; auto bind = [](auto m){ return [=](auto fvm){ return fvm(m()); }; }; static_assert(bind(monad(2))(monad)() == monad(2)()); |
(C++17 이후) |
추가 요구사항
표현식 E 가 위에서 언급된 어떤 것도 평가하지 않더라도, E 를 평가했을 때 런타임-정의되지 않은 동작 이 발생한다면 E 가 핵심 상수 표현인지는 구현에 따라 정의됩니다.
표현식 E 가 위에서 언급한 어떤 것도 평가하지 않더라도, E 를 평가할 경우 다음 중 어떤 것을 평가하게 된다면 E 가 핵심 상수 표현식인지 여부는 명시되지 않습니다:
- 표준 라이브러리에서 정의되지 않은 동작을 가진 연산.
- va_start 매크로의 호출.
표현식이 핵심 상수 표현식인지 판단하기 위한 목적으로,
std::
allocator
<
T
>
의 멤버 함수 본문 평가는
T
가 리터럴 타입인 경우 무시됩니다.
표현식이 코어 상수 표현식인지 여부를 결정하기 위해, union 의 trivial 복사/이동 생성자 또는 복사/이동 할당 연산자 호출의 평가는 (존재하는 경우) union의 active 멤버를 복사/이동하는 것으로 간주됩니다.
|
표현식이 핵심 상수 표현(core constant expression)인지 판단하기 위한 목적으로, 구조화된 바인딩(structured binding) bd 를 명명하는 식별자 표현식(identifier expression)의 평가는 다음과 같은 의미론을 가집니다:
|
(C++26부터) |
표현식이 핵심 상수 표현식으로 평가되는 동안, 모든 식별자 표현식과 수명이 표현식 평가 외부에서 시작된 객체나 참조를 참조하는 * this 사용은 전체 상수 평가를 포함하는 수명을 가진 해당 객체나 참조의 특정 인스턴스를 참조하는 것으로 처리됩니다.
- 그러한 객체의 경우 상수 표현식에서 사용할 수 없는 객체 (C++20부터) 는 해당 객체의 동적 타입이 constexpr-unknown 입니다.
- 그러한 참조의 경우 상수 표현식에서 사용할 수 없는 (C++20부터) 참조는 참조된 타입의 지정되지 않은 객체에 바인딩되는 것으로 처리되며, 해당 객체와 모든 하위 객체의 수명은 전체 상수 평가를 포함하고 동적 타입은 constexpr-unknown입니다.
정수 상수 표현식
Integral constant expression 는 정수형 또는 범위 없는 열거형 타입의 표현식이 prvalue로 암시적으로 변환된 것으로, 변환된 표현식이 핵심 상수 표현식인 경우를 말합니다.
클래스 타입의 표현식이 정수 상수 표현식이 필요한 곳에서 사용되면, 해당 표현식은 문맥상 암시적으로 변환됩니다 정수 타입이나 비범위 열거형 타입으로.
변환된 상수 표현식
변환된 상수 표현식
은 타입
T
의 표현식이
암시적으로 변환된
것으로, 변환된 표현식이 상수 표현식이고 암시적 변환 시퀀스가 다음 요소들만 포함하는 경우를 말합니다:
| (C++17부터) |
그리고 만약 reference binding 이 발생하는 경우, 그것은 오직 direct binding 만 가능합니다.
다음 컨텍스트들은 변환된 상수 표현식이 필요합니다:
| (C++14부터) | |
| (C++26부터) |
bool 타입의 상황적 변환된 상수 표현식 은 표현식으로, bool로 상황적 변환된 , 변환된 표현식이 상수 표현식이고 변환 순서가 위의 변환들만 포함하는 경우입니다.
다음 상황들은 타입 bool 의 문맥상 변환된 상수 표현식을 요구합니다:
| (C++23 이전) | |
|
(C++17 이후)
(C++23 이전) |
|
| (C++20 이후) |
구성 요소객체 obj 의 구성 값 은 다음과 같이 정의됩니다:
객체 obj 의 구성 참조 는 다음 참조들을 포함합니다:
변수 var 의 구성 값 과 구성 참조 는 다음과 같이 정의됩니다:
변수 var 의 구성 참조 ref 에 대해, ref 가 임시 객체나 그 서브객체에 바인딩되고 해당 임시 객체의 수명이 ref 의 수명까지 연장된 경우, 해당 임시 객체의 구성 값과 참조도 재귀적으로 var 의 구성 값과 참조입니다. Constexpr-표현 가능한 개체정적 저장 기간을 가지는 객체는 프로그램의 어떤 지점에서든 constexpr-참조 가능 합니다.
자동 저장 기간을 가지는 객체
obj
는 다음 조건을 만족할 때 지점
객체나 참조
x
는 다음 모든 조건을 만족할 때 지점
|
(C++26부터) | ||||||||
상수 초기화된 엔티티
상수 표현식에서 사용 가능변수는 잠재적으로 상수(potentially-constant) 입니다. 만약 그것이 constexpr 변수 이거나 참조 타입이거나 volatile이 아닌 const 한정 정수형 또는 열거형 타입인 경우입니다.
상수 초기화된 잠재적 상수 변수
var
은 다음 조건 중 하나를 만족할 때
명시적으로 상수 평가되는 표현식다음 표현식들(대상 타입으로의 변환을 포함)은 manifestly constant-evaluated 입니다:
평가가 명백하게 상수 평가되는 맥락에서 발생하는지 여부는
std::is_constant_evaluated
와
|
(C++20 이후) |
상수 평가에 필요한 함수와 변수
다음 표현식 또는 변환은 잠재적으로 상수 평가될 수 있습니다 :
- 명백하게 상수 평가되는 표현식
- 잠재적으로 평가되는 표현식
- 중괄호로 둘러싸인 초기화자 목록 의 직접 하위 표현식 ( 변환이 축소 변환인지 판단 하기 위해 상수 평가가 필요할 수 있음)
- 템플릿 엔티티 내에서 발생하는 주소 연산자 표현식 (해당 표현식이 값 종속적 인지 판단하기 위해 상수 평가가 필요할 수 있음)
- 위 항목들 중 하나의 하위 표현식이면서 중첩된 평가되지 않는 피연산자 의 하위 표현식이 아닌 경우
함수는 constexpr 함수이고 상수 평가될 수 있는 표현식에 의해 이름이 지정된 경우 상수 평가에 필요합니다 .
변수는 상수 평가에 필요합니다 만약 그것이 constexpr 변수이거나, 비휘발성 const 한정 정수 타입이거나 참조 타입이며, 그것을 나타내는 식별자 표현식 이 잠재적으로 상수 평가되는 경우입니다.
디폴트 함수의 정의와 함수 템플릿 특수화의 인스턴스화 또는 변수 템플릿 특수화 (C++14부터) 는 상수 평가를 위해 해당 함수 또는 변수가 (C++14부터) 필요한 경우 트리거됩니다.
상수 부분식
상수 부분식(constant subexpression) 은 부분식 으로서의 평가가 식 e 를 핵심 상수 식(core constant expression) 이 되지 못하게 하지 않는 식이며, 여기서 e 는 다음 표현식들 중 어느 것도 아닙니다:
| (C++20부터) |
참고 사항
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_constexpr_in_decltype
|
201711L
|
(C++20)
(DR11) |
상수 평가에 필요할 때 함수 및 변수 정의 생성 needed for constant evaluation |
__cpp_constexpr_dynamic_alloc
|
201907L
|
(C++20) | constexpr 함수에서 동적 저장 기간 연산 |
__cpp_constexpr
|
202306L
|
(C++26) | constexpr void * 캐스트: constexpr 타입 소거를 향하여 |
202406L
|
(C++26) | constexpr 배치 new 및 new [ ] | |
__cpp_constexpr_exceptions
|
202411L
|
(C++26) | constexpr 예외: [1] , [2] |
예제
|
이 섹션은 불완전합니다
이유: 예제가 없음 |
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 94 | C++98 |
산술 상수 표현식이 변수와 정적 데이터 멤버를
포함할 수 없었음 |
포함 가능함 |
| CWG 366 | C++98 |
문자열 리터럴을 포함하는 표현식이
정수 상수 표현식일 수 있었음 |
실제로는 그렇지 않음 |
| CWG 457 | C++98 |
volatile 변수를 포함하는 표현식
정수 상수 표현식이 될 수 있었음 |
이제는 그렇지 않음 |
| CWG 1293 | C++11 |
문자열 리터럴이 상수 표현식에서
사용 가능한지 여부가 불분명했음 |
사용 가능함 |
| CWG 1311 | C++11 | volatile glvalues가 상수 표현식에서 사용될 수 있음 | 금지됨 |
| CWG 1312 | C++11 |
reinterpret_cast
는 상수 표현식에서 금지되지만,
void * 로의 캐스팅과 void * 로부터의 캐스팅이 동일한 효과를 달성할 수 있음 |
cv
void
*
타입으로부터
객체 포인터 타입으로의 금지된 변환 |
| CWG 1313 | C++11 |
정의되지 않은 동작이 허용됨;
모든 포인터 뺄셈이 금지됨 |
UB 금지; 동일 배열
포인터 뺄셈 허용 |
| CWG 1405 | C++11 |
상수 표현식에서 사용 가능한 객체의 경우,
해당 객체의 mutable 서브객체들도 사용 가능했음 |
사용 불가능함 |
| CWG 1454 | C++11 |
constexpr 함수를 통해 참조로 상수 전달이
허용되지 않았음 |
허용됨 |
| CWG 1455 | C++11 | 변환된 상수 표현식은 prvalue만 가능했음 | lvalue도 가능함 |
| CWG 1456 | C++11 |
주소 상수 표현식이 배열의 끝 바로 다음 주소를
지정할 수 없었음 |
허용됨 |
| CWG 1535 | C++11 |
다형적 클래스 타입의 피연산자를 가진
typeid 표현식은 런타임 검사가 포함되지 않은 경우에도 코어 상수 표현식이 아니었음 |
피연산자 제약 조건이
다형적 클래스 타입의 glvalues로 제한됨 |
| CWG 1581 | C++11 |
상수 평가에 필요한 함수들이
정의되거나 인스턴스화될 필요가 없었음 |
필수 |
| CWG 1613 | C++11 |
핵심 상수 표현식이 람다 표현식 내부의
odr-used 참조를 평가할 수 있었음 |
일부 참조를
평가할 수 없었음 |
| CWG 1694 | C++11 |
정적 저장 기간 참조에 임시 객체의 값을 바인딩하는 것이
상수 표현식이었음 |
상수 표현식이
아님 |
| CWG 1872 | C++11 |
핵심 상수 표현식이
constexpr
함수 요구 사항을 만족하지 않는
함수 템플릿 인스턴스화를 호출할 수 있었음 |
해당 인스턴스화는
호출할 수 없음 |
| CWG 1952 | C++11 |
표준 라이브러리 정의되지 않은 동작들
진단이 요구됨 |
진단 여부가 명시되지 않음
(진단되는지 여부가 불명확) |
| CWG 2022 | C++98 |
상수 표현식 결정이 복사 생략 수행 여부에
의존할 수 있음 |
복사 생략이 항상
수행된다고 가정 |
| CWG 2126 | C++11 |
상수 초기화된 수명이 연장된 const-한정 리터럴 타입의 임시 객체들이
상수 표현식에서 사용 불가능했음 |
사용 가능 |
| CWG 2129 | C++11 | 정수 리터럴이 상수 표현식이 아니었음 | 상수 표현식임 |
| CWG 2167 | C++11 |
평가에 지역적인 비멤버 참조
평가를 비 constexpr로 만듦 |
비멤버
참조 허용됨 |
| CWG 2278 | C++98 | CWG 이슈 2022 의 해결책이 구현 불가능했음 |
복사 생략(copy elision)이
수행되지 않는다고 가정 |
| CWG 2299 | C++14 |
상수 평가에서
<cstdarg>
매크로 사용 가능 여부가 불명확했음 |
va_arg
금지,
va_start
미지정
|
| CWG 2400 | C++11 |
상수 표현식에서 사용 가능하지 않은 객체와 그 수명이 호출을 포함하는
표현식 외부에서 시작된 객체에서 constexpr 가상 함수를 호출하는 것은 상수 표현식일 수 있음 |
상수 표현식이
아님 |
| CWG 2490 | C++20 |
상수 평가에서 (의사) 소멸자 호출에
제한이 없었음 |
제한 추가됨 |
| CWG 2552 | C++23 |
핵심 상수 표현식을 평가할 때, 제어 흐름이
비블록 변수의 선언을 통과할 수 없음 |
가능함 |
| CWG 2558 | C++11 | 불확정 값이 상수 표현식일 수 있음 | 상수 표현식이 아님 |
| CWG 2647 | C++20 | volatile 한정 타입의 변수들이 잠재적으로 상수일 수 있음 | 실제로는 그렇지 않음 |
| CWG 2763 | C++11 |
상수 평가 중
[[
noreturn
]]
위반을 검출할 필요가 없었음
|
필수 |
| CWG 2851 | C++11 |
변환된 상수 표현식이
부동 소수점 변환을 허용하지 않음 |
비축소(non-narrowing)
부동 소수점 변환 허용 |
| CWG 2907 | C++11 |
핵심 상수 표현식이
std::nullptr_t glvalue에 대해 lvalue-to-rvalue 변환을 적용할 수 없었음 |
해당 변환을
적용할 수 있음 |
| CWG 2909 | C++20 |
초기화자가 없는 변수는 기본 초기화가
일부 초기화를 수행하는 경우에만 상수 초기화될 수 있었음 |
해당 타입이 const-기본-초기화-가능한
경우에만 상수 초기화될 수 있음 |
| CWG 2924 |
C++11
C++23 |
제약 조건을 위반하는 표현식이
[[
noreturn
]]
(C++11) 또는
[[
assume
]]
(C++23)의 핵심 상수 표현식인지 여부가 명시되지 않았음
|
이는
구현에 따라 정의됨 |
| P2280R4 | C++11 |
이 평가 외부에서 수명이 시작된 객체나 참조를 참조하는
식별자 표현식이나 * this 를 포함하는 표현식을 평가하는 것은 상수 표현식이 아님 |
상수 표현식이
될 수 있음 |
참고 항목
constexpr
지정자
(C++11)
|
변수나 함수의 값을 컴파일 시간에 계산할 수 있음을 지정함 |
|
(C++11)
(C++17에서 사용 중단됨)
(C++20에서 제거됨)
|
타입이 리터럴 타입인지 검사함
(클래스 템플릿) |
|
C 문서
for
상수 표현식
|
|