wchar_t, char8_t, char16_t, char32_t în C++

Tipuri de date C++98 (char16_t, char32_t: C++11; char8_t: C++20)

Tipuri de caractere pentru Unicode: char8_t (UTF-8), char16_t (UTF-16), char32_t (UTF-32) și wchar_t, caracterul „lat” dependent de sistem.

Sintaxă
wchar_t  w = L'A';     // caracter „lat”, mărime dependentă de sistem
char8_t  a = u8'A';    // unitate UTF-8 (C++20)
char16_t b = u'ș';     // unitate UTF-16
char32_t c = U'ș';     // un caracter Unicode întreg (UTF-32)

Ce face wchar_t, char8_t, char16_t, char32_t

Un char are doar 8 biți, prea puțin pentru toate literele lumii. De aceea C++ are tipuri de caractere pentru codificările Unicode, fiecare cu prefixul lui de literal: u8"..." pentru char8_t (UTF-8), u"..." pentru char16_t (UTF-16) și U"..." pentru char32_t (UTF-32). Șirurile corespunzătoare sunt std::u8string, std::u16string și std::u32string.

În UTF-32 fiecare caracter ocupă exact un char32_t, deci lungimea șirului este chiar numărul de litere. În UTF-8 și UTF-16 un caracter poate ocupa mai multe unități.

wchar_t (prefixul L) este mai vechi și are mărimi diferite: 2 octeți pe Windows (unde API-ul sistemului îl folosește peste tot) și 4 octeți pe Linux și macOS. În cod portabil, preferă char cu UTF-8 sau tipurile char8_t/char16_t/char32_t.

Exemple

Exemplul 1: Același cuvânt în patru codificări

„Iași” are 4 litere. În UTF-8, „ș” ocupă 2 unități, deci 5 în total; în UTF-16 și UTF-32 fiecare literă ocupă o unitate. Mărimea lui wchar_t este cea de pe macOS.

#include <print>
#include <string>

int main() {
    std::u8string  s8  = u8"Iași";
    std::u16string s16 = u"Iași";
    std::u32string s32 = U"Iași";
    std::wstring   sw  = L"Iași";
    std::println("UTF-8:   {} unități de {} octet", s8.size(), sizeof(char8_t));
    std::println("UTF-16:  {} unități de {} octeți", s16.size(), sizeof(char16_t));
    std::println("UTF-32:  {} unități de {} octeți", s32.size(), sizeof(char32_t));
    std::println("wchar_t: {} unități de {} octeți", sw.size(), sizeof(wchar_t));
}

Rezultat

UTF-8:   5 unități de 1 octet
UTF-16:  4 unități de 2 octeți
UTF-32:  4 unități de 4 octeți
wchar_t: 4 unități de 4 octeți

Exemplul 2: Codul Unicode al unei litere

Un char32_t ține codul Unicode complet. „ș” cu virgulă (cel corect în română) are codul U+0219, iar varianta veche, cu sedilă, are U+015F: pentru calculator sunt litere diferite.

#include <print>

int main() {
    char32_t s_virgula = U'ș';
    char32_t t_virgula = U'ț';
    std::println("ș are codul U+{:04X}", static_cast<unsigned>(s_virgula));
    std::println("ț are codul U+{:04X}", static_cast<unsigned>(t_virgula));
}

Rezultat

ș are codul U+0219
ț are codul U+021B

Greșeli frecvente

  • Încerci să afișezi un std::u8string cu std::cout sau std::println. Din C++20, char8_t este un tip separat și nu se afișează direct; pentru afișare folosește std::string obișnuit (care în practică conține tot UTF-8).
  • Presupui că wchar_t are aceeași mărime peste tot. Un fișier scris cu wchar_t pe Windows (2 octeți) nu se citește corect pe Linux (4 octeți).

Comenzi înrudite

  • char: Tipul pentru un singur caracter (un octet); în spate este un număr mic, codul caracterului.
  • sizeof: Dă numărul de octeți ocupați în memorie de un tip sau de o variabilă; se calculează la compilare.
  • signed, unsigned, short, long: Modificatori care schimbă mărimea (short, long) sau semnul (signed, unsigned) tipurilor întregi.
  • std::string: Șir de caractere care își gestionează singur memoria; atenție, lucrează cu octeți, nu cu litere.