Dans quelle mesure Unicode est-il pris en charge dans C ++ 11?

J’ai lu et entendu que C ++ 11 supporte Unicode. Quelques questions à ce sujet:

  • Dans quelle mesure la bibliothèque standard C ++ prend-elle en charge Unicode?
  • Est-ce que std::ssortingng fait ce qu’il faut?
  • Comment l’utiliser?
  • Où sont les problèmes potentiels?

Dans quelle mesure la bibliothèque standard C ++ prend-elle en charge unicode?

Terriblement.

Une parsing rapide des fonctions de bibliothèque pouvant fournir un support Unicode me donne cette liste:

  • Bibliothèque de cordes
  • Bibliothèque de localisation
  • Bibliothèque d’entrée / sortie
  • Bibliothèque d’expressions régulières

Je pense que tout sauf le premier apporte un soutien terrible. J’y reviendrai plus en détail après un rapide détour par vos autres questions.

Est-ce que std::ssortingng fait ce qu’il faut?

Oui. Selon le standard C ++, c’est ce que std::ssortingng et ses frères devraient faire:

Le modèle de classe basic_ssortingng décrit des objects pouvant stocker une séquence composée d’un nombre variable d’objects de type caractère arbitraires avec le premier élément de la séquence à la position zéro.

Eh bien, std::ssortingng fait très bien. Est-ce que cela fournit une fonctionnalité spécifique à Unicode? Non.

Devrait-il? Probablement pas. std::ssortingng convient bien à une séquence d’objects char . C’est utile Le seul inconvénient est que c’est une vue très bas niveau du texte et que le standard C ++ ne fournit pas un niveau supérieur.

Comment l’utiliser?

Utilisez-le comme une séquence d’objects char ; prétendre que c’est autre chose ne peut que causer de la douleur.

Où sont les problèmes potentiels?

Partout? Voyons voir…

Bibliothèque de cordes

La bibliothèque de chaînes de caractères nous fournit la basic_ssortingng , qui est simplement une séquence de ce que la norme appelle des “objects 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. Ceci est une vue de texte adaptée à la sérialisation / désérialisation / stockage.

Il fournit également des outils de la bibliothèque C qui peuvent être utilisés pour combler le fossé entre le monde restreint et le monde Unicode: c16rtomb / mbrtoc16 et c32rtomb / mbrtoc32 .

Bibliothèque de localisation

La bibliothèque de localisation croit toujours que l’un de ces “objects de type char” est égal à un “caractère”. Cela est bien sûr idiot et rend impossible le bon fonctionnement de beaucoup de choses au-delà d’un petit sous-ensemble d’Unicode comme ASCII.

Considérons, par exemple, ce que la norme appelle “interfaces de commodité” dans l’en-tête :

 template  bool isspace (charT c, const locale& loc); template  bool isprint (charT c, const locale& loc); template  bool iscntrl (charT c, const locale& loc); // ... template  charT toupper(charT c, const locale& loc); template  charT tolower(charT c, const locale& loc); // ... 

Comment voulez-vous que l’une de ces fonctions classe 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 char32_t uniquement char32_t : U'\U0001F34C' est une unité de code unique dans UTF-32.

Cependant, cela signifie que vous n’obtenez que des transformations simples avec toupper et tolower , qui, par exemple, ne sont pas suffisantes pour certaines langues allemandes: “ß” majuscule à “SS” ☦ mais toupper ne peut que renvoyer une unité de code.

Ensuite, wssortingng_convert / wbuffer_convert et les facettes de conversion de code standard.

wssortingng_convert est utilisé pour convertir entre les chaînes d’un encodage donné en chaînes dans un autre encodage donné. Il existe deux types de chaîne impliqués dans cette transformation, que le standard appelle une chaîne d’octets et une chaîne large. Comme ces termes sont vraiment trompeurs, je préfère utiliser respectivement “sérialisé” et “désérialisé” †.

Les codages à convertir entre eux sont déterminés par un codecvt (une facette de conversion de code) transmis en tant qu’argument de type modèle à wssortingng_convert .

wbuffer_convert exécute une fonction similaire mais sous la forme d’un tampon de stream désérialisé large qui encapsule un tampon de stream sérialisé en octets . Toute E / S est effectuée via le tampon de stream sérialisé octet sous-jacent avec les conversions vers et depuis les codages donnés par l’argument codecvt. L’écriture se sérialise dans ce tampon, puis écrit à partir de ce tampon, et la lecture se lit dans le tampon puis se désérialise.

