[Advanced C++] 11. Constexpr function

dev.kelvin·2025년 1월 14일
1

Advanced C++

목록 보기
11/74
post-thumbnail

1. Constexpr Function

constexpr이 아닌 일반 함수는 constexpr 변수값 초기화에 사용할 수 없다 (compile error)

constexpr 변수의 값에는 항상 상수표현식만 올 수 있기 때문에 일반 함수는 올 수 없다

따라서 constexpr 함수를 사용해야 한다

constexpr function

constexpr 함수는 상수 표현식에서 호출이 가능한 함수이다

	constexpr 반환형 함수이름(매개변수)
    {
    
    }

이렇게 함수 앞에 constexpr을 붙이면 해당 함수는 상수 표현식에서 호출이 가능한 constexpr 함수가 된다
(constexpr 키워드가 붙으면 컴파일러와 다른 개발자에게 해당 함수가 상수 표현식에서 사용될 수 있음을 알리는 것이다)

따라서 constexpr함수는 컴파일 타임에 평가된다

	constexpr int getFooInt(int input)
    {
        constexpr int temp = 100;

        return temp * input;
    }

    int main()
    {
        constexpr int FooInt = getFooInt(2);

        return 0;
    }

getFooInt(2)는 컴파일 타임에 반환값을 계산한 후 함수 호출 자체를 값으로 바꾼다
(getFooInt(2) -> 200으로 대체된다)

이때 constexpr함수가 상수표현식으로 사용되려면 조건이 있는데, constexpr함수의 인자는 컴파일 타임에 알 수 있는 값이어야 하며 constexpr함수 내의 모든 표현식과 문장은 컴파일 타임에 평가 가능해야 한다는 점이다 (이때 constexpr이 아닌 로컬 변수가 상수들로 인해 초기화 되었다면 해당 로컬변수는 사용이 가능하다)

또한 constexpr함수가 컴파일 시점에 평가될 때 이 함수가 호출하는 다른 함수들도 전부 컴파일 시점에 평가될 수 있어야 한다 (그래야 컴파일 시점에 결과 반환이 가능하기 때문)

constexpr함수는 꼭 컴파일 타임에만 평가되는 함수가 아니다, 런타임에도 평가될 수 있다 하지만 이런 경우에는 constexpr의 효과가 없다 (일반 함수랑 동일함)

    int main()
    {
        int x{ 5 }; // not constexpr
        int y{ 6 }; // not constexpr

        std::cout << greater(x, y) << " is greater!\n"; //인자가 컴파일 타임에 알 수 없는 값이기 때문에 (상수 표현식이 아님), 해당 함수는 런타임에 평가된다

        return 0;
    }

constexpr 함수는 컴파일 타임에 필요하다면 컴파일 타임, 그렇지 않다면 런타임에 평가된다

	constexpr int getFoo(int a)
    {
    	return a;
    }
    
    int main()
    {
    	int x { getFoo(10) };    	
    }

int x가 상수 표현식이 아니기 때문에 컴파일 타임에 getFoo()가 평가될 필요가 전혀 없다, 따라서 반드시 컴파일 타임에 평가되지는 않는다 (런타임에 평가될 수 있음)

하지만 constexpr 함수를 사용하는 이유 자체가 컴파일 타임에 평가하기 위함이기 때문에 대부분 컴파일 타임 평가 용도로 사용하는것이 좋다

constexpr 함수의 매개변수는 암시적으로 constexpr이 아니며 명시적으로 constexpr을 붙일수도 없다, 따라서 매개변수를 상수 표현식에 사용할 수 없다

	constexpr int foo(int a)
    {
    	constexpr int temp {a}; //compile error (a는 constexpr(상수표현식)이 아님)
    }

constexpr 함수가 컴파일 타임에 어떻게 평가될까?

constexpr 함수가 컴파일 타임에 평가되기 위해서는 컴파일러는 constexpr 함수의 정의를 알고 있어야 한다

단순히 선언만 보고 컴파일러는 해당 constexpr 함수를 컴파일 타임에 평가할 수 없다

    constexpr int add(int x, int y); //X

    constexpr int add(int x, int y)  //O
    {
        return x + y; 
    }

그렇다면 다른 파일에서 이 constexpr 함수를 호출하려고 할 때 함수의 선언뿐 아니라 정의를 알아야 한다는것이다, 이는 이전에 정리한 ODR (하나의 정의만 존재해야 한다)에 위배된다

따라서 constexpr 함수는 기본적으로 inline으로 간주된다, inline 함수는 함수 정의가 동일하다는 가정하에 여러 파일에 존재해도 컴파일러가 중복으로 간주하지 않는다 -> ODR 규칙 위배하지 않음

보통 constexpr 함수는 .h에서 선언과 동시에 정의하며 다른 cpp에서 #include로 가져와 사용하게 한다

constexpr 정리

    int main()
    {
        constexpr int g { greater(5, 6) }; //항상 컴파일 타임에 평가
        std::cout << g << " is greater!\n";

        std::cout << greater(5, 6) << " is greater!\n"; //컴파일타임 or 런타임평가 (꼭 컴파일 타임에 평가가 필요하지 않기때문에)

        int x{ 5 }; 
        std::cout << greater(x, 6) << " is greater!\n";  //constxpr (상수표현식)이 아닌 변수가 인자로 들어갔기 때문에 일반적으로 런타임 평가 (컴파일 타임에 x가 5로 알려졌기 때문에 as-if룰로 컴파일 타임에 평가될수도 있지만 일반적으로 더 가능성이 높은 런타임에 평가된다)

        std::cin >> x;
        std::cout << greater(x, 6) << " is greater!\n";  //무조건 런타임

        return 0;
    }

