potisanのプログラミングメモ

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

C++20 標準ライブラリでUTF-16とUTF-32を変換する関数

C++標準ライブラリを使用してUTF-16UTF-32を相互変換する関数のコードです。コンセプトにより制約するため、C++20以降用です。

u16_surrogate.hpp

#pragma once

namespace u16_surrogate
{
    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 = 0x00010000;
    const char32_t codepoint_surrogatepair_last = 0x0010ffff;

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

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

    // 文字がサロゲートであれば真を返します。
    constexpr bool is_surrogate(char16_t c) noexcept
    {
        return is_high_surrogate(c) || is_low_surrogate(c);
    }

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

    // 2つの文字がサロゲートペアであればコードポイントへ変換して真を返します。
    // サロゲートペアでない場合は偽を返します。
    constexpr bool surrogatepair_to_codepoint(char16_t high, char16_t low, char32_t& codepoint) noexcept
    {
        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) noexcept
    {
        return codepoint_surrogatepair_first <= codepoint && codepoint <= codepoint_surrogatepair_last;
    }

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

u16s_u32s.hpp

#pragma once

#ifndef _STRING_
#error <string>のインクルードが必要です。
#endif

#include "u16_surrogate.hpp"

namespace u16s_u32s
{
    template <typename result_type>
    requires requires (result_type result) {
        result.clear();
        result.reserve(std::declval<size_t>());
        result.push_back(std::declval<char32_t>());
    }
    bool to_u32container(std::u16string_view source, result_type& result)
        noexcept(
            noexcept(result.clear()) &&
            noexcept(result.reserve(std::declval<size_t>())) &&
            noexcept(result.push_back(std::declval<char32_t>())) &&
            noexcept(source[std::declval<size_t>()]))
    {
        const auto l = source.size();
        result.clear();
        result.reserve(l / 2);
        for (size_t i = 0; i < l; i++)
        {
            const auto c1 = source[i];
            if (u16_surrogate::is_high_surrogate(c1))
            {
                if (++i == l)
                    return false;
                const auto c2 = source[i];
                char32_t codepoint;
                if (!u16_surrogate::surrogatepair_to_codepoint(c1, c2, codepoint))
                    return false;
                result.push_back(codepoint);
            }
            else
            {
                result.push_back(c1);
            }
        }
        return true;
    }

    template <typename result_type>
    requires requires (result_type result) {
        result.clear();
        result.reserve(std::declval<size_t>());
        result.push_back(std::declval<char16_t>());
    }
    bool to_u16container(std::u32string_view source, result_type& result)
        noexcept(
            noexcept(result.clear()) &&
            noexcept(result.reserve(std::declval<size_t>())) &&
            noexcept(result.push_back(std::declval<char16_t>())) &&
            noexcept(source[std::declval<size_t>()]))
    {
        const auto l = source.size();
        result.clear();
        result.reserve(l * 2);
        for (size_t i = 0; i < l; i++)
        {
            const auto c = source[i];
            if (u16_surrogate::is_codepoint_surrogatepair(c))
            {
                char16_t high, low;
                u16_surrogate::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;
    }

    // UTF-16文字列をUTF-32文字列へ変換します。
    // 変換が成功した場合は真を返します。
    inline bool to_u32string(std::u16string_view source, std::u32string& result)
        noexcept(noexcept(to_u32container<std::u32string>(source, result)))
    {
        return to_u32container<std::u32string>(source, result);
    }

    // UTF-32文字列をUTF-16文字列へ変換します。
    // 変換は常に成功するため、常に真を返します。
    inline bool to_u16string(std::u32string_view source, std::u16string& result)
        noexcept(noexcept(to_u16container<std::u16string>(source, result)))
    {
        return to_u16container<std::u16string>(source, result);
    }

#ifdef _VECTOR_
    // UTF-16文字列をUTF-32文字ベクトルへ変換します。
    // 変換が成功した場合は真を返します。
    inline bool to_u32vector(std::u16string_view source, std::vector<char32_t>& result)
        noexcept(noexcept(to_u32container<std::vector<char32_t>>(source, result)))
    {
        return to_u32container<std::vector<char32_t>>(source, result);
    }

    // UTF-32文字列をUTF-16文字ベクトルへ変換します。
    // 変換は常に成功するため、常に真を返します。
    inline bool to_u16vector(std::u32string_view source, std::vector<char16_t>& result)
        noexcept(noexcept(to_u16container<std::vector<char16_t>>(source, result)))
    {
        return to_u16container<std::vector<char16_t>>(source, result);
    }
#endif
}

テストコード

#include <string>
#include <vector>

#include "u16s_u32s.hpp"

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

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

    std::vector<char32_t> v1;
    u16s_u32s::to_u32vector(u"🍊🍎😁", v1);
}