Dans quelle mesure la bibliothèque standard C ++ prend-elle en charge l'Unicode?
Terriblement.
Une analyse rapide des fonctionnalités de la bibliothèque susceptibles de fournir un support Unicode me donne cette liste:
- Bibliothèque de chaînes
- Bibliothèque de localisation
- Bibliothèque d'entrée / sortie
- Bibliothèque d'expressions régulières
Je pense que tous, sauf le premier, fournissent un soutien terrible. J'y reviendrai plus en détail après un petit détour par vos autres questions.
Fait std::string
ce qu'il doit faire?
Oui. Selon la norme C ++, voici ce que std::string
ses frères et sœurs devraient faire:
Le modèle de classe basic_string
décrit des objets qui peuvent stocker une séquence constituée d'un nombre variable d'objets arbitraires de type char avec le premier élément de la séquence à la position zéro.
Eh bien, std::string
ça va très bien. Cela fournit-il des fonctionnalités spécifiques à Unicode? Non.
Devrait-il? Probablement pas. std::string
est bien comme une séquence d' char
objets. C'est utile; le seul inconvénient est qu'il s'agit d'une vue de texte de très bas niveau et que le C ++ standard n'en fournit pas une de plus haut niveau.
Comment l'utiliser?
Utilisez-le comme une séquence d' char
objets; prétendre que c'est quelque chose d'autre ne peut que se terminer par la douleur.
Où sont les problèmes potentiels?
Partout? Voyons voir...
Bibliothèque de chaînes
La bibliothèque de chaînes nous fournit basic_string
, qui est simplement une séquence de ce que le standard appelle des "objets de type char". Je les appelle des unités de code. Si vous voulez une vue de haut niveau du texte, ce n'est pas ce que vous recherchez. Il s'agit d'une vue de texte adaptée à la sérialisation / désérialisation / stockage.
Il fournit également quelques outils de la bibliothèque C qui peuvent être utilisés pour combler le fossé entre le monde étroit et le monde Unicode: c16rtomb
/ mbrtoc16
et c32rtomb
/ mbrtoc32
.
Bibliothèque de localisation
La bibliothèque de localisation pense toujours qu'un de ces "objets de type char" équivaut à un "caractère". Ceci est bien sûr ridicule et rend impossible le bon fonctionnement de beaucoup de choses au-delà d'un petit sous-ensemble d'Unicode comme ASCII.
Considérez, par exemple, ce que la norme appelle «interfaces de commodité» dans l'en- <locale>
tête:
template <class charT> bool isspace (charT c, const locale& loc);
template <class charT> bool isprint (charT c, const locale& loc);
template <class charT> bool iscntrl (charT c, const locale& loc);
// ...
template <class charT> charT toupper(charT c, const locale& loc);
template <class charT> charT tolower(charT c, const locale& loc);
// ...
Comment vous attendez-vous à ce que l'une de ces fonctions catégorise correctement, par exemple, U + 1F34C ʙᴀɴᴀɴᴀ, comme dans u8"🍌"
ou u8"\U0001F34C"
? Cela ne fonctionnera jamais, car ces fonctions ne prennent qu'une seule unité de code en entrée.
Cela pourrait fonctionner avec une locale appropriée si vous n'utilisiez char32_t
que: U'\U0001F34C'
est une unité de code unique en UTF-32.
Cependant, cela signifie toujours que vous n'obtenez que les transformations de casse simples avec toupper
et tolower
, qui, par exemple, ne sont pas assez bonnes pour certains paramètres régionaux allemands: "ß" majuscules en "SS" ☦ mais toupper
ne peut renvoyer qu'une unité de code de caractère .
Ensuite, wstring_convert
/ wbuffer_convert
et les facettes de conversion de code standard.
wstring_convert
est utilisé pour convertir des chaînes d'un codage donné en chaînes d'un autre codage donné. Il existe deux types de chaîne impliqués dans cette transformation, que la norme appelle une chaîne d'octets et une chaîne large. Puisque ces termes sont vraiment trompeurs, je préfère utiliser respectivement "sérialisé" et "désérialisé" †.
Les codages à convertir sont décidés par un codecvt (une facette de conversion de code) passé en tant qu'argument de type de modèle à wstring_convert
.
wbuffer_convert
exécute une fonction similaire, mais en tant que tampon de flux désérialisé large qui enveloppe un tampon de flux sérialisé d' octets . Toutes les E / S sont effectuées via le tampon de flux sérialisé d' octets sous-jacent avec des conversions vers et à partir des encodages donnés par l'argument codecvt. L'écriture sérialise dans ce tampon, puis écrit à partir de celui-ci, et la lecture lit dans le tampon, puis désérialise à partir de celui-ci.
La norme fournit des modèles de classe codecvt pour une utilisation avec ces installations: codecvt_utf8
, codecvt_utf16
, codecvt_utf8_utf16
, et certaines codecvt
spécialisations. Ensemble, ces facettes standard fournissent toutes les conversions suivantes. (Remarque: dans la liste suivante, l'encodage à gauche est toujours la chaîne sérialisée / streambuf, et l'encodage à droite est toujours la chaîne désérialisée / streambuf; le standard autorise les conversions dans les deux sens).
- UTF-8 ↔ UCS-2 avec
codecvt_utf8<char16_t>
et codecvt_utf8<wchar_t>
où sizeof(wchar_t) == 2
;
- UTF-32 avec UTF-8 ↔
codecvt_utf8<char32_t>
, codecvt<char32_t, char, mbstate_t>
et codecvt_utf8<wchar_t>
où sizeof(wchar_t) == 4
;
- UTF-16 ↔ UCS-2 avec
codecvt_utf16<char16_t>
et codecvt_utf16<wchar_t>
où sizeof(wchar_t) == 2
;
- UTF-16 ↔ UTF-32 avec
codecvt_utf16<char32_t>
et codecvt_utf16<wchar_t>
où sizeof(wchar_t) == 4
;
- UTF-16 avec UTF-8 ↔
codecvt_utf8_utf16<char16_t>
, codecvt<char16_t, char, mbstate_t>
et codecvt_utf8_utf16<wchar_t>
où sizeof(wchar_t) == 2
;
- étroit ↔ large avec
codecvt<wchar_t, char_t, mbstate_t>
- no-op avec
codecvt<char, char, mbstate_t>
.
Plusieurs d'entre eux sont utiles, mais il y a beaucoup de choses gênantes ici.
Tout d'abord - saint substitut élevé! ce schéma de dénomination est compliqué.
Ensuite, il y a beaucoup de support UCS-2. UCS-2 est un encodage d'Unicode 1.0 qui a été remplacé en 1996 car il ne prend en charge que le plan multilingue de base. Je ne sais pas pourquoi le comité a jugé souhaitable de se concentrer sur un codage qui a été remplacé il y a plus de 20 ans. Ce n'est pas comme si le support pour plus d'encodages était mauvais ou quoi que ce soit, mais UCS-2 apparaît trop souvent ici.
Je dirais que char16_t
c'est évidemment destiné au stockage des unités de code UTF-16. Cependant, c'est une partie de la norme qui pense autrement. codecvt_utf8<char16_t>
n'a rien à voir avec UTF-16. Par exemple, wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
compilera correctement, mais échouera inconditionnellement: l'entrée sera traitée comme la chaîne UCS-2 u"\xD83C\xDF4C"
, qui ne peut pas être convertie en UTF-8 car UTF-8 ne peut coder aucune valeur dans la plage 0xD800-0xDFFF.
Toujours sur le front UCS-2, il n'y a aucun moyen de lire à partir d'un flux d'octets UTF-16 dans une chaîne UTF-16 avec ces facettes. Si vous avez une séquence d'octets UTF-16, vous ne pouvez pas la désérialiser en une chaîne de char16_t
. C'est surprenant, car il s'agit plus ou moins d'une conversion d'identité. Encore plus surprenant, cependant, est le fait qu'il existe un support pour la désérialisation d'un flux UTF-16 dans une chaîne UCS-2 avec codecvt_utf16<char16_t>
, qui est en fait une conversion avec perte.
Le support UTF-16-as-bytes est cependant assez bon: il prend en charge la détection de l'extrémité d'une nomenclature, ou sa sélection explicite dans le code. Il prend également en charge la production de sorties avec et sans nomenclature.
Il y a des possibilités de conversion plus intéressantes absentes. Il n'y a aucun moyen de désérialiser d'un flux ou d'une chaîne d'octets UTF-16 en une chaîne UTF-8, car UTF-8 n'est jamais pris en charge en tant que forme désérialisée.
Et ici, le monde étroit / large est complètement séparé du monde UTF / UCS. Il n'y a pas de conversion entre les encodages étroit / large à l'ancienne et les encodages Unicode.
Bibliothèque d'entrée / sortie
La bibliothèque d'E / S peut être utilisée pour lire et écrire du texte dans des encodages Unicode à l'aide des fonctions wstring_convert
et wbuffer_convert
décrites ci-dessus. Je ne pense pas qu'il y ait grand-chose d'autre qui devrait être pris en charge par cette partie de la bibliothèque standard.
Bibliothèque d'expressions régulières
J'ai déjà exposé des problèmes avec les expressions régulières C ++ et Unicode sur Stack Overflow. Je ne répéterai pas tous ces points ici, mais simplement déclarer que les expressions rationnelles C ++ n'ont pas de support Unicode de niveau 1, ce qui est le strict minimum pour les rendre utilisables sans recourir à UTF-32 partout.
C'est tout?
Oui c'est ça. C'est la fonctionnalité existante. Il existe de nombreuses fonctionnalités Unicode qui sont introuvables, comme les algorithmes de normalisation ou de segmentation de texte.
U + 1F4A9 . Existe-t-il un moyen d'obtenir un meilleur support Unicode en C ++?
Les suspects habituels: ICU et Boost.Locale .
† Une chaîne d'octets est, sans surprise, une chaîne d'octets, c'est-à-dire des char
objets. Cependant, contrairement à une chaîne littérale large , qui est toujours un tableau d' wchar_t
objets, une "chaîne large" dans ce contexte n'est pas nécessairement une chaîne d' wchar_t
objets. En fait, la norme ne définit jamais explicitement ce que signifie une "chaîne large", donc il nous reste à deviner la signification de l'utilisation. Étant donné que la terminologie standard est bâclée et déroutante, j'utilise la mienne, au nom de la clarté.
Les codages comme UTF-16 peuvent être stockés sous forme de séquences de char16_t
, qui n'ont alors aucune endianité; ou ils peuvent être stockés sous forme de séquences d'octets, qui ont une endianité (chaque paire consécutive d'octets peut représenter une char16_t
valeur différente selon l'endianness). La norme prend en charge ces deux formulaires. Une séquence de char16_t
est plus utile pour la manipulation interne dans le programme. Une séquence d'octets est le moyen d'échanger de telles chaînes avec le monde extérieur. Les termes que j'utiliserai à la place de "octet" et "large" sont donc "sérialisés" et "désérialisés".
‡ Si vous êtes sur le point de dire "mais Windows!" tenez votre 🐎🐎 . Toutes les versions de Windows depuis Windows 2000 utilisent UTF-16.
☦ Oui, je connais les großes Eszett (ẞ), mais même si vous deviez changer tous les paramètres régionaux allemands du jour au lendemain pour avoir ß majuscule en ẞ, il y a encore beaucoup d'autres cas où cela échouerait. Essayez de mettre U + FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ en majuscules. Il n'y a pas de ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ; c'est juste des majuscules à deux F. Ou U + 01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ; il n'y a pas de capital pré-composé; il met juste des majuscules à un J majuscule et un caron combinant.