상수 표현식
상수 표현식 (Constant expressions)
컴파일 타임에 평가할 수 있는 표현식을 상수 표현식(constant expression) 이라고 해요. 이 표현식들은 비타입 템플릿 인자, 배열 크기, 그리고 상수 표현식이 필요한 다른 문맥에서 쓸 수 있어요. 먼저 왜 필요한지 예를 볼게요.
int n = 1;
std::array<int, n> a1; // Error: “n” is not a constant expression
const int cn = 2;
std::array<int, cn> a2; // OK: “cn” is a constant expression
출처: cppreference
본문
정의 (Definition)
아래 나열한 상수 표현식 범주 중 하나에 속하는 표현식이 상수 표현식이에요.
정수 상수 표현식 (Integral constant expression, C++98)
C++은 다음 위치에서 정수 또는 열거형 상수로 평가되는 표현식을 요구해요.
- 배열 경계(new 표현식에서 첫 번째를 제외한 차원 포함)
case레이블 상수- 비트 필드 길이
- 열거자(enum constant) 초기화식
- 정적 데이터 멤버 초기화식
- 정수 또는 열거형 타입의 템플릿 비타입 인자
다음 조건을 모두 만족하는 표현식이 정수 상수 표현식이에요.
- 다음 개체만 포함해요.
- 산술 타입의 리터럴
- 열거자
- 다음 조건을 모두 만족하는 변수 또는 정적 데이터 멤버
const한정volatile한정이 아님- 정수 또는 열거형 타입
- 상수 표현식으로 초기화됨
- 정수 또는 열거형 타입의 비타입 템플릿 매개변수
sizeof표현식
- 명시적으로 정수 또는 열거형 타입으로 변환된 경우가 아니면 부동소수점 리터럴을 사용하지 않아요.
- 비-정수·비-열거형 타입으로의 변환을 적용하지 않아요.
sizeof의 피연산자를 제외하고는 다음 개체를 사용하지 않아요.- 함수
- 클래스 객체
- 포인터
- 참조
- 대입 연산자
- 증가 연산자
- 감소 연산자
- 함수 호출 연산자
- 쉼표 연산자
다른 상수 표현식 범주 (Other constant expression categories)
다른 표현식들은 상수 초기화(constant initialization) 목적으로만 상수 표현식으로 간주돼요. 그런 상수 표현식은 다음 중 하나여야 해요.
- 널 포인터 값으로 평가되는 표현식
- 널 멤버 포인터 값으로 평가되는 표현식
- 산술 상수 표현식
- 주소 상수 표현식
- 참조 상수 표현식
- 완전 객체 타입에 대한 주소 상수 표현식에 정수 상수 표현식을 더하거나 뺀 것
- 멤버 포인터 상수 표현식
산술 상수 표현식은 정수 상수 표현식의 요구사항을 만족하되 다음 예외가 있는 표현식이에요.
- 부동소수점 리터럴을 명시적 변환 없이 사용할 수 있어요.
- 부동소수점 타입으로의 변환을 적용할 수 있어요.
주소 상수 표현식은 다음 조건을 모두 만족하는 포인터 타입의 표현식이에요.
- 포인터가 정적 저장 기간 객체, 문자열 리터럴, 또는 함수를 지시하는 lvalue를 가리켜요. 그 객체는 비-POD 클래스 타입의 하위 객체가 아니에요.
- 포인터가 다음 방법 중 하나로 생성됐어요.
- 주소 연산자(
&)를 명시적으로 사용 - 포인터 타입의 비타입 템플릿 매개변수를 암시적으로 사용
- 배열 또는 함수 타입의 표현식 사용
- 주소 연산자(
- 표현식이 어떤 함수도 호출하지 않아요.
- 표현식이 결과 객체에 접근하지 않으면서 명시적 포인터 변환(
dynamic_cast제외)과 다음 연산자를 사용해요.- 첨자 연산자
- 역참조(indirection) 연산자
- 주소 연산자
- 멤버 접근 연산자
- 첨자 연산자를 사용한다면 그 피연산자 중 하나가 정수 상수 표현식이에요.
참조 상수 표현식은 다음 조건을 모두 만족하는 참조 타입의 표현식이에요.
- 참조가 정적 저장 기간 객체, 참조 타입의 비타입 템플릿 매개변수, 또는 함수를 지시해요. 참조는 비-POD 클래스 타입의 멤버나 베이스 클래스를 지시하지 않아요.
- 표현식이 어떤 함수도 호출하지 않아요.
- 표현식이 결과 객체에 접근하지 않으면서 명시적 참조 변환(
dynamic_cast제외)과 다음 연산자를 사용해요.- 첨자 연산자
- 역참조 연산자
- 주소 연산자
- 멤버 접근 연산자
- 첨자 연산자를 사용한다면 그 피연산자 중 하나가 정수 상수 표현식이에요.
멤버 포인터 상수 표현식은 멤버 포인터 타입의 표현식으로, 한정된 식별자(qualified identifier)에 주소 연산자를 적용해 포인터를 만들고, 선택적으로 명시적 멤버 포인터 변환이 앞에 올 수 있는 표현식이에요. (until C++11)
상수 표현식의 허용 결과(permitted results) 로는 다음 개체가 있어요.
- 아래 제약을 만족하는 값을 가진, 정적 저장 기간의 임시 객체
- 정적 저장 기간의 비-임시 객체
- 비-immediate(since C++20) 함수
상수 표현식은 허용 결과인 개체를 가리키는 glvalue 핵심 상수 표현식이거나, 그 값이 다음 제약을 만족하는 prvalue 핵심 상수 표현식이에요.
- 각 구성 참조(constituent reference)가 허용 결과인 개체를 가리켜요.
- 스칼라 타입의 구성 값이 불확정(indeterminate) 값이 아니에요.
- 포인터 타입의 각 구성 값이 다음 중 하나예요.
- 정적 저장 기간 객체의 주소
- 정적 저장 기간 객체의 끝을 지나친 주소
- 비-immediate(since C++20) 함수의 주소
- 널 포인터 값
- 멤버 포인터 타입의 구성 값이 immediate 함수를 지시하지 않아요. (since C++11) (until C++26)
상수 표현식은 객체나 비-immediate 함수를 가리키는 glvalue 핵심 상수 표현식이거나, 그 값이 다음 제약을 만족하는 prvalue 핵심 상수 표현식이에요.
- 각 구성 참조가 객체나 비-immediate 함수를 가리켜요.
- 스칼라 타입의 구성 값이 불확정(indeterminate) 또는 오류(erroneous) 값이 아니에요.
- 포인터 타입의 구성 값이 immediate 함수를 가리키는 포인터나 잘못된(invalid) 포인터 값이 아니에요.
- 멤버 포인터 타입의 구성 값이 immediate 함수를 지시하지 않아요. (since C++26)
표현식이 상수 표현식인지 판단할 때 복사 생략(copy elision)은 수행되지 않는다고 가정해요.
C++98의 상수 표현식 정의는 위 붕괴(collapse) 상자 안에 완전히 들어 있어요. 아래 설명은 C++11 이후 버전에 적용돼요.
리터럴 타입 (Literal type)
다음 타입들을 통틀어 리터럴 타입 이라고 불러요.
- cv 한정된
void(since C++14) - 스칼라 타입
- 참조 타입
- 리터럴 타입의 배열
- 다음 조건을 모두 만족하는, cv 한정될 수 있는 클래스 타입
- 사소한 소멸자(until C++20) 혹은
constexpr소멸자(since C++20)를 가져요. - 모든 비정적 비-variant 데이터 멤버와 베이스 클래스가 비-volatile 리터럴 타입이에요.
- 다음 타입 중 하나예요.
- 클로저 타입(since C++17)
- 다음 조건 중 하나를 만족하는 집계 공용체 타입
- variant 멤버가 없음
- 비-volatile 리터럴 타입인 variant 멤버가 하나 이상 있음
- 비-공용체 집계 타입으로, 각 익명 공용체 멤버가 다음 조건 중 하나를 만족함
- variant 멤버가 없음
- 비-volatile 리터럴 타입인 variant 멤버가 하나 이상 있음
- 복사 또는 이동 생성자가 아닌
constexpr생성자(템플릿)를 하나 이상 가진 타입
- 사소한 소멸자(until C++20) 혹은
리터럴 타입의 객체만 상수 표현식 안에서 생성될 수 있어요.
핵심 상수 표현식 (Core constant expression)
핵심 상수 표현식은 그 평가가 다음 언어 구성 중 어떤 것도 평가하지 않을 표현식이에요.
this포인터. 단, 표현식의 일부로 평가되는constexpr함수 안이거나, 암시적·명시적 클래스 멤버 접근 표현식에 나타날 때는 제외.- (since C++23) 상수 표현식에서 사용할 수 없는(not usable in constant expressions) 정적 또는 스레드 저장 기간을 가진 블록 변수의 선언을 통과하는 제어 흐름
constexpr로 선언되지 않은 함수(또는 생성자)를 호출하는 함수 호출 표현식
constexpr int n = std::numeric_limits<int>::max(); // OK: max() is constexpr
constexpr int m = std::time(nullptr); // Error: std::time() is not constexpr
- 선언됐지만 정의되지 않은
constexpr함수에 대한 함수 호출 - constexpr 함수/생성자 요구사항을 만족하지 못하는
constexpr함수/생성자 템플릿 인스턴스화에 대한 함수 호출 - 동적 타입이 constexpr-unknown인 객체에 대해 호출된
constexpr가상 함수에 대한 함수 호출 - 구현 정의 한계를 초과하는 표현식
- 표준 속성으로 인한 잠재적 정의되지 않은 동작을 제외하고, 어떤 형태의 핵심 언어 정의되지 않거나(undefined) 오류(erroneous)(since C++26) 동작으로 이끄는 표현식
constexpr double d1 = 2.0 / 1.0; // OK
constexpr double d2 = 2.0 / 0.0; // Error: not defined
constexpr int n = std::numeric_limits<int>::max() + 1; // Error: overflow
int x, y, z[30];
constexpr auto e1 = &y - &x; // Error: undefined
constexpr auto e2 = &z[20] - &z[3]; // OK
constexpr std::bitset<2> a;
constexpr bool b = a[2]; // UB, but unspecified if detected
- (until C++17) 람다 표현식
- lvalue-to-rvalue 암시적 변환. 단 다음에 적용되는 경우는 제외.
- (cv 한정될 수 있는)
std::nullptr_t타입의 glvalue - 상수 표현식에서 사용할 수 있는 객체를 지시하는 비-volatile 리터럴 타입 glvalue
- (cv 한정될 수 있는)
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
}
- 수명이 이 표현식의 평가 안에서 시작된 비-volatile 객체를 참조하는 비-volatile 리터럴 타입 glvalue
- 공용체의 비활성 멤버나 그 하위 객체에 적용된 lvalue-to-rvalue 암시적 변환 또는 수정(활성 멤버와 공통 초기 시퀀스를 공유하더라도)
- 값이 불확정인 객체에 대한 lvalue-to-rvalue 암시적 변환
- 활성 멤버가 mutable인(있다면) 공용체에 대한 암시적 복사/이동 생성자/대입의 호출. 단 수명이 이 표현식의 평가 밖에서 시작됨.
- (until C++20) 공용체의 활성 멤버를 바꿀 대입 표현식
- 포인터가 널 포인터 값을 보유하거나
T와 유사한 타입의 객체를 가리키지 않는 한,void포인터에서 객체 포인터 타입T*로의 변환(since C++26) - 피연산자가 동적 타입이 constexpr-unknown인 객체를 가리키는 glvalue인
dynamic_cast(since C++20) reinterpret_cast- (until C++20) 의사 소멸자 호출
- (until C++14) 증가 또는 감소 연산자
- (since C++14) 객체의 수정. 단 객체가 비-volatile 리터럴 타입이고 그 수명이 표현식의 평가 안에서 시작된 경우는 제외.
constexpr int incr(int& n)
{
return ++n;
}
constexpr int g(int k)
{
constexpr int x = incr(k); // Error: incr(k) is not a core constant
// expression because lifetime of k
// began outside the expression incr(k)
return x;
}
constexpr int h(int k)
{
int x = incr(k); // OK: x is not required to be initialized
// with a core constant expression
return x;
}
constexpr int y = h(1); // OK: initializes y with the value 2
// h(1) is a core constant expression because
// the lifetime of k begins inside the expression h(1)
- (since C++20) 수명이 이 표현식의 평가 안에서 시작되지 않은 객체에 대한 소멸자 호출 또는 의사 소멸자 호출
- 다형성 타입의 glvalue에 적용되고 그 glvalue가 동적 타입이 constexpr-unknown인 객체를 가리키는
typeid표현식(since C++20) - 다음 조건 중 하나를 만족하지 않는
new표현식(since C++20)- 선택된 할당 함수가 교체 가능한(replaceable) 전역 할당 함수이고, 할당된 저장소가 이 표현식의 평가 안에서 해제됨. (since C++20)
- 선택된 할당 함수가 할당 타입
T를 가진 비-할당(non-allocating) 형태이고, 배치 인자가 다음 조건을 모두 만족함.- 다음을 가리킴:
T가 배열 타입이 아니라면T와 유사한 타입의 객체, 또는T가 배열 타입이라면T와 유사한 타입의 객체의 첫 요소
- 수명이 이 표현식의 평가 안에서 시작된 저장소를 가리킴. (since C++26)
- 다음을 가리킴:
- 이 표현식의 평가 안에서 할당된 저장소 영역을 해제하지 않는
delete표현식(since C++20) - (since C++20) 코루틴:
await-표현식 또는yield-표현식 - (since C++20) 결과가 불특정(unspecified)인 삼중 비교(three-way comparison)
- 결과가 불특정인 동등·관계 연산자
- (until C++14) 대입 또는 복합 대입 연산자
- (until C++26)
throw표현식 - (since C++26) 예외 객체의 생성. 단 예외 객체와
std::current_exception또는std::rethrow_exception호출로 만들어진 모든 암시적 복사본이 이 표현식의 평가 안에서 파괴되는 경우는 제외.
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)); // OK since C++26
static_assert(always_throw()); // Error: uncaught exception
- asm-선언(asm-declaration)
va_arg매크로의 호출goto문- 예외 타입의 정의에 도달할 수 없는 곳에서 예외를 던질
dynamic_cast또는typeid표현식이나new표현식(since C++26)(since C++26) - 람다 표현식 안에서
this또는 그 람다 밖에 정의된 변수에 대한 참조. 단 그 참조가 odr-use가 되는 경우.
void g()
{
const int n = 0;
constexpr int j = *&n; // OK: outside of a lambda-expression
[=]
{
constexpr int i = n; // OK: 'n' is not odr-used and not captured here.
constexpr int j = *&n; // Ill-formed: '&n' would be an odr-use of 'n'.
};
}
ODR-use가 클로저에 대한 함수 호출에서 일어난다면, 그건 this나 둘러싼 변수를 가리키는 게 아니라 클로저의 데이터 멤버에 접근하는 것이므로 문제가 되지 않아요.
// OK: 'v' & 'm' are odr-used but do not occur in a constant-expression
// within the nested lambda
auto monad = [](auto v){ return [=]{ return v; }; };
auto bind = [](auto m){ return [=](auto fvm){ return fvm(m()); }; };
// OK to have captures to automatic objects created during constant expression evaluation.
static_assert(bind(monad(2))(monad)() == monad(2)());
추가 요구사항 (Extra requirements)
표현식 E가 위의 어떤 것도 평가하지 않더라도, E를 평가하는 것이 [[noreturn]]이나 [[assume]]의 사용으로 인해 런타임 정의되지 않은 동작을 만들게 된다면 E가 핵심 상수 표현식인지는 구현 정의예요.
표현식 E가 위의 어떤 것도 평가하지 않더라도, E를 평가하는 것이 다음 중 어떤 것이라도 평가한다면 E가 핵심 상수 표현식인지는 불특정(unspecified)이에요.
- 표준 라이브러리에서 정의되지 않은 동작이 있는 연산
va_start매크로의 호출
표현식이 핵심 상수 표현식인지 판단할 때, T가 리터럴 타입이라면 std::allocator<T>의 멤버 함수 본문의 평가는 무시돼요.
표현식이 핵심 상수 표현식인지 판단할 때, 공용체의 사소한 복사/이동 생성자나 복사/이동 대입 연산자 호출의 평가는 (있다면) 공용체의 활성 멤버를 복사/이동하는 것으로 간주돼요.
표현식이 핵심 상수 표현식인지 판단할 때, 구조화 바인딩 bd를 이름 붙이는 식별자 표현식의 평가는 다음 의미를 가져요.
bd가 발명된 참조(invented reference)ref에 묶인 객체를 가리키는 lvalue라면, 마치ref가 지명된 것처럼 동작해요.- 그렇지 않고
bd가 배열 요소를 이름 붙이면,e[i]를 평가하는 것처럼 동작해요. 여기서e는 구조화 바인딩 선언의 초기화식에서 초기화된 변수의 이름이고i는bd가 가리키는 요소의 인덱스예요. - 그렇지 않고
bd가 클래스 멤버를 이름 붙이면,e.m을 평가하는 것처럼 동작해요. 여기서m은bd가 가리키는 멤버의 이름이에요. (since C++26)
표현식을 핵심 상수 표현식으로 평가하는 동안, 수명이 그 표현식의 평가 밖에서 시작된 객체나 참조를 가리키는 모든 이름과 *this 사용은, 수명(그리고 모든 하위 객체·모든 공용체 멤버의 수명)이 전체 상수 평가를 포함하는 그 객체나 참조의 특정 인스턴스를 가리키는 것으로 취급돼요.
- 상수 표현식에서 사용할 수 없는(since C++20) 그런 객체에 대해, 객체의 동적 타입은 constexpr-unknown이에요.
- 상수 표현식에서 사용할 수 없는(since C++20) 그런 참조에 대해, 참조는 수명과 모든 하위 객체의 수명을 포함하고 동적 타입이 constexpr-unknown인 참조된 타입의 불특정 객체에 묶인 것으로 취급돼요.
정수 상수 표현식 (Integral constant expression)
정수 상수 표현식은 정수 또는 무범위(unscoped) 열거형 타입의 표현식으로, 변환된 표현식이 핵심 상수 표현식인 prvalue로 암시적으로 변환된 것이에요.
정수 상수 표현식이 필요한 곳에 클래스 타입의 표현식이 쓰이면, 그 표현식은 문맥상 정수 또는 무범위 열거형 타입으로 암시적 변환돼요.
변환된 상수 표현식 (Converted constant expression)
타입 T의 변환된 상수 표현식은 T로 암시적 변환된 표현식으로, 변환된 표현식이 상수 표현식이고, 암시적 변환 시퀀스에 다음만 포함된 것이에요.
constexpr사용자 정의 변환- lvalue-to-rvalue 변환
- 정수 승격(integral promotion)
- 축소되지 않는(non-narrowing) 정수 변환
- 부동소수점 승격
- 축소되지 않는 부동소수점 변환
- 배열-to-포인터 변환
- 함수-to-포인터 변환
- 함수 포인터 변환
- 한정(qualification) 변환
std::nullptr_t에서의 널 포인터 변환std::nullptr_t에서의 널 멤버 포인터 변환(since C++17)
그리고 참조 바인딩이 일어난다면 직접 바인딩(direct binding)만 가능해요.
다음 문맥이 변환된 상수 표현식을 요구해요.
case레이블의 상수 표현식- 기반 타입이 고정된 열거자의 초기화식
- 정수 및 열거형(until C++17) 비타입 템플릿 인자
- 배열 경계
new표현식에서 첫 번째를 제외한 차원(since C++14)- 팩 인덱싱(pack indexing) 표현식과 팩 인덱싱 지정자의 인덱스(since C++26)
bool 타입의 문맥상 변환된 상수 표현식은 bool로 문맥상 변환된 표현식으로, 변환된 표현식이 상수 표현식이고 변환 시퀀스에 위 변환들만 포함된 것이에요.
다음 문맥이 bool 타입의 문맥상 변환된 상수 표현식을 요구해요.
noexcept지정static_assert선언(until C++23)constexpr if문(since C++17) (until C++23)- 조건부
explicit지정자(since C++20)
구성 개체(엔티티) (Constituent entities)
비공식적으로, 객체 obj의 구성 값(constituent values)과 구성 참조는 obj를 이루는 스칼라 값과 참조예요.
공식적으로, 객체 obj의 구성 값은 다음과 같이 정의돼요.
obj가 스칼라 타입이면 구성 값은obj의 값이에요.- 그렇지 않으면 구성 값은 비활성 공용체 멤버를 제외한
obj의 직접 하위 객체들의 구성 값이에요.
객체 obj의 구성 참조는 다음 참조들을 포함해요.
- 참조 타입인
obj의 직접 멤버들 - 비활성 공용체 멤버를 제외한
obj의 직접 하위 객체들의 구성 참조
변수 var의 구성 값과 구성 참조는 다음과 같이 정의돼요.
var가 객체를 선언하면 구성 값과 참조는 그 객체의 구성 값과 참조예요.var가 참조를 선언하면 구성 참조는 그 참조예요.
var의 어떤 구성 참조 ref에 대해, ref가 그 수명이 ref의 수명으로 확장된 임시 객체나 그 하위 객체에 묶여 있다면, 그 임시 객체의 구성 값과 참조도 재귀적으로 var의 구성 값과 참조가 돼요.
constexpr로 표현 가능한 개체 (Constexpr-representable entities)
정적 저장 기간을 가진 모든 객체는 프로그램의 어느 지점에서도 constexpr-참조 가능(constexpr-referenceable) 해요.
자동 저장 기간 객체는 P를 직접 포함하는 함수 본문에 정의되어 있다면 지점 P에서 constexpr-참조 가능해요.
void f() {
constexpr int x = 42;
constexpr const int& ref = x; // OK, x is constexpr-referenceable here
const int& r = 42; // r binds to a temporary object with automatic storage duration
constexpr const int& ref2 = r; // OK, the temporary is constexpr-referenceable here
[&] {
constexpr const int& ref = x; // Error: x is not constexpr-referenceable here
};
}
(since C++26) 그 외 객체(예: 스레드 저장 기간을 가진 객체)는 결코 constexpr-참조 가능하지 않아요.
객체나 참조 x는 다음 조건을 모두 만족하면 지점 P에서 constexpr-표현 가능(constexpr-representable) 이라고 해요.
x의 어떤 객체obj를 가리키는 각 구성 값에 대해obj는P에서 constexpr-참조 가능해요.x의 어떤 객체obj를 지나친 뒤를 가리키는 각 구성 값에 대해obj는P에서 constexpr-참조 가능해요.x의 어떤 객체obj를 가리키는 각 구성 참조에 대해obj는P에서 constexpr-참조 가능해요.
상수 초기화된 개체 (Constant-initialized entities)
변수나 임시 객체 obj는 다음 조건을 모두 만족하면 상수 초기화(constant-initialized) 됐다고 말해요.
- 초기화식을 가지거나, 그 타입이 const-기본-생성 가능(const-default-constructible)해요.
- 초기화의 전체 표현식이 상수 표현식을 요구하는 문맥에서의 상수 표현식이에요. 단,
obj가 객체라면 그 전체 표현식은obj와 그 하위 객체가 비-리터럴 클래스 타입이더라도 그들을 위한constexpr생성자를 호출할 수 있어요. (until C++26)
변수 var는 다음 조건을 모두 만족하면 상수 초기화 가능(constant-initializable) 하다고 말해요.
- 초기화의 전체 표현식이 상수 표현식을 요구하는 문맥에서의 상수 표현식이에요. 이때 모든 계약 단언(contract assertion)은 "무시(ignore)" 평가 의미론을 사용해요.
var의 초기화 선언 직후,var가 선언한 객체나 참조가 constexpr-표현 가능해요.var가 선언한 객체나 참조x가 정적 또는 스레드 저장 기간을 가지면,x는var의 초기화 선언을 따르는, 즉시 스코프가 네임스페이스 스코프인 가장 가까운 지점에서 constexpr-표현 가능해요.
상수 초기화 가능한 변수는, 초기화식을 가지거나 그 타입이 const-기본-생성 가능하다면 상수 초기화됩니다.
void f() {
int ax = 0; // ax is constant-initialized
thread_local int tx = 0; // tx is constant-initialized
static int sx; // sx is not constant-initialized (it doesn't have an initializer)
constexpr int& raa = ax; // OK, raa is constant-initialized
constexpr int& rat = tx;
// Error: rat is not constant-initialized
// because it's not constexpr-representable here
static constexpr int& rsa = ax;
// Error: rsa is not constant-initialized
// because it's not constexpr-representable at the nearest namespace scope
}
(since C++26)
상수 표현식에서 사용 가능 (Usable in constant expressions)
변수가 constexpr 변수이거나 참조 또는 비-volatile const-한정 정수·열거형 타입을 가지면 그 변수는 potentially-constant 해요.
변수 var는 지점 P에서 다음 조건을 모두 만족하면 상수 표현식에서 사용 가능 해요.
var가 상수 초기화되었고,var가 potentially-constant이고,var의 초기화 선언D가P에서 도달 가능하고,- 다음 조건 중 하나를 만족해요.
var가constexpr변수이다.var가 TU-지역(TU-local) 값으로 초기화되지 않았다.P가D와 같은 번역 단위에 있다.
객체나 참조는 지점 P에서 constexpr-표현 가능하고(since C++26) 다음 개체 중 하나라면 상수 표현식에서 사용 가능해요.
P에서 상수 표현식에서 사용 가능한 변수- 상수 표현식에서
P에서 사용 가능한 변수로 수명이 확장된 비-volatile const-한정 리터럴 타입의 임시 객체 - 템플릿 매개변수 객체(since C++20)
- 문자열 리터럴 객체
- 위 중 어떤 것의 비-mutable 하위 객체
- 위 중 어떤 것의 참조 멤버
명백한 상수 평가 표현식 (Manifestly constant-evaluated expressions)
다음 표현식들(대상 타입으로의 변환 포함)은 명백히 상수 평가(manifestly constant-evaluated) 돼요.
- 배열 경계
new표현식에서 첫 번째를 제외한 차원- 비트 필드 길이
- 열거형 초기화식
- 정렬(alignment)
case레이블의 상수 표현식- 비타입 템플릿 인자
noexcept지정의 표현식static_assert선언의 표현식constexpr변수의 초기화식constexpr if문의 조건- 조건부
explicit지정자의 표현식 - immediate 호출
- 제약이 만족되는지 판단할 때 개념 정의, 중첩 요구사항,
requires절의 제약 표현식 - 참조 타입 또는 const-한정 정수·열거형 타입 변수의 초기화식. 단 그 초기화식이 상수 표현식인 경우에만.
- 정적·스레드 지역 변수의 초기화식. 단 초기화식의 모든 부분 표현식(생성자 호출과 암시적 변환 포함)이 상수 표현식인 경우에만(즉, 초기화식이 상수 초기화식인 경우).
평가가 명백히 상수 평가되는 문맥에서 일어나는지 여부는 std::is_constant_evaluated와 if consteval(since C++23)로 감지할 수 있어요. (since C++20)
상수 평가에 필요한 함수와 변수 (Functions and variables needed for constant evaluation)
다음 표현식이나 변환은 잠재적으로 상수 평가(potentially constant evaluated) 돼요.
- 명백히 상수 평가되는 표현식
- 평가 가능한(potentially-evaluated) 표현식
- 중괄호로 둘러싼 초기화 리스트의 immediate 부분 표현식(변환이 축소(narrowing)인지 판단하기 위해 상수 평가가 필요할 수 있음)
- 템플릿 개체 안에서 일어나는 주소 연산 표현식(그 표현식이 값 의존(value-dependent)인지 판단하기 위해 상수 평가가 필요할 수 있음)
- 위 중 하나의 부분 표현식이되, 중첩된 미평가(evaluated되지 않은) 피연산자의 부분 표현식은 아닌 것
함수는 상수 함수이고 잠재적으로 상수 평가되는 표현식으로 이름 붙여진다면 상수 평가에 필요 하다고 말해요.
변수는 constexpr 변수이거나 비-volatile const-한정 정수 타입 또는 참조 타입이고, 그 변수를 지시하는 식별자 표현식이 잠재적으로 상수 평가된다면 상수 평가에 필요하다고 말해요.
기본(defaulted) 함수의 정의와 함수 템플릿 전문화나 변수 템플릿 전문화(since C++14)의 인스턴스화는 그 함수나 변수(since C++14)가 상수 평가에 필요할 때 촉발돼요.
상수 부분 표현식 (Constant subexpression)
상수 부분 표현식은 표현식 e의 부분 표현식으로서의 평가가 e가 핵심 상수 표현식이 되는 것을 막지 않을 표현식이에요. 이때 e는 다음 중 어떤 것도 아니에요.
throw표현식yield표현식(since C++20)- 대입 표현식
- 쉼표 표현식
참고 (Notes)
피처 테스트 매크로 (Feature-test macro)
| 매크로 | 값 | 표준 | 피처 |
|---|---|---|---|
__cpp_constexpr_in_decltype |
201711L | (C++20) (DR11) | 상수 평가에 필요할 때 함수·변수 정의 생성 |
__cpp_constexpr_dynamic_alloc |
201907L | (C++20) | constexpr 함수에서 동적 저장 기간 연산 |
__cpp_constexpr |
202306L | (C++26) | void*에서 constexpr 캐스트: constexpr 타입 소거로 향해 |
__cpp_constexpr |
202406L | (C++26) | constexpr 배치 new와 new[] |
__cpp_constexpr_exceptions |
202411L | (C++26) | constexpr 예외 |
예제 (Example)
이 섹션은 불완전해요. 사유: 예제 없음.
결함 보고 (Defect reports)
다음 동작 변경 결함 보고는 이전에 발표된 C++ 표준에 소급 적용됐어요.
| DR | 적용 대상 | 발표된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 94 | C++98 | 산술 상수 표현식이 변수와 정적 데이터 멤버를 포함할 수 없었음 | 포함할 수 있음 |
| CWG 366 | C++98 | 문자열 리터럴을 포함하는 표현식이 정수 상수 표현식일 수 있었음 | 아님 |
| CWG 457 | C++98 | volatile 변수를 포함하는 표현식이 정수 상수 표현식일 수 있었음 | 아님 |
| CWG 1293 | C++11 | 문자열 리터럴이 상수 표현식에서 사용 가능한지 불명확했음 | 사용 가능 |
| CWG 1311 | C++11 | volatile glvalue가 상수 표현식에서 사용될 수 있었음 | 금지 |
| CWG 1312 | C++11 | 상수 표현식에서 reinterpret_cast가 금지됐지만 void*와 사이의 캐스트로 같은 효과를 낼 수 있었음 |
타입 cv void*에서 객체 포인터 타입으로의 변환 금지 |
| CWG 1313 | C++11 | 정의되지 않은 동작이 허용됐고 모든 포인터 뺄셈이 금지됨 | UB 금지, 같은 배열 포인터 뺄셈은 OK |
| 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 표현식이 런타임 검사가 없어도 핵심 상수 표현식이 아니었음 |
피연산자 제약이 다형성 클래스 타입의 glvalue로 제한됨 |
| CWG 1581 | C++11 | 상수 평가에 필요한 함수가 정의되거나 인스턴스화될 필요가 없었음 | 필요 |
| CWG 1613 | C++11 | 핵심 상수 표현식이 람다 표현식 안에서 ODR-사용된 참조를 평가할 수 있었음 | 어떤 참조는 평가할 수 없음 |
| CWG 1694 | C++11 | 지역 변수를 가리키는 prvalue 포인터를 참조에 바인딩하는 것이 상수 표현식이었음 | 상수 표현식이 아님 |
| CWG 1872 | C++11 | 핵심 상수 표현식이 constexpr 함수 요구사항을 만족하지 못하는 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의 해결을 구현할 수 없었음 | 복사 생략이 절대 수행되지 않는다고 가정 |
| 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-한정 타입의 변수가 potentially-constant일 수 있었음 | 아님 |
| CWG 2763 | C++11 | [[noreturn]] 위반이 상수 평가 중 감지되지 않아도 됐음 |
필요 |
| CWG 2851 | C++11 | 변환된 상수 표현식이 부동소수점 변환을 허용하지 않았음 | 축소되지 않는 부동소수점 변환 허용 |
| CWG 2907 | C++11 | 핵심 상수 표현식이 std::nullptr_t glvalue에 lvalue-to-rvalue 변환을 적용할 수 없었음 |
적용할 수 있음 |
| CWG 2909 | C++20 | 초기화식이 없는 변수는 기본 초기화가 어떤 초기화를 수행하는 경우에만 상수 초기화될 수 있었음 | 타입이 const-기본-초기화 가능(const-default-initializable)일 때만 상수 초기화 가능 |
| CWG 2924 | C++11, C++23 | [[noreturn]](C++11) 또는 [[assume]](C++23)의 제약을 위반하는 표현식이 핵심 상수 표현식인지 불특정이었음 |
구현 정의 |
| P2280R4 | C++11 | 수명이 이 평가 밖에서 시작된 객체나 참조를 가리키는 식별자 표현식이나 *this를 포함하는 표현식을 평가하는 것이 상수 표현식이 아님 |
상수 표현식일 수 있음 |
더 알아보기
- constexpr 지정자 (C++11) — 변수나 함수의 값을 컴파일 타임에 계산할 수 있음을 지정해요.
- is_literal_type (C++11)(C++17에서 deprecated, C++20에서 제거) — 타입이 리터럴 타입인지 확인하는 클래스 템플릿이에요.
- C documentation for Constant expressions