본문 바로가기

PL/C++

[C++20] #define sz(x) (int)x.size()는 이제 그만

개요

#define sz(x) (int)x.size()

C++로 PS를 좀 해본 사람이라면 누구나 공감할 만한 매크로일 것이다.
생소한 사람들을 위해 사용 예시를 들어 보자면, 다음과 같다.

std::vector<int> vec;

// do something

for (int i = 0; i < sz(vec); ++i) {
    // ...
}

대충 벡터 등의 컨테이너 크기를 구하는 데 쓸 수 있다.
하지만 이렇게 쓸 거라면 그냥 vec.size()로 쓰지 않고 왜 굳이 (int)로 번거롭게 형변환을 하는 것일까?

형변환 하는 이유

그 이유는 size() 메서드가 std::size_t 타입을 반환하기 때문이다.
std::size_t는 unsigned integer이므로 자칫하면 의도치 않은 상황을 유발할 수 있다. 다음의 코드를 보자.

std::vector<int> vec;

if (-1 < vec.size()) {
    std::cout<< "OK";
}

vec의 크기는 0이고, -1보다 크므로 논리적으로는 "OK"가 출력될 것 같다.
실제로는 출력될 수도 있고, 출력되지 않을 수도 있다.

std::size_t 타입의 크기는 unsigned int 타입의 크기보다 작을 수도, 같을 수도, 더 클 수도 있다.
위의 코드에서 -1은 int 타입이고 vec.size()는 std::size_t 타입이므로 둘을 비교하기 위해 더 큰 자료형의 크기에 맞춰서 암묵적인 형변환이 일어난다. 이 경우, 다음과 같은 시나리오를 생각해 볼 수 있다.

  • std::size_t의 크기가 int의 크기보다 작은 경우
    int로 형변환이 일어나고 -1 < 0은 참이므로 "OK"가 출력된다.
  • std::size_t의 크기가 int의 크기와 같은 경우
    크기가 같으면 unsigned로 형변환이 일어나고 -1은 UINT_MAX가 된다.
    UINT_MAX < 0은 거짓이므로 출력되지 않는다.
  • std::size_t의 크기가 int의 크기보다 큰 경우
    std::size_t로 형변환이 일어나고 2번의 흐름과 동일하다.

이제 vec.size()의 결과를 (int)로 형변환하는 이유를 알 수 있을 것이다.
그렇다면 C++20에는 어떻게 쓸 수 있을까?

std::ssize의 등장

위와 같은 문제점 때문에 결국 signed 버전이 생겼다. 이 함수는 std::ptrdiff_t 타입을 반환한다.
앞으로는 벡터 등의 다른 컨테이너나 배열의 사이즈를 구할 때 다음과 같이 구할 수 있다.

std::vector<int> vec;

// do something

for (int i = 0; i < std::ssize(vec); ++i) {
    // ...
}
int arr[SIZE];

for (int i = 0; i < std::ssize(arr); ++i) {
    // ...
}

백준 온라인저지 사이트에서는 C++20으로 std::ssize 사용이 잘 되는 것을 확인하였다.
하지만 그 외 다른 사이트는 이 글을 쓰는 시점에서 아직 최대 C++17을 지원하므로 당장은 사용이 어려워 보인다.

관련 링크

en.cppreference.com/w/cpp/iterator/size

'PL > C++' 카테고리의 다른 글

[C/C++] 다차원 배열에서 인덱스 침범에 대한 고찰  (1) 2021.01.29