constexpr

하루공부·2024년 1월 20일
0

C++

목록 보기
15/25
post-thumbnail

C++ 아이콘 제작자: Darius Dan - Flaticon


constexpr

  • constexpr 키워드는 객체나 함수 앞에 붙일 수 있는 키워드로 리턴 값을 컴파일 타임에 알 수 있다
    라는 의미를 전달한다.
    • 컴파일 타임에 어떠한 식의 값을 결정할 수 있다면 해당 식을 상수식 (Constant expression) 이라고 표현
    • 상수식들 중에서 값이 정수인 것을 정수 상수식 (Integral constant expression)

배열 선언식 arr[size] 가 컴파일 되기 위해선 size가 정수 상수식이여야 하고
템플릿 타입 인자도 정수 상수식, enum에서 값을 지정할 때도 정수 상수식이여야 한다.
C++ 언어상 정수 상수식이 등장하는 곳은 매우 많다.


  • 객체의 정의에 constexpr이 오게 된다면, 해당 객체는 어떠한 상수식에도 사용될 수 있다.


constexpr vs const

  • 같은 상수로 값을 변경할 수 없다는 공통점이 있다.
  • 하지만 const 상수는 굳이 컴파일 타임에 그 값을 알 필요가 없다.
int a;
const int b = a;

b는 컴파일 타임에 값을 알 수 없지만 값을 지정해주면 값을 바꿀 수 없다는 것은 확실하다.
만약 상수식으로 초기화 한다면 컴파일러 마음대로 어느 타임에 초기화가 가능하다.
그래서 정확히 컴파일 타임 상수를 얻고자 하면 constexpr를 사용하자.


int a;
constexpr int b = a; // 잘못됨

constexpr 변수는 반드시 오른쪽에 다른 상수식이 와야한다.
하지만 위의 예제는 컴파일러 입장에서 컴파임타임에 a가 머일지 모른다.
==> 컴파일 오류



constexpr 함수

  • 기존에 컴파일 타임 상수인 객체를 만드는 함수는 TMP를 사용하여 만들었다.

    하지만 구현된 코드를 이해하기 어렵고
    반복문들은 재귀 호출의 형태로 구현해야 해서 복잡하다.


  • 이제는 함수 리턴 타입에 constexpr을 사용해 조건만 맞는다면 쉽게 만들 수 있다.
constexpr int Factorial(int n) {
	int total = 1;
	for (int i = 1; i <= n; i++) { total *= i; }
	return total;
}

template <int N>
struct A {
	int operator()() { return N; }
};

int main() {
	A<Factorial(10)> a; // Factorial(10) 이 컴파일 타임에 계산
	std::cout << a() << std::endl;
}

  • C++ 14 부터 아래와 제약 조건들은 constexpr 함수 내부에서 사용 불가하다.
    • goto 문 사용
    • 예외 처리 (try 문; C++ 20 부터 가능하게 바뀌었습니다.)
    • 리터럴 타입이 아닌 변수의 정의
    • 초기화 되지 않는 변수의 정의
    • 실행 중간에 constexpr 이 아닌 함수를 호출하게 됨

리터럴 타입

  • 리터럴 타입은 컴파일러가 컴파일 타임에 정의할 수있는 타입이다.
  • C++에서 정의하는 리터럴 타입은 아래와 같다
    • void 형
    • 스칼라 타입 (char, int, bool, long, float, double) 등등
    • 레퍼런스 타입
    • 리터럴 타입의 배열
    • 디폴트 소멸자를 가지고 다음 중 하나를 만족하는 타입
      • 람다 함수
      • Arggregate 타입 (사용자 정의 생성자, 소멸자가 없으며 모든 데이터 멤버들이 public) 쉽게 말해 pair 같은 애들을 이야기함
      • constexpr 생성자를 가지며 복사 및 이동 생성자가 없음


constexpr 생성자

  • constexpr 덕분에 사용자가 직접 리터럴 타입을 만들 수 있게 되었다

  • constexpr 생성자는 인자들이 반드시 리터럴 타입이여야 하고 해당 클래스는 다른 클래스를 상속 받을 수 없다.

class Vector {
public:
	constexpr Vector(int x, int y) : x_(x), y_(y) {} // 리터럴 타입을 인자로 받음
	constexpr int x() const { return x_; }
	constexpr int y() const { return y_; }
private:
	int x_;
	int y_;
};

constexpr Vector Add(const Vector& v1, const Vector& v2) { return { v1.x() + v2.x(), v1.y() + v2.y() }; }

template <int N>
struct A { int operator()() { return N; } };

int main() {
	constexpr Vector v1{ 1, 2 };
	constexpr Vector v2{ 2, 3 };

	A<v1.x()> a;    // constexpr 객체의 constexpr 멤버 함수는 역시 constexpr!
	std::cout << a() << std::endl;

	A<Add(v1, v2).x()> b;    // AddVec 역시 constexpr 을 리턴한다.
	std::cout << b() << std::endl;
}
  • 두 멤버 변수를 접근하는 함수 역시 constexpr로 정의했다.
    ==> 두 멤버 변수를 접근하는 함수 역시 constexpr로 정의
  • constexpr 생성자를 만들어서 해당 클래스 객체 생성을 constexpr로 만들 수 있다.
    ==> 해당 생성자가 없으면 못 만듦

A<v1.x()> a;
그리고 v1의 constexpr 멤버 함수인 x를 호출하였는데
x 역시 constexpr 함수이므로 위 코드는 결국 A<1> a 와 다름이 없다
만일 v1 이나 x 가 하나라도 constexpr 이 아니라면 위 코드는 컴파일 오류 발생



if constexpr

  • if constexpr은 우선 조건이 반드시 bool 타입으로 변환될 수 있어야 하는 컴파일 타임 상수식이여야 한다.
    ==> 해당 조건의 true와 false에 따라서 if - else가 각각 아예 컴파일이 되지 않음

    어떠한 타입인지 검사를 해서 해당 타입에 대해서만 어떠한 작업을 하고 아니라면 일반적인 작업을 하는 로직을 짜고 싶을 때 이용한다.


공부한 내용 복습

개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!

0개의 댓글