La norme fournit des modèles de classe codecvt à utiliser avec ces fonctionnalités: codecvt_utf8 , codecvt_utf16 , codecvt_utf8_utf16 et certaines spécialisations de codecvt . Ensemble, ces facettes standard fournissent toutes les conversions suivantes. (Remarque: dans la liste suivante, l’encodage sur la gauche est toujours la chaîne sérialisée / streambuf et l’encodage sur la droite est toujours la chaîne / streambuf désérialisée; la norme autorise les conversions dans les deux sens).

  • UTF-8, UCS-2 avec codecvt_utf8 et codecvt_utf8 where sizeof(wchar_t) == 2 ;
  • UTF-8, UTF-32 avec codecvt_utf8 , codecvt et codecvt_utf8 where sizeof(wchar_t) == 4 ;
  • UTF-16, UCS-2 avec codecvt_utf16 et codecvt_utf16 where sizeof(wchar_t) == 2 ;
  • UTF-16, UTF-32 avec codecvt_utf16 et codecvt_utf16sizeof(wchar_t) == 4 ;
  • UTF-8, UTF-16 avec codecvt_utf8_utf16 , codecvt et codecvt_utf8_utf16 sizeof(wchar_t) == 2 ;
  • étroit ↔ large avec codecvt
  • no-op avec codecvt .

Plusieurs d’entre eux sont utiles, mais il y a beaucoup de choses embarrassantes ici.

Tout d’abord – saint substitut sacré! ce schéma de nommage est en désordre.

Ensuite, il y a beaucoup de support UCS-2. UCS-2 est un encodage Unicode 1.0 qui a été remplacé en 1996 car il ne prend en charge que le plan multilingue de base. Pourquoi le comité a-t-il jugé souhaitable de se concentrer sur un encodage remplacé il y a plus de 20 ans, je ne sais pas ‡. Ce n’est pas comme si le support pour plus d’encodages était mauvais, mais UCS-2 apparaît trop souvent ici.

Je dirais que char16_t est évidemment destiné à stocker des unités de code UTF-16. Cependant, c’est une partie de la norme qui pense autrement. codecvt_utf8 n’a rien à voir avec UTF-16. Par exemple, wssortingng_convert>().to_bytes(u"\U0001F34C") comstackra bien, mais échouera sans condition: l’entrée sera traitée comme la chaîne UCS-2 u"\xD83C\xDF4C" ne peut pas être converti en UTF-8 car UTF-8 ne peut encoder aucune valeur dans la plage 0xD800-0xDFFF.

Toujours sur le front UCS-2, il est impossible de lire un stream 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 caractères char16_t . C’est surprenant, car il s’agit plus ou moins d’une conversion d’identité. Encore plus surprenant est le fait qu’il existe un support pour la désérialisation d’un stream UTF-16 en une chaîne UCS-2 avec codecvt_utf16 , qui est en réalité une conversion avec perte.

Le support de UTF-16-as-by-octets est plutôt bon: il prend en charge la détection de fichiers à partir d’une nomenclature, ou la sélection explicitement dans le code. Il prend également en charge la production de sortie avec et sans nomenclature.

Il existe des possibilités de conversion plus intéressantes. Il n’y a aucun moyen de désérialiser un stream ou une chaîne d’octets UTF-16 dans 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 conversions entre les codages étroit / large anciens et les codages Unicode.

Bibliothèque d’entrée / sortie

La bibliothèque d’E / S peut être utilisée pour lire et écrire du texte dans les codages Unicode à l’aide des fonctions wssortingng_convert et wbuffer_convert décrites ci-dessus. Je ne pense pas qu’il y ait beaucoup d’autre chose qui devrait être supporté 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 regex C ++ et Unicode sur Stack Overflow auparavant. Je ne répéterai pas tous ces points ici, mais je vous signale simplement que les expressions rationnelles C ++ ne prennent pas en charge le niveau 1 Unicode, ce qui est le ssortingct minimum pour les rendre utilisables sans avoir à utiliser UTF-32 partout.

C’est tout?

Oui c’est ça. C’est la fonctionnalité existante. Il y a beaucoup de fonctionnalités Unicode que l’on ne voit nulle part comme les algorithmes de normalisation ou de segmentation de texte.