그렇다면 해당 함수를 무조건 컴파일 타임에 평가시키려면 어떻게 할까?

consteval

C++20에 도입된 consteval은 해당 함수를 컴파일 타임에 반드시 평가시키며 그렇지 못하면 컴파일 에러를 발생시킨다, 이러한 함수들을 immediate function이라 한다

	consteval int foo(int a, int b)
    {
    	return a + b;
    }
    
    int main()
    {
    	constexpr int k { foo(10, 20) }; //compile time evaluate
        
        std::cout << foo(10, 20) << '\n'; //compile time evaluate (꼭 컴파일 타임에 필요하지는 않지만 consteval함수이기 때문에 항상 compile time에 평가된다)
        
        int x{10};
        foo(x, 20); //compile error
    }

상수 표현식이 아닌 x가 consteval 함수의 인자로 들어갔기 때문에 컴파일 타임에 평가할 수 없다, 따라서 compile error를 발생시킨다

constexpr 함수와 마찬가지로 consteval 함수의 매개변수도 constexpr이 아니다

그렇기때문에 consteval은 constexpr 함수보다 덜 유연하다, constexpr은 컴파일/런타임 둘 다 가능하지만 consteval 함수는 컴파일 타임만 가능하기 때문이다

하지만 현재로서 constexpr 함수 호출이 컴파일 타임인지 런타임에 평가되는지 확인하는 매커니즘은 제공되지 않는다

std::is_constant_evaluated

std::is_constant_evaluated는 현재 함수가 해당 코드상 컴파일 타임에 평가되어야 하는지 bool로 알려준다

    constexpr int someFunction()
    {
        if (std::is_constant_evaluated()) // 컴파일 타임에 평가되는지 확인
            return 1; // 컴파일 타임에만 실행될 코드
        else
            return 2; // 런타임에서 실행될 코드
    }

    int main() {
        constexpr int val = someFunction(); // 컴파일 타임에 평가되는 경우
        std::cout << val << std::endl; // 1이 출력됨
        std::cout << someFunction() << std::endl; // 2가 출력됨 (반드시 컴파일 타임에 평가될 필요가 없기 때문에 런타임에 평가되었나봄)
        return 0;
    }

하지만 std::is_constant_evaluated는 실제로 평가된 결과가 아닌 해당 코드가 컴파일 타임에 평가 되어야만 하는 상황인지 아닌지를 판단하고 bool로 return한다, 따라서 실제 결과가 다를 수 있다

constexpr, consteval 함수의 여러 특징

constexpr, consteval 함수에는 상수값이 아닌 local변수나 함수 인자도 사용이 가능하다

	consteval int doSomething(int x, int y)
    {
        x = x + 2;       

        int z { x + y }; 
        if (x > y)
            z = z - 1;   

        return z;
    }

constexpr 함수 내부에서 로컬변수, 매개변수가 컴파일 타임에 해당 값이 정해지면 상수가 아니어도 사용 가능한 것이다

    constexpr int goo(int c) 
    {
        return c;
    }

    constexpr int foo(int b)
    {
        return goo(b);  // foo()가 컴파일 타임에 평가되면 goo(b)도 컴파일 타임에 평가 가능
    }

constexpr 함수는 상수 표현식에서 호출될때는 다른 constexpr 함수들만 호출할 수 있다, 이때 constexpr이 상수 표현식에서 호출되지 않는다면 constexpr이 아닌 함수도 호출할 수 있다

C++23 전에는 constexpr함수 내부에서 constexpr이 아닌 함수를 호출하는것이 문법적으로 잘못된 형식이었으나 이후에는 가능하도록 변경되었다

constexpr 함수를 잘 사용하려면?

  • 가능한 constexpr 함수 내부에서는 constexpr 함수만 호출하자
  • constexpr 함수가 상수 표현식, 비상수 표현식에서 서로 다른 동작을 해야할 경우 if(std::is_constant_evaluated())를 사용하여 조건 체크를 해주자
  • 항상 상수 표현식에서 constexpr 함수를 테스트 해보자, 비상수 표현식에서는 잘 작동해도 상수 표현식에서는 잘 작동하지 않을 수 있기 때문이다
  • 순수 함수(pure function)을 constexpr로 만들어 사용하자, 이때 pure function이란 동일한 인자를 넘기면 항상 동일한 return값을 반환하고 부작용 (외부 변수 변경 및 입출력이 없는)이 없는 함수를 말한다
    (물론 항상 순수 함수에만 사용하지는 않는다)

그렇다면 왜 모든 함수를 constexpr로 사용하지 않을까?

  • 당연하지만 constexpr은 해당 함수가 상수 표현식에서 사용될 수 있음을 알린다, 상수 표현식에서 사용되지 않는다면 붙히지 않는다
  • constexpr은 interface로서 다른 constexpr 함수에서 호출될 수 있고 상수 표현식에서 사용할 수 있다는 의미를 전달한다
  • constexpr함수가 컴파일타임에 평가된다면 런타임에 함수 검사가 불가능하여 디버깅이 힘들다

꼭 필요한 곳에 constexpr 키워드를 붙여서 사용하자

profile
GameDeveloper🎮 Dev C++, DataStructure, Algorithm, UE5, Assembly🛠, Git/Perforce🌏

0개의 댓글