C++20의 u8/char8_t 하위 호환성 참사
C++20에서는 UTF-8 문자열 리터럴인 u8"..."의 원소 타입이 기존 const char에서 const char8_t로 바뀌었습니다. 그 결과 C++17에서 정상적으로 컴파일되던 코드가 const char*를 요구하는 기존 API 호출에서 깨질 수 있으며, Google C++ Style Guide도 가능한 경우 u8 접두사를 피하라고 권고합니다.

본문
새로운 C++ 표준은 이전 표준과 하위 호환된다는 인식이 널리 퍼져 있지만, 실제로는 모든 변경이 기존 코드와 호환되는 것은 아닙니다. 글은 C++20의 UTF-8 문자열 리터럴과 char8_t 도입을 최근 사례로 제시합니다. 특히 기존 코드베이스를 C++17에서 C++20으로 전환할 때, 겉보기에는 단순한 표준 버전 변경만으로 컴파일 오류가 발생할 수 있다는 점을 설명합니다.
C++17과 C++20에서 달라진 u8 문자열 리터럴
예제에는 UTF-8 문자열을 입력으로 받는 다음과 같은 함수가 등장합니다.
void DoSomething(const char* utf8Text);
이 함수에 u8"Connie plus some UTF-8 stuff..." 형태의 문자열을 전달하는 코드는 C++17 모드에서 정상적으로 컴파일됩니다. C++11, C++14, C++17에서는 u8"..." 문자열 리터럴의 타입이 const char[N]으로 해석되기 때문입니다. 문자열의 인코딩은 UTF-8이지만, 각 원소의 타입은 일반적인 char 기반 문자열과 같습니다.
그러나 C++20에서는 같은 구문의 타입이 const char8_t[N]으로 바뀝니다. 즉 UTF-8이라는 인코딩 자체가 바뀐 것이 아니라, 그 문자열을 구성하는 원소의 타입이 char에서 별도의 char8_t 타입으로 변경됩니다. 문자열 리터럴이 함수 인자로 전달되거나 변수에 저장되는 과정에서는 배열이 포인터로 변환될 수 있지만, C++20에서는 결과가 const char8_t* 계열이 됩니다. 따라서 const char*를 요구하는 DoSomething에는 그대로 전달할 수 없습니다.
실제로 발생하는 컴파일 오류
원문이 제시한 MSVC(Microsoft Visual C++) 오류는 다음과 같습니다.
‘void DoSomething(const char *)’: cannot convert argument 1 from ‘const char8_t *’ to ‘const char *’
C++17에서 같은 코드를 사용하던 개발자가 언어 표준 옵션만 C++20으로 변경하면, 기존 함수 선언이나 호출부를 수정하지 않은 상태에서 이 오류를 만날 수 있습니다. 예제 자체는 단순하지만, 기존 라이브러리와 대규모 코드베이스에서 const char*를 받는 함수, 문자열 처리 유틸리티, 문자열 관련 인터페이스가 널리 사용되고 있다면 동일한 타입 변경이 여러 위치에서 연쇄적으로 나타날 수 있습니다. 원문은 이러한 변화가 이전 표준에서 문제없이 빌드되던 코드가 새 표준 모드에서 갑자기 깨지는 사례가 될 수 있다고 지적합니다.
호환성 문제를 피하는 방법
원문이 소개하는 한 가지 대응 방법은 가능한 경우 u8 접두사 사용을 피하는 것입니다. Google C++ Style Guide도 이를 권고하면서, u8 접두사가 C++20부터 C++17과 상당히 다른 의미를 갖고 char 배열 대신 char8_t 배열을 생성한다고 설명합니다. 또한 해당 가이드는 이 동작이 C++23에서 다시 바뀔 것이라고 언급합니다. 따라서 UTF-8 문자열을 다루는 기존 코드에서는 단순히 문자열 리터럴 앞에 u8을 붙이는 것이 항상 안전한 선택은 아니며, 사용하는 표준 버전과 호출 대상 API가 기대하는 문자 타입을 함께 확인해야 합니다.
이 사례의 핵심은 C++20이 UTF-8 지원을 추가했다는 데만 있지 않습니다. 기존 코드가 의존하던 문자 타입의 정체성이 바뀌면서, 인코딩은 동일해도 타입 시스템 관점에서는 다른 값이 되었다는 점이 중요합니다. 특히 C++17에서 const char* 기반 인터페이스를 구축한 프로젝트는 C++20 전환 전에 u8 문자열 리터럴의 타입과 관련 API의 매개변수 타입을 점검해야 합니다.
Lobsters 반응
- @fanf — C에서도 비슷한 호환성 문제가 있습니다. C17에서 u8"str" 문자열 리터럴의 원소는 보통 signed인 char 타입입니다. (C17에는 u8'c' 문자 상수가 없습니다.) 따라서 일반 문자열 리터럴과 기능적으로 동일한데, 무엇을 위한 것인지 잘 모르겠습니다. C23에서는 u8'c' 문자 상수의 타입이 char8_t, 즉 unsigned char가 되며, 이름과 표준의 서술이 달리 암시하는 것과 달리 ASCII 문자만 표현할 수 있습니다. u8"str" 문자열 리터럴의 원소도 이제 char8_t 타입이므로, 보통 C17의 u8"str"과는 signedness가 달라집니다. C23의 Annex M은 이를 언급하지…
- @dilawar — C++는 너무 커졌습니다. 하위 호환성에 집착하는 태도 자체는 훌륭하지만, 그 결과는 부풀어 오른 운영체제 창처럼 될 것입니다. 새로운 것은 추가되고, 오래된 것은 제거되지 않습니다. 계속해서 덩치만 커집니다.
원문: Giodi Canio / 번역·요약: Trawling