U + 1F4A9 . Est-il possible 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 objects char . Cependant, contrairement à un littéral de chaîne large , qui est toujours un tableau d’objects wchar_t , une “chaîne large” dans ce contexte n’est pas nécessairement une chaîne d’objects wchar_t . En fait, la norme ne définit jamais explicitement ce que signifie une “chaîne large”, nous devons donc deviner le sens de l’utilisation. Étant donné que la terminologie standard est bâclée et déroutante, j’utilise le mien, au nom de la clarté.

Les codages comme UTF-16 peuvent être stockés sous la forme de séquences de char16_t , qui n’ont alors aucun caractère endian; ou ils peuvent être stockés en tant que séquences d’octets, qui ont un caractère endian (chaque paire d’octets consécutive peut représenter une valeur char16_t différente en fonction de l’endianness). La norme prend en charge ces deux formes. 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 ainsi “sérialisés” et “désérialisés”.

‡ Si vous êtes sur le sharepoint dire “mais Windows!” tenez votre 🐎🐎 . Toutes les versions de Windows depuis Windows 2000 utilisent UTF-16.

☦ Oui, je connais le großes Eszett (ẞ), mais même si vous deviez changer toutes les locales allemandes du jour au lendemain pour que ß soit majuscule, il y a encore beaucoup d’autres cas où cela échouerait. Essayez de mettre en majuscule U + FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ. Il n’y a pas de ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ; il ne fait que deux majuscules. Ou U + 01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ; il n’y a pas de capital précomposé; il ne fait que majuscule à un J majuscule et à un caron combinant.

Unicode n’est pas pris en charge par Standard Library (pour toute signification raisonnable de supporté).

std::ssortingng n’est pas meilleur que std::vector : il est complètement inconscient d’Unicode (ou de toute autre représentation / encodage) et traite simplement son contenu comme un blob d’octets.

Si vous avez seulement besoin de stocker et de caténer des blobs, cela fonctionne plutôt bien; mais dès que vous souhaitez une fonctionnalité Unicode (nombre de points de code, nombre de graphèmes, …), vous n’avez pas de chance.

La seule bibliothèque complète que je connaisse est ICU. L’interface C ++ a été dérivée de celle de Java, elle est donc loin d’être idiomatique.

Vous pouvez stocker UTF-8 en toute sécurité dans un std::ssortingng (ou dans un char[] ou un char* , d’ailleurs), car un NUL Unicode (U + 0000) est un octet nul dans UTF-8 et que c’est la seule façon dont un octet nul peut apparaître dans UTF-8. Par conséquent, vos chaînes UTF-8 seront correctement terminées conformément à toutes les fonctions de chaîne C et C ++, et vous pouvez les déplacer avec C ++ iostreams (y compris std::cout et std::cerr , tant que votre environnement local est UTF). -8).

Ce que vous ne pouvez pas faire avec std::ssortingng pour UTF-8 est d’obtenir la longueur en points de code. std::ssortingng::size() vous indiquera la longueur de la chaîne en octets , qui est seulement égale au nombre de points de code lorsque vous vous trouvez dans le sous-ensemble ASCII de UTF-8.

Si vous devez utiliser des chaînes UTF-8 au niveau du sharepoint code – pas simplement les stocker et les imprimer – ou si vous avez affaire à UTF-16, qui est susceptible de contenir de nombreux octets null internes, vous avez besoin pour rechercher les types de chaînes de caractères larges.

C ++ 11 a quelques nouveaux types de chaînes littérales pour Unicode.

Malheureusement, le support dans la bibliothèque standard pour les codages non uniformes (comme UTF-8) est toujours mauvais. Par exemple, il n’y a pas de moyen pratique d’obtenir la longueur (en points de code) d’une chaîne UTF-8.

Cependant, il existe une bibliothèque très utile appelée tiny-utf8 , qui est fondamentalement un remplacement de std::ssortingng / std::wssortingng . Il vise à combler le vide de la classe de conteneur utf8-ssortingng encore manquante.

Cela pourrait être la manière la plus confortable de traiter les chaînes utf8 (c’est-à-dire sans normalisation Unicode et autres choses similaires). Vous travaillez confortablement sur des points de code , tandis que votre chaîne rest codée en caractères codés en longueur.