使用std :: codecvt_utf8方面的问题

时间:2013-10-14 09:18:52

标签: c++ visual-studio utf-8 locale codecvt

以下是使用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转换的一个字节,但是Visual Studio 2012上面if条件的中间值为false。

更新

失败的是out_mid == out_endstate != 0条件。基本上,我希望至少生成一个字节,并且可以生成UTF-8序列的下一个字节的必要状态存储在state变量中。

2 个答案:

答案 0 :(得分:4)

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的现有实现调用)定义如下:

  

当下一个多字节字符超过要存储到dst指向的数组中的len个总字节数的限制时,转换会停止[...]。

另外两个,看看codecvt_utf8的现有实现:你已经探索过微软了,这里是libc ++中的内容:codecvt_utf8::do_out这里在Windows上调用ucs2_to_utf8ucs4_to_utf8在其他系统上,和ucs2_to_utf8 does the following(评论我的):

        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));
        }

如果输出序列无法容纳因使用一个输入宽字符而产生的多字节字符,则不会写入任何内容。

答案 1 :(得分:2)

虽然没有直接引用它,但我认为这是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_nextstd::codecvt::out的最后一个参数)在这里作为你离开的地方的参考 - 所以你知道在哪里继续 - 在你的情况下确实是相同的位置就像你开始的地方(extern_type* to)参数一样。