프로그래밍 언어/C++

C++ 상수식 (constexpr)

ShovelingLife 2022. 7. 22. 09:49
  • constexpr은 const처럼 변수에 적용 할 수 있으며 해당 변수에 대한 변경을 시도하면 컴파일러는 에러를 발생한다. const와는 다르게 생성자에 적용 할 수 있다.
  • constexpr로 지정된 값이나 리턴 값이 상수이며 컴파일 타임에 계산된다. 템플릿 인수 및 배열 선언 같은 const 정수 값이 사용되는 곳에 constexpr 정수 값이 사용 될 수 있다.
  • 컴파일 타임에 값이 계산되면 프로그램 실행 속도가 빨라지며 메모리 사용량이 줄어든다. 

constexpr 리턴 값

 constexpr 함수의 리턴 값이 constexpr 속성을 가지려면 리터럴 타입이어야 한다.

const int64_t const_mypow(int64_t itarget, int isquare)
{
    if (isquare <= 1)  return itarget;
    return itarget * const_mypow(itarget, isquare - 1);
}

// constexpr의 리턴 값을 가지는 리터럴 함수
constexpr int64_t constexpr_mypow(int64_t itarget, int isquare)
{
    if (isquare <= 1)  return itarget;
    return itarget * constexpr_mypow(itarget, isquare - 1);
}

int _tmain(int argc, _TCHAR* argv[])
{
    std::array<int, 256>                        arr;            // 가능 = 상수
    std::array<int, const_mypow(2, 16)>         const_arr;      // error C2975 컴파일 상수식이 필요합니다.
    std::array<int, constexpr_mypow(2, 16)>     constexpr_arr;  // 가능 = constexpr 리턴 함수
    return 0;
}

constexpr 변수

 const와 constexpr 변수의 가장 큰 다른점은 const는 런타임까지 초기화를 지연 할 수 있지만 constexpr는 컴파일 타임에 초기화 되어야 한다.

class Rect
{
public:
    constexpr Rect(int width, int height) :m_iwidth(width), m_iheight(height)
    {}

    constexpr int getArea() const { return m_iwidth * m_iheight;}
private:
    int m_iwidth;
    int m_iheight;
};  
 
// Rect라는 객체를 constexpr로 호출 할 수 있습니다.
// constexpr 생성자가 존재하기 때문에 가능합니다. 
constexpr Rect ce_rect = Rect(10, 10);
ce_rect.getArea();                          // const로 선언된 함수를 호출 할 수 있습니다.

constexpr int x = 205
constexpr float y{ 15 };
constexpr int i;                            // Error 초기화되지 않음
int j = 0;
constexpr int k = j + 1;                    // Error j는 상수표현식이 아닙니다.

constexpr 함수

 constexpr 함수에서 주의 할 점은 constexpr 함수로 선언되어 있다고 해서 무조건 컴파일 타임에 리턴값이 산출되지 않는다.

// mypow는 constexpr 함수입니다. 
// 전달 받은 인자가 runtime에 초기화된다면 일반 함수 처럼 동작
// 전달 받은 인자가 컴파일 타임에 초기화된다면 일반 함수 처럼 동작
constexpr int64_t mypow(int64_t itarget, int isquare)
{
    if (isquare <= 1)  return itarget;
    return itarget * mypow(itarget, isquare - 1);
}

int _tmain(int argc, _TCHAR* argv[])
{
    // 런타임에 초기화되는 randow 변수
    auto current = std::chrono::system_clock::now();
    auto duration = current.time_since_epoch();
    std::mt19937_64 getMT(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
    std::uniform_int_distribution<int64_t> uniformDist(1, 10);

    // constexpr 함수 mypow를 런타임에 초기화되는 인자로 호출 
    // 일반 함수처럼 동작 합니다. 
    auto value1 = mypow(uniformDist(getMT), static_cast<int>(uniformDist(getMT)));
    
    // constexpr 함수 mypow를 constexpr 인자로 호출 constexpr 값을 리턴
    constexpr auto value2 = mypow(10, 2);

    std::array<int, value1> array1;
    std::array<int, value2> array2;

    return 0;
}

constexpr c++14 개선 사항

// c++ 11에서는 한줄의 리턴식으로 표현 해야 하는 제한이 있습니다.
constexpr int64_t mypow(int64_t itarget, int isquare)
{
    return (isquare <= 1) ? itarget : itarget * mypow(itarget, isquare - 1);
}

// c++ 14에서는 한줄로 표현하는 제한이 사라졌습니다. 
constexpr int64_t mypow(int64_t itarget, int isquare)
{
    if (isquare <= 1)  return itarget;
    return itarget * mypow(itarget, isquare - 1);
}

// 루프를 통해서도 구현 가능
constexpr int64_t mypow(int64_t itarget, int isquare)
{
    for (int i = 1; i < isquare; i++)
    {
        itarget *= itarget;
    }
    return itarget;
}

constexpr함수 조건

  • constexpr 함수는 리터럴 타입만 받아들이거나 리턴한다. 
  • constexpr 함수는 재귀적일 수 있다. 
  • virtual 함수가 될 수 없다.
  • Body에서 가능한 구문
    • null Statement
    • static_assert
    • typedef, using
    • 함수가 생성자가 아니라면 하나의 리턴 구문 필요함
    • 등등...
  • Body에서 불가능한 구문
    • goto 
    • try-block
    • 초기화 수행이 없는 변수 정의
    • 리터럴타입이 아닌 변수 정의
    • Static 변수
    • tls 변수(thread local storage)
    • 등등...
// constexpr 함수에서 사용하지 못하는 구문을 정리합니다. 
constexpr int64_t NotConstexprFunc(int64_t itarget, int isquare)
{
    goto FAIL;        // goto문을 사용할 수 없습니다.

    try{}             // try 구문 사용 할 수 없습니다.
    catch(...){}

    thread_local int t_var; // thread_local 변수는 사용할 수 없습니다.
    static int s_var; // static 변수 사용 할 수 없습니다.
    CUser user;       // 리터럴 타입아닌 변수 정의 불가 
    double var;       // 초기화 되지 않은 변수 정의 불가
    
FAIL:                 // 레이블을 사용할 수 없습니다.
}

 

출처 : https://jungwoong.tistory.com/54