여러 가지 방식으로 실행할 수 있는 어떤 실험의 결과를 담는 자료구조가 필요하다고 합시다. 예를 들어 실험 과정에서 조명의 밝기를 높게 설정하거나, 낮게 설정하거나, 아예 끌 수도 있습니다. 또한 선풍기 속도나 온도도 높음, 낮음, 끔으로 설정할 수 있습니다. 실험에 관련된 환경 조건이 n개이고 각 조건이 가질 수 있는 상태가 세 가지라고 한다면, 가능한 조합은 총 3의 n승 가지입니다. 따라서 조건들의 모든 조합에 대한 실험 결과를 저장하려면 적어도 3의 n승개의 값을 담을 수 있는 자료구조가 필요합니다. 각 실험 결과가 하나의 int 값이고 n이 컴파일 도중에 알려진다고(또는 계산할 수 있다고) 가정할 때, 그러한 자료구조로는 std::array가 적당할 것입니다. 그런데 컴파일 도중에 3의 n승을 계산하려면 어떻게 해야 할까요?? 필요한 수학적 기능성을 제공하는 C++ 표준 라이브러리 함수로는 std::pow가 있지만, 두 가지 문제점 때문에 이것을 그대로 사용할 수는 없습니다. 첫째로, std::pow는 부동소수점 형식에 대해 작동하지만 지금 필요한 것은 정수 결과입니다. 둘째로, std::pow는 constexpr이 아닙니다(즉, 컴파일 시점 값으로 후출해도 반드시 컴파일 시점 결과를 돌려준다는 보장이 없습니다). 따라서 std::array의 크기를 지정하는 데에는 사용할 수 없습니다.
이 두 가지 문제점은 지금 여구에 맞는 pow 함수를 직접 작성하면 해결됩니다. 구체적인 구현은 잠시 후에 제시하고, 우선 그러한 함수를 선언하고 사용하는 코드부터 봅시다.
constexpr
int pow(int base, int exp) noexcept // pow는 결코 예외를 던지지 않는 constexpr 함수
{
... // 구현은 나중에
}
constexpr auto numConds = 5; // 조건들의 개수
std::array<int, pow(3, numConds)> result; // result는 3^numConds 개의 요소들을 담음
pow 앞에 constexpr이 있다고 해서 pow가 반드시 const 값을 돌려주는 것은 아님을 기억하기 바랍니다. 그 constexpr은 오직 base와 exp가 컴파일 시점 상수일 때에만 pow의 결과를 컴파일 시점 상수로 사용할 수 있다는 뜻입니다. 만일 base나 exp 중 하나라도 컴파일 시점 상수가 아니면 pow의 결과가 실행 시점에서 계산될 수 있습니다. 이는 pow를 이를테면 std::array의 크기를 컴파일 시점에서 계산하는 용도로 사용할 수 있을 뿐만 아니라, 다음 예처럼 실행 시점의 문맥에서 사용할 수도 있음을 뜻합니다.
auto base = readFromDB("base"); // 실행시점에서 값들을 구함
auto exp = readFromDB("exponent");
auto baseToExp = pow(base, exp); // 실행시점에서 pow 함수를 호출함
constexpr 함수는 컴파일 시점 값들로 호출했을 때 반드시 컴파일 시점 결과를 산출할 수 있어야 하므로, 구현에 일정한 제약들이 따릅니다. 그런데 C++과 C++14의 제약들이 조금 다릅니다.
C++11에서 constexpr 함수는 실행 가능 문장이 많아야 하나이어야 하고, 보통의 경우 그 문장은 return 문일 수밖에 없습니다.
(* "많아야" 하나임을 주의하기 바랍니다. 즉, 실행 가능 문장의 개수는 0 또는 1입니다. 잠시 후에 나오는 Point 생성자의 예에서 보겠지만, 명시적인 실행 가능 문장이 아예 없을 수도 있습니다.)
이것이 큰 제약이긴 하지만, 두 가지 요령을 이용하면 constexpr 함수의 표현력을 생각 이상으로 확장할 수 있습니다. 하나는 조건부 연산자(삼항 연산자) "?:"을 if-else 문 대신 사용하는 것이고, 또 하나는 루프 대신 재귀를 사용하는 것입니다. 다음은 이 요령들을 이용해서 pow를 구현한 예입니다.
constexpr int pow(int base, int exp) noexcept
{
return (exp == 0 ? 1 : base * pow(base, exp - 1));
}
(* 이 구현 예들의 거듭제곱 계산 알고리즘은 지수가 0 또는 양수일 때에만 유효합니다. 따라서 exp 매개변수를 unsigned 형식으로 선언하는 것이 바람직합니다.)
이 함수가 잘 작동하긴 하지만, 함수형 프로그래밍에 익숙한 프로그래머가 아닌 이상 이런 코드를 좋게 생각하는 사람은 거의 없을 것입니다. C++14에서는 xonstexpr 함수에 대해 제약이 상당히 느슨해져서, 다음과 같은 구현이 허용됩니다.
constexpr int pow(int base, int exp) noexcept // C++14
{
auto result = 1;
for (in ti = 0; i < exp; ++i) result *= base;
return result;
}
constexpr 함수는 반드시 리터럴 형식(literal type)들을 받고 돌려주어야 합니다. 직므 논의에 맞게 간단히 이야기하자면, 리터럴 형식은 컴파일 도중에 값을 결정할 수 있는 형식입니다. C++11에서 void를 제외한 모든 내장 형식이 리터럴 형식에 해당합니다. 그리고 생성자와 적절한 멤버 함수들이 constexpr인 사용자 형식도 리터럴 형식이 될 수 있습니다.
'컴퓨터과학' 카테고리의 다른 글
[Effective C++] (15-4) 가능하면 항상 constexpr을 사용할 것 / (16-1) const 멤버 함수를 스레드에 안전하게 작성할 것 (0) | 2020.06.06 |
---|---|
[Effective C++] (15-3) 가능하면 항상 constexpr을 사용할 것 (0) | 2020.06.05 |
[Effective C++] (15-1) 가능하면 항상 constexpr을 사용할 것 (0) | 2020.06.03 |
[Effective C++] (14-4) 예외를 방출하지 않을 함수는 noexcept로 선언할 것 (0) | 2020.06.02 |
[Effective C++] (14-3) 예외를 방출하지 않을 함수는 noexcept로 선언할 것 (0) | 2020.06.01 |