potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。

C++ UTF-16サロゲートペア判定とUTF-16/UTF-32文字列の相互変換

UTF-16サロゲートペア判定とUTF-16UTF-32文字列の相互変換コードです。Unicodeの仕様はネット上の解説ページを参照しているため、間違えてる可能性もあります。

#include <algorithm>
#include <string>

namespace u16_surrogatepair
{
    const char16_t high_surrogate_first = 0xd800;
    const char16_t high_surrogate_last = 0xdbff;
    const char16_t low_surrogate_first = 0xdc00;
    const char16_t low_surrogate_last = 0xdfff;
    const char32_t codepoint_surrogatepair_first = 0x010000;
    const char32_t codepoint_surrogatepair_last = 0x10ffff;

    // 文字が上位サロゲートであれば真を返します。
    constexpr bool is_high_surrogate(char16_t c) {
        return high_surrogate_first <= c && c <= high_surrogate_last;
    }

    // 文字が下位サロゲートであれば真を返します。
    constexpr bool is_low_surrogate(char16_t c) {
        return low_surrogate_first <= c && c <= low_surrogate_last;
    }

    // 2つの文字がサロゲートペアであれば真を返します。
    constexpr bool is_surrogatepair(char16_t high, char16_t low) {
        return is_high_surrogate(high) && is_low_surrogate(low);
    }

    // 2つの文字がサロゲートペアであればコードポイントへ変換して真を返します。
    // サロゲートペアでない場合は偽を返します。
    constexpr bool surrogatepair_to_codepoint(char16_t high, char16_t low, char32_t& codepoint) {
        if (!is_surrogatepair(high, low)) {
            return false;
        }
        codepoint = codepoint_surrogatepair_first
            + ((high - high_surrogate_first) << 10) | (low - low_surrogate_first);
        return true;
    }

    // コードポイントがサロゲートペアに対応する場合は真を返します。
    constexpr bool is_codepoint_surrogatepair(char32_t codepoint)
    {
        return codepoint_surrogatepair_first <= codepoint && codepoint <= codepoint_surrogatepair_last;
    }

    // コードポイントがサロゲートペアに対応する場合は変換して真を返します。
    // サロゲートペアに対応しない場合は偽を返します。
    constexpr bool codepoint_to_surrogatepair(char32_t codepoint, char16_t& high, char16_t& low)
    {
        if (!is_codepoint_surrogatepair(codepoint)) {
            return false;
        }
        auto c = codepoint - codepoint_surrogatepair_first;
        high = (c >> 10) | high_surrogate_first;
        low = (c & 0b1111111111) | low_surrogate_first;
        return true;
    }
}

// UTF-16文字列をUTF-32文字列へ変換します。
// 変換が成功した場合は真を返します。
bool to_u32string(const std::u16string& s, std::u32string& result)
{
    result.clear();
    result.reserve(s.size() / 2);
    for (size_t i = 0, l = s.size(); i < l; i++)
    {
        auto c1 = s[i];
        if (u16_surrogatepair::is_high_surrogate(c1))
        {
            if (++i == l)
                return false;
            auto c2 = s[i];
            char32_t codepoint;
            if (!u16_surrogatepair::surrogatepair_to_codepoint(c1, c2, codepoint))
                return false;
            result.push_back(codepoint);
        }
        else
        {
            result.push_back(c1);
        }
    }
    return true;
}

// UTF-16文字列をUTF-32文字列へ変換します。
// 変換は常に成功するため、常に真を返します。
bool to_u16string(const std::u32string& s, std::u16string& result)
{
    result.clear();
    result.reserve(s.size() * 2);
    for (size_t i = 0, l = s.size(); i < l; i++)
    {
        auto c = s[i];
        if (u16_surrogatepair::is_codepoint_surrogatepair(c))
        {
            char16_t high, low;
            u16_surrogatepair::codepoint_to_surrogatepair(c, high, low);
            result.push_back(high);
            result.push_back(low);
        }
        else
        {
            result.push_back(static_cast<char16_t>(c));
        }
    }
    return true;
}

int main()
{
    std::u32string s32;
    to_u32string(u"🍊🍎😁", s32);

    std::u16string s16;
    to_u16string(s32, s16);
}