728x90
반응형

constexpr은 객체에 사용하면 const의 향상된 버전이지만 함수에 사용하면 다른 의미를 가진다.

그러나 일반적으로 constexpr이 나타내는 의미는 컴파일 시간에 알려진 값을 나타낸다.

 

constexpr 객체

constexpr은 객체를 const로 만들고, LiteralType(값이 알려져 있고 컴파일 시점에 사용할 수 있는 객체)으로 만든다.

이렇게 컴파일 중에 알려진 값은 여러 권한이 있는데 예를 들어 읽기 전용 메모리에 저장될 수 있으며 불변의 상수 표현식이 필요할 때 대신 사용할 수도 있다.(배열 크기나 템플릿 매개변수(std::array 객체 길이 포함), 열거형 멤버의 값, 정렬 지정자 등이 포함된다.)

이러한 작업을 수행하기 위해 변수를 사용하려는 경우 컴파일러에서 컴파일 시점의 값을 확인하므로 반드시 constexpr로 선언해야 한다.

int sz; // 비constexpr 변수
...
constexpr auto arraySize1 = sz; // 오류! 컴파일 시점에 sz값을 알 수 없다.
std::array<int, sz> data1; // 오류! 컴파일 시점에 sz값을 알 수 없다.

constexpr auto arraySize2 = 10; // 가능! 10은 컴파일 시점 상수이다.
std::array<int, arraySize2> data2; // 가능! arraySize2는 constexpr이다.

const객체는 컴파일 시점에 알려진 값으로  반드시 초기화되지 않기 때문에 const는 constexpr과 동일한 보장을 하지 않는다.

int sz;
...

const auto arraySize = sz; // 가능! arraySize는 sz의 const 복사본
std::array<int, arraySize> data; // 오류! arraySize값은 컴파일 시점에 알려지지 않음

즉, 모든 constexpr 객체는 const 객체이지만 모든 const 객체가 constexpr 객체인 것은 아니다.

 

constexpr 함수

constexpr 함수는 두 가지 면이 있다.

  • 컴파일 시점 상수를 요구하는 문맥에 constexpr함수를 사용할 수 있다. constexpr 함수에 전달된 모든 매개변수를 컴파일 시점에 알 수 있는 경우 결과는 컴파일 도중에 계산된다. 인수의 값이 컴파일 시점에 알 수 없다면 컴파일은 거부된다. 
  • 컴파일 시점에 알려지지 않는 하나 이상의 값들로 constexpr함수를 호출하면 함수는 일반적인 함수처럼 동작한다. 즉, 그 결과는 실행 시점에서 계산된다.

이는 이전 기능의 손실 없이 constexpr 함수를 선언할 수 있음을 의미한다.

constexpr을 사용할 때 C++11과 C++14의 다른 점을 살펴보자.

 

C++11

return문을 최대 하나만 포함할 수 있다. 하지만 이 조건은 삼항 연산자(?-:)를 사용하여 조건문을 대체할 수 있고, 재귀를 사용하여 루프를 대체할 수도 있다.

그리고 멤버 함수는 암묵적으로 const로 선언되며, C++11에서 void는 리터널 타입이 아니기 때문에 void를 반환할 수 없다.

constexpr int pow(int base, int exp) noexcept
{
    return (exp == 0 ? 1 : base * pow(base, exp - 1));
}

 

C++14

constexpr함수가 리터널타입이 아닌 타입도 리턴할 수 있게 되었고, constexpr함수의 return문이 많아야 하나이어야 한다는 제약도 사라졌다. 그리고 멤버 함수일 때 더 이상 암시적으로 const가 아니다.

constexpr int pow(int base, int exp) noexcept
{
    auto result = 1;
    for (int i = 0; i < exp; ++i)
        result *= base;
    
    return result;
}

 

그리고 생성자와 적절한 멤버 함수들이 constexpr인 사용자 타입도 리터널 타입이 될 수 있다.

class Point
{
public:
    constexpr Point(double xVal = 0, double yVal = 0) noexcept
    : x(xVal), y(yVal)
    {}
    
    constexpr double xValue(void) const noexcept { return x; }
    constexpr double yValue(void) const noexcept { return y; }
    
    void setX(double newX) noexcept { x = newX; }
    void setY(double newY) noexcept { y = newY; }
    
private:
    double x, y;
};

constexpr Point p1(9.4, 27.7); // 가능! constexpr 생성자가 컴파일 시점에서 실행됨 
constexpr Point p2(28.8, 5.3); // 가능!

// constexpr 멤버 함수들을 호출
constexpr Point midpoint(const Point& p1, const Point& p2) noexcept
{
    return { (p1.xValue() + p2.xValue()) / 2, (p1.yValue() + p2.yValue()) / 2 }; 
}

constexpr auto mid = midpoint(p1, p2); // constexpr 함수의 결과를 이용해서 constexpr 객체를 초기화한다.

여기서 point의 생성자는 constexpr로 선언되는데, 전달된 매개변수를 컴파일 시점에 알 수 있다면 생성된 point의 멤버 변수도 컴파일 시점에 알 수 있기 때문에 point는 constexpr로 초기화될 수도 있다.

마지막으로, C++14에서는 더 이상 constexpr함수가 암시적으로 const인 경우가 아니므로 setter도 constexpr가 될 수 있다.

class Point
{
public:
    ...
    
    constexpr void setX(double newX) noexcept
    { x = newX; }
    
    constexpr void setY(double newY) noexcept
    { y = newY; }
    
    ...
};

---
// C++14: 이를 이용해서 이제 이런 함수를 작성할 수 있다. 원점을 기준으로 p와 대칭인 Point 객체를 돌려준다.
constexpr Point reflection(const Point& p) noexcept
{
    Point result; // 비const Point를 생성
    
    result.setX(-p.xValue()); // 그 Point의 x와 y를 설정
    result.setY(-p.yValue());
    
    return result; // 그 복사본을 반환
}

constexpr Point p1(9.4, 27.7);
constexpr Point p2(28.8, 5.3);
constexpr auto mid = midpoint(p1, p2);

// reflectedMid의 값은 (-19.1, -16.5)이다; 이는 컴파일 도중에 알 수 있다.
constexpr auto reflectedMid = reflection(mid);

 

요약

  • constexpr 객체는 const이며, 컴파일 도중에 알려지는 값들로 초기화된다.
  • constexpr 함수는 그 값이 컴파일 도중에 알려지는 인수들로 호출하는 경우에는 컴파일 시점 결과를 산출한다.
  • constexpr 객체나 함수는 비 constexpr 객체나 함수보다 광범위한 문맥에서 사용할 수 있다.
  • constexpr은 객체나 함수의 인터페이스 일부이다.
728x90
반응형