Проблема с фасетом std::codecvt_utf8
Вот фрагмент кода, который использует std::codecvt_utf8<>
грань для преобразования из wchar_t
до UTF-8. В Visual Studio 2012 мои ожидания не оправдались (см. Условие в конце кода). Мои ожидания неверны? Зачем? Или это проблема библиотеки Visual Studio 2012?
#include <locale>
#include <codecvt>
#include <cstdlib>
int main ()
{
std::mbstate_t state = std::mbstate_t ();
std::locale loc (std::locale (), new std::codecvt_utf8<wchar_t>);
typedef std::codecvt<wchar_t, char, std::mbstate_t> codecvt_type;
codecvt_type const & cvt = std::use_facet<codecvt_type> (loc);
wchar_t ch = L'\u5FC3';
wchar_t const * from_first = &ch;
wchar_t const * from_mid = &ch;
wchar_t const * from_end = from_first + 1;
char out_buf[1];
char * out_first = out_buf;
char * out_mid = out_buf;
char * out_end = out_buf + 1;
std::codecvt_base::result cvt_res
= cvt.out (state, from_first, from_end, from_mid,
out_first, out_end, out_mid);
// This is what I expect:
if (cvt_res == std::codecvt_base::partial
&& out_mid == out_end
&& state != 0)
;
else
abort ();
}
Ожидание здесь заключается в том, что out()
Функция выводит один байт преобразования UTF-8 за раз, но середина if
Условие выше ложно с Visual Studio 2012.
ОБНОВИТЬ
Что терпит неудачу, так это out_mid == out_end
а также state != 0
условия. По сути, я ожидаю, что по крайней мере один байт будет произведен и необходимое состояние, чтобы следующий байт последовательности UTF-8 мог быть воспроизведен, был сохранен в state
переменная.
2 ответа
Стандартное описание partial
код возврата codecvt::do_out
говорит именно это:
в таблице 83:
partial
не все исходные символы преобразованы
В 22.4.1.4.2[locale.codecvt.virtuals]/5:
Возвращает: значение перечисления, как показано в таблице 83. Возвращаемое значение
partial
, если(from_next==from_end)
, указывает, что или последовательность назначения не поглотила все доступные элементы назначения, или что дополнительные элементы источника необходимы, прежде чем может быть создан другой элемент назначения.
В вашем случае не все (нулевые) исходные символы были конвертированы, что технически ничего не говорит о содержании выходной последовательности (предложение 'if' в предложении не вводится), но, вообще говоря, "целевая последовательность не поглощена все доступные элементы назначения "здесь говорит о допустимых многобайтовых символах. Они являются элементами многобайтовой последовательности символов, созданной codecvt_utf8
,
Было бы неплохо иметь более четкую стандартную формулировку, но вот два косвенных доказательства:
Один: функция преобразования старого в многобайтовый C std::wcsrtombs
(чьи специфичные для локали варианты обычно вызываются существующими реализациями codecvt::do_out
для системных языков) определяется следующим образом:
Преобразование прекращается [...], когда следующий многобайтовый символ превысит лимит общих байтов len, которые будут сохранены в массиве, указанном dst.
И во-вторых, посмотрите на существующие реализации codecvt_utf8
: вы уже исследовали Microsoft, и вот что в libC++: codecvt_utf8::do_out
здесь звонки ucs2_to_utf8
на Windows и ucs4_to_utf8
в других системах ucs2_to_utf8 делает следующее (мои комментарии):
else if (wc < 0x0800)
{
// not relevant
}
else // if (wc <= 0xFFFF)
{
if (to_end-to_nxt < 3)
return codecvt_base::partial; // <- look here
*to_nxt++ = static_cast<uint8_t>(0xE0 | (wc >> 12));
*to_nxt++ = static_cast<uint8_t>(0x80 | ((wc & 0x0FC0) >> 6));
*to_nxt++ = static_cast<uint8_t>(0x80 | (wc & 0x003F));
}
в выходную последовательность ничего не записывается, если она не может вместить многобайтовый символ, полученный в результате использования одного входного широкого символа.
Хотя нет прямой ссылки на это, я думаю, что это наиболее логичное поведение std::codecvt::out
, Рассмотрим следующий сценарий:
- Ты используешь
std::codecvt::out
так же, как вы - не переводить никаких символов (возможно, не зная) в вашout_buf
, - Теперь вы хотите перевести еще одну строку в ваш
out_buf
(снова используяstd::codecvt::out
) такой, что он добавляет контент, который уже находится внутри - Для этого вы решили использовать свой
buf_mid
как вы знаете, он указывает сразу после вашей строки, которую вы перевели на первом шаге. - Сейчас если
std::codecvt::out
работал в соответствии с вашими ожиданиями (buf_mid
указывая на символ после первого) затем первый символ вашегоout_buf
никогда не будет написано, что будет не совсем то, что вы хотите / ожидаете в этом случае.
По сути, extern_type*& to_next
(последний параметр std::codecvt::out
) здесь для вас как справка о том, где вы оставили - так что вы знаете, где продолжить - что в вашем случае действительно та же позиция, что и с того, с чего вы начали (extern_type* to
) параметр.