[C++] 상수 : const, constexpr, consteval에 대한 이해

C++은 크게 두 가지 불변성 표기법을 지원한다.

  • const : "이 값을 바꾸지 않겠다고 약속해" 의 의미로, 변수가 한 번 초기화된 이후로 값을 변경할 수 없도록 한다. 컴파일러는 const로 명시한 약속을 이행한다. 또한 const의 값은 런타임에 계산될 수 있다. (뒤에서 자세하게 살펴보자!)
  • constexpr : "컴파일 타임에 평가된다" 정도의 의미로 주로 상수를 명시하거나 성능을 높이기 위해 쓴다. 값을 컴파일러가 미리 계산하기 때문.

근데 여기서 컴파일, 런타임이 헷갈릴 수 있다.

컴파일 타임 그리고 런타임.

음 C는 컴파일러 언어이기에 소스 코드부터 실행파일이 실행되기까지는 알다싶이 다음과 같은 과정을 가진다.

전처리 - 컴파일 - 링킹 - 실행

그리고 여기서 컴파일의 모든 과정을 컴파일 타임이라 지칭한다. 소스 파일이 어셈블리어로 변환된다.

런타임은 실행파일을 실행해 프로그램이 실제 실행단계에서 종료까지의 모든 과정을 말한다. 이때 사용자의 입력을 받아서 변수가 입력되고 메모리에 할당되는 등 처리된다. 이 과정에서 발생하는 오류를 런타임 오류라한다.

예제로 이해하기

const int a = 5;
a = 10; // 오류 발생: a는 const로 선언되었으므로 변경할 수 없다.

constexpr int b = 10; // b는 컴파일 타임에 10으로 초기화된다.
constexpr int square(int x) { return x * x; }
constexpr int c = square(b); // c도 컴파일 타임에 계산된다. 

square 함수는 constexpr로 정의되었어. 이는 컴파일 타임에서 값이 결정된다는 얘기야. 또한 int c 변수도 컴파일 타임에서 결정되니 그 시점에 c는 상수값을 이미 가지고 있게 되는거지. square(b)도 상수값.

const int d = square(b); // 정상: 컴파일 타임에 계산. b는 컴파일 타임에 상수로 초기화 되었고, square(b)도 컴파일 타임에 계산되어 100이 됨. 그 값이 d로 상수 고정
const int e = square(10); // 정상: 이하 비슷함
double sum(const vector<double>&); // sum은 인수를 수정하지 않는다.

vector<double> v {1.1, 1.2, 1.3}; // vector는 변수다. 상수가 아니다.
const double s1 = sum(v); // 정상: sum(v)는 런타임에 평가된다. 
constexpr double s2 = sum(v); // 오류 발생: sum(v)는 상수가 아니다. sum(v)는 런타임에 값이 결정된다.

에러의 이유 두가지

  • sum() 함수 자체가 constexpr이 아니다. constexpr변수는 컴파일 타임에서 결정돼야 해. 근데 sum()은 일반적으로 정의되었으니 런타임에 결정되고 이행되지 않은 결과를 담을 수 없어.
  • 인수인 vector는 일반적으로 constexpr를 적용할 수 없어, 왜냐면 동적 메모리를 사용하는 자료형이기 때문에 컴파일 타임에서 평가되는 상수가 될 수 없지. 결정되지 않은 변수를 인수로 사용할 수도 없어.

constexpr 함수

constexpr double square(double x) {return x*x;}

컴파일 타임에 값이 결정될 수도 있는 함수

constexpr double max1 = 1.4*square(17); // 1.4*square(17) 상수를 인수로 받아서 컴파일 타임에 결정됨. 결과도 상수식.
constexpr double max1 = 1.4*square(var); // 오류: var는 변수이므로 square(var)는 런타임에 평가된다. 컴파일 타임에 결정되는 contexpr 변수에 담을 수 없다.

const double max3 = 1.4*square(var); // 정상: 런타임에 평가될 수 있다.

이렇듯 런타임에도 평가될 수 있는게 constexpr이다. 만약 컴파일 타임에만 평가시키고 싶다면 consteval를 사용한다.

consteval

  • 이 키워드를 사용하면 반드시 컴파일 타임에서 호출되어야 한다. 항상 컴파일 타임에서 계산된다.
consteval int square(int x) { return x * x; }
constexpr int d = square(2); // d는 컴파일 타임에 계산된다.
const int e = square(2); // 정상: 컴파일 타임에만 값이 계산될 뿐. const 변수는 상수값으로 런타임까지 그 값을 유지한다.

출처

⌜비야네 스트롭스트룹⌟, "A Toor Of C++",에이콘