проблема с фасетом 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.


person wilx    schedule 14.10.2013    source источник


Ответы (2)


Стандартное описание кода возврата partial для codecvt::do_out говорит именно об этом:

в таблице 83:

partial не все исходные символы преобразованы

В 22.4.1.4.2[locale.codecvt.virtuals]/5:

Возвраты: значение перечисления, как показано в таблице 83. Возвращаемое значение partial, если (from_next==from_end), указывает, что либо целевая последовательность не поглотила все доступные целевые элементы, либо необходимы дополнительные исходные элементы. до того, как может быть создан другой целевой элемент.

В вашем случае не все (нулевые) исходные символы были преобразованы, что технически ничего не говорит о содержании выходной последовательности (предложение «если» в предложении не введено), но, говоря в целом, «целевая последовательность не впитала все доступные элементы назначения» здесь говорится о допустимых многобайтовых символах. Это элементы многобайтовой последовательности символов, создаваемой codecvt_utf8.

Было бы неплохо иметь более четкую стандартную формулировку, но вот два косвенных доказательства:

Во-первых, функция преобразования std::wcsrtombs из большого числа байтов в многобайтовые в старом языке C (чьи специфичные для локали варианты обычно вызываются существующими реализациями 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));
        }

ничего не записывается в выходную последовательность, если она не может вместить многобайтовый символ, полученный в результате использования одного входного широкого символа.

person Cubbi    schedule 17.10.2013
comment
Я не уверен, против какой части моих ожиданий вы пытаетесь возразить. Не могли бы вы уточнить? - person wilx; 18.10.2013
comment
@wilx вы ожидаете, что функция выдаст байт, а не многобайтовый символ. Это никогда не указывается, чтобы иметь возможность делать это, и аналогичные функции, а также существующие реализации не предназначены для этого. - person Cubbi; 18.10.2013
comment
Предполагая, что вы правы, насколько большим должен быть буфер? std::codecvt::max_length()? - person wilx; 18.10.2013

Хотя прямого упоминания об этом нет, я думаю, что это наиболее логичное поведение 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) параметр.

person N A    schedule 17.10.2013
comment
Существует также переменная/параметр state, который должен предоставить реализации некоторую информацию о состоянии преобразования. То, что вы описываете выше, это ИМХО, о чем следует позаботиться через состояние и вызов codecvt::unshift() (возможно, снова в цикле, если выходной буфер имеет длину всего один байт) перед повторным вызовом codecvt::out() для добавляемой строки. - person wilx; 18.10.2013
comment
@wilx это очень хороший момент. Microsoft уже ответила на ваш вопрос? (Я видел ваш вопрос на их сайте) - person N A; 20.10.2013
comment
Я пока не получил никакой реакции, кроме двух автоматических сообщений. - person wilx; 21.10.2013