Ответ 1
Насколько хорошо стандартная библиотека C++ поддерживает юникод?
Жутко.
Быстрый просмотр библиотечных средств, которые могут обеспечить поддержку Unicode, дает мне этот список:
- Библиотека строк
- Библиотека локализации
- Библиотека ввода/вывода
- Библиотека регулярных выражений
Я думаю, что все, кроме первого, оказывают ужасную поддержку. Я вернусь к этому более подробно после короткого обхода других ваших вопросов.
Делает ли
std::string
то, что должен?
Да. Согласно стандарту C++, вот что должен делать std::string
и его братья и сестры:
Шаблон класса
basic_string
описывает объекты, которые могут хранить последовательность, состоящую из различного числа произвольных подобныхbasic_string
объектов с первым элементом последовательности в нулевой позиции.
Ну, std::string
делает это просто отлично. Предоставляет ли это какие-либо специфичные для Unicode функции? Нет.
Должно ли это? Возможно нет. std::string
подходит как последовательность объектов char
. Это полезно; Единственное раздражение в том, что это очень низкоуровневое представление текста, а стандарт C++ не обеспечивает более высокого уровня.
Как мне это использовать?
Используйте его как последовательность объектов char
; притворяться, что это что-то еще, должно закончиться болью.
Где потенциальные проблемы?
Повсюду? Давай посмотрим...
Библиотека строк
Библиотека строк предоставляет нам basic_string
, которая является просто последовательностью того, что стандарт называет "объектами типа char". Я называю их кодовыми единицами. Если вы хотите просмотреть текст на высоком уровне, это не то, что вы ищете. Это вид текста, подходящего для сериализации/десериализации/хранения.
Он также предоставляет некоторые инструменты из библиотеки C, которые можно использовать для преодоления разрыва между узким миром и миром Unicode: c16rtomb
/mbrtoc16
и c32rtomb
/mbrtoc32
.
Библиотека локализации
Библиотека локализации по-прежнему считает, что один из этих "похожих на символы" объектов равен одному "символу". Это, конечно, глупо и делает невозможным правильную работу многих вещей, кроме небольшого подмножества Юникода, такого как ASCII.
Рассмотрим, например, что стандарт называет "удобными интерфейсами" в заголовке <locale>
:
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);
// ...
Как вы ожидаете, что какая-либо из этих функций правильно классифицирует, скажем, U + 1F34C ʙᴀɴᴀɴᴀ, как в u8"🍌"
или u8"\U0001F34C"
? Нет никакого способа, которым это когда-либо будет работать, потому что эти функции принимают только одну единицу кода в качестве входных данных.
Это может работать с соответствующей локалью, если вы использовали только char32_t
: U'\U0001F34C'
- это единица кода в UTF-32.
Однако это по-прежнему означает, что вы получаете только простые преобразования корпусов с помощью toupper
и tolower
, которые, например, не подходят для некоторых немецких языков: верхние регистры "ß" - "SS" ☦, но toupper
может возвращать только одну единицу кода символа.
Далее, wstring_convert
/wbuffer_convert
и стандартные аспекты преобразования кода.
wstring_convert
используется для преобразования между строками в одной заданной кодировке в строки в другой заданной кодировке. В этом преобразовании участвуют два строковых типа, которые в стандарте называются байтовой строкой и широкой строкой. Поскольку эти термины действительно вводят в заблуждение, я предпочитаю использовать "сериализованный" и "десериализованный" соответственно †.
Кодировки для преобразования определяются с помощью codecvt (фасета преобразования кода), передаваемого в качестве аргумента типа шаблона в wstring_convert
.
wbuffer_convert
выполняет аналогичную функцию, но как широкий десериализованный буфер потока, который оборачивает байтовый сериализованный буфер потока. Любой ввод/вывод выполняется через базовый байтовый сериализованный потоковый буфер с преобразованиями в и из кодировок, заданных аргументом codecvt. Запись сериализуется в этот буфер, а затем записывает из него, а чтение читает в буфер, а затем десериализуется из него.
Стандарт предоставляет некоторые шаблоны классов codecvt для использования со следующими средствами: codecvt_utf8
, codecvt_utf16
, codecvt_utf8_utf16
и некоторые специализации codecvt
. Вместе эти стандартные аспекты обеспечивают все следующие преобразования. (Примечание: в следующем списке кодировка слева всегда является сериализованной строкой /streambuf, а кодировка справа всегда десериализованной строкой /streambuf; стандарт допускает преобразования в обоих направлениях).
- UTF-8 UCS-2 с
codecvt_utf8<char16_t>
иcodecvt_utf8<wchar_t>
гдеsizeof(wchar_t) == 2
; - UTF-8 ↔ UTF-32 с
codecvt_utf8<char32_t>
,codecvt<char32_t, char, mbstate_t>
иcodecvt_utf8<wchar_t>
гдеsizeof(wchar_t) == 4
; - UTF-16 ↔ UCS-2 с
codecvt_utf16<char16_t>
иcodecvt_utf16<wchar_t>
гдеsizeof(wchar_t) == 2
; - UTF-16 ↔ UTF-32 с
codecvt_utf16<char32_t>
иcodecvt_utf16<wchar_t>
гдеsizeof(wchar_t) == 4
; - UTF-8 ↔ UTF-16 с
codecvt_utf8_utf16<char16_t>
,codecvt<char16_t, char, mbstate_t>
иcodecvt_utf8_utf16<wchar_t>
гдеsizeof(wchar_t) == 2
; - узкий, широкий с
codecvt<wchar_t, char_t, mbstate_t>
- нет операции с
codecvt<char, char, mbstate_t>
.
Некоторые из них полезны, но здесь есть много неловких вещей.
Прежде всего - святой верховный суррогат! эта схема именования является грязной.
Тогда есть много поддержки UCS-2. UCS-2 - это кодировка из Unicode 1.0, которая была заменена в 1996 году, потому что она поддерживает только базовую многоязычную плоскость. Почему комитет счел желательным сосредоточиться на кодировке, которая была заменена более 20 лет назад, я не знаю ‡. Не то, чтобы поддержка большего количества кодировок была плохой или что-то в этом роде, но UCS-2 появляется здесь слишком часто.
Я бы сказал, что char16_t
явно предназначен для хранения кодовых блоков UTF-16. Тем не менее, это одна часть стандарта, которая считает иначе. codecvt_utf8<char16_t>
имеет ничего общего с UTF-16. Например, wstring_convert<codecvt_utf8<char16_t>>().to_bytes(u"\U0001F34C")
скомпилируется нормально, но безоговорочно завершится ошибкой: вход будет обрабатываться как строка UCS-2 u"\xD83C\xDF4C"
, которая не может быть преобразован в UTF-8, потому что UTF-8 не может кодировать любое значение в диапазоне 0xD800-0xDFFF.
Тем не менее, на фронте UCS-2 нет способа считывания из потока байтов UTF-16 в строку UTF-16 с этими аспектами. Если у вас есть последовательность байтов UTF-16, вы не можете десериализовать ее в строку char16_t
. Это удивительно, потому что это более или менее преобразование личности. Еще более удивительным является тот факт, что существует поддержка десериализации из потока UTF-16 в строку UCS-2 с codecvt_utf16<char16_t>
, которая фактически является преобразованием с потерями.
Тем не менее, поддержка UTF-16 в качестве байтов довольно хороша: она поддерживает обнаружение бесконечности в спецификации или ее явное выделение в коде. Он также поддерживает создание вывода с и без спецификации.
Есть еще несколько интересных возможностей для конвертации. Невозможно десериализовать поток байтов или строку UTF-16 в строку UTF-8, поскольку UTF-8 никогда не поддерживается в качестве десериализованной формы.
И здесь узкий/широкий мир полностью отделен от мира UTF/UCS. Не существует преобразований между узкими/широкими кодировками старого стиля и любыми кодировками Unicode.
Библиотека ввода/вывода
Библиотеку ввода/вывода можно использовать для чтения и записи текста в кодировках Unicode с использованием описанных выше средств wstring_convert
и wbuffer_convert
. Я не думаю, что есть еще что-то, что могло бы быть поддержано этой частью стандартной библиотеки.
Библиотека регулярных выражений
Я уже объяснил проблемы с регулярными выражениями C++ и Unicode в переполнении стека. Я не буду повторять здесь все эти пункты, а просто скажу, что регулярные выражения C++ не имеют поддержки Unicode 1-го уровня, что является минимальным условием для их использования без использования UTF-32 везде.
Это?
Да, это так. Это существующий функционал. Существует множество функций Unicode, которые нигде не встречаются, такие как алгоритмы нормализации или сегментации текста.
U + 1F4A9. Есть ли способ получить лучшую поддержку Unicode в C++?
Обычные подозреваемые: ICU и Boost.Locale.
† Неудивительно, что строка байтов - это строка байтов, т. char
объектов. Однако, в отличие от литерала с широкой строкой, который всегда является массивом объектов wchar_t
, "широкая строка" в этом контексте не обязательно является строкой объектов wchar_t
. Фактически, стандарт никогда не определяет явно, что означает "широкая строка", поэтому нам остается только догадываться о значении использования. Поскольку стандартная терминология небрежная и запутанная, я использую свою собственную во имя ясности.
Кодировки, подобные UTF-16, могут храниться в виде последовательностей char16_t
, которые затем не имеют порядка байтов; или они могут быть сохранены как последовательности байтов, которые имеют порядковый номер (каждая последовательная пара байтов может представлять различное значение char16_t
зависимости от порядкового номера). Стандарт поддерживает обе эти формы. Последовательность char16_t
более полезна для внутренних манипуляций в программе. Последовательность байтов - это способ обмена такими строками с внешним миром. Термины, которые я буду использовать вместо "байт" и "широкий", таким образом, "сериализуются" и "десериализуются".
‡ Если вы собираетесь сказать "но Windows!" держи свой 🐎🐎. Все версии Windows начиная с Windows 2000 используют UTF-16.
☦ Да, я знаю о grosses Eszett (ẞ), но даже если бы вам пришлось поменять все немецкие локации на ночь, чтобы прописные буквы были прописными, то есть еще множество других случаев, когда это не сработало. Попробуйте прописные буквы U + FB00 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟɪɢᴀᴛᴜʀᴇ ғғ. Там нет ʟᴀᴛɪɴ ᴄᴀᴘɪᴛᴀʟ ʟɪɢᴀᴛᴜʀᴇ ғғ; это только прописные буквы до двух Fs. Или U + 01F0 ʟᴀᴛɪɴ sᴍᴀʟʟ ʟᴇᴛᴛᴇʀ ᴊ ᴡɪᴛʜ ᴄᴀʀᴏɴ; нет предварительно составленного капитала; это только заглавные буквы к заглавной букве J и объединяющему caron.