web-dev-qa-db-ja.com

std :: u8stringとstd :: stringの間の変換

C++ 20では、UTF-8にchar8_tおよびstd::u8stringが追加されました。ただし、std::coutのUTF-8バージョンはなく、OS APIは主にcharおよび実行文字セットを想定しています。したがって、UTF-8と実行文字セットを変換する方法が必要です。

私は char8_t論文 を読み直していましたが、UTF-8とECSの間で変換する唯一の方法はstd::c8rtomb関数とstd::mbrtoc8関数を使用することです。ただし、そのAPIは非常に混乱します。誰かがサンプルコードを提供できますか?

13
Lyberta

2020年の更新MAR 17

_std::c8rtomb_および_std::mbrtoc8_はまだ提供されていません。

2019年11月

_std::c8rtomb_および_std::mbrtoc8_は、「3」によって作成された将来のC++ 20対応コンパイラによって、実行エンコーディングとUTF-8間の変換を可能にするために、まだ提供されていません。それらはC++ 20標準で記述されています。

主観的かもしれませんが、c8rtomb()は私にとって「扱いにくい」インターフェースではありません。

[〜#〜]ワンドボックス[〜#〜]

_//  g++ prog.cc -std=gnu++2a
//  clang++ prog.cc -std=c++2a
#include <stdio.h>
#include <clocale>
#ifndef __clang__
#include <cuchar>
#else
// clang has no <cuchar>
#include <uchar.h>
#endif
#include <climits>

template<size_t N>
void  u32sample( const char32_t (&str32)[N] )
{
    #ifndef __clang__
    std::mbstate_t state{};
    #else
    mbstate_t state{};
    #endif

    char out[MB_LEN_MAX]{};
    for(char32_t const & c : str32)
    {
    #ifndef __clang__
        /*std::size_t rc =*/ std::c32rtomb(out, c, &state);
    #else
        /* std::size_t rc =*/ ::c32rtomb(out, c, &state);
    #endif
        printf("%s", out ) ;
    }
}

#ifdef __STDC_UTF_8__
template<size_t N>
void  u8sample( const char8_t (& str8)[N])
{
    std::mbstate_t state{};

    char out[MB_LEN_MAX]{};
    for(char8_t const & c : str8)
    {
       /* std::size_t rc = */ std::c8rtomb(out, c, &state);
        printf("%s", out ) ;
    }
}
#endif // __STDC_UTF_8__
int main () {
    std::setlocale(LC_ALL, "en_US.utf8");

    #ifdef __linux__
    printf("\nLinux like OS, ") ;
    #endif

    printf(" Compiler %s\n", __VERSION__   ) ;

   printf("\nchar32_t *, Converting to 'char *', and then printing --> " ) ;
   u32sample( U"ひらがな" ) ;

  #ifdef __STDC_UTF_8__
   printf("\nchar8_t *, Converting to 'char *', and then printing --> " ) ;
   u8sample( u8"ひらがな" ) ;
  #else
   printf("\n\n__STDC_UTF_8__ is not defined, can not use char8_t");
  #endif

   printf("\n\nDone ..." ) ;

    return 42;
}
_

今日の時点でコンパイルされない行をコメントアウトして文書化しました。

0
Chef Gladiator

C++ 20に準拠しているはずのコードを次に示します。現在(2020年3月)には、このペーパーで定義されている変換関数を実装しているコンパイラがないため、現在実装されているものに制約されず、C++ 20の完全な仕様を使用することにしました。したがって、std::basic_stringまたはstd::basic_string_viewコードユニットの範囲を取得します。戻り値は一般的ではありませんが、代わりに出力範囲を取るように変更するのは簡単です。これは読者への演習として残されています。

/// \brief Converts the range of UTF-8 code units to execution encoding.
/// \tparam R Type of the input range.
/// \param[in] input Input range.
/// \return std::string in the execution encoding.
/// \throw std::invalid_argument If input sequence is ill-formed.
/// \note This function depends on the global locale.
template <std::ranges::input_range R>
requires std::same_as<std::ranges::range_value_t<R>, char8_t>
std::string ToECSString(R&& input)
{
    std::string output;
    char temp_buffer[MB_CUR_MAX];
    std::mbstate_t mbstate{};
    auto i = std::ranges::begin(input);
    auto end = std::ranges::end(input);
    for (; i != end; ++i)
    {
        std::size_t result = std::c8rtomb(temp_buffer, *i, &mbstate);
        if (result == -1)
        {
            throw std::invalid_argument{"Ill-formed UTF-8 sequence."};
        }
        output.append(temp_buffer, temp_buffer + result);
    }
    return output;
}

/// \brief Converts the input range of code units in execution encoding to
/// UTF-8.
/// \tparam R Type of the input range.
/// \param[in] input Input range.
/// \return std::u8string containing UTF-8 code units.
/// \throw std::invalid_argument If input sequence is ill-formed or does not end
/// at the scalar value boundary.
/// \note This function depends on the global C locale.
template <std::ranges::input_range R>
requires std::same_as<std::ranges::range_value_t<R>, char>
std::u8string ToUTF8String(R&& input)
{
    std::u8string output;
    char8_t temp_buffer;
    std::mbstate_t mbstate{};
    std::size_t result;
    auto i = std::ranges::begin(input);
    auto end = std::ranges::end(input);
    while (i != end)
    {
        result = std::mbrtoc8(&temp_buffer, std::to_address(i), 1, &mbstate);
        switch (result)
        {
            case 0:
            {
                ++i;
                break;
            }
            case std::size_t(-3):
            {
                break;
            }
            case std::size_t(-2):
            {
                ++i;
                break;
            }
            case std::size_t(-1):
            {
                throw std::invalid_argument{"Invalid input sequence."};
            }
            default:
            {
                std::ranges::advance(i, result);
                break;
            }
        }
        if (result != std::size_t(-2))
        {
            output.append(1, temp_buffer);
        }
    }
    if (result == -2)
    {
            throw std::invalid_argument{
                "Code unit sequence does not end at the scalar value "
                "boundary."};
    }
    return output;
}

/// \brief Converts the contiguous range of code units in execution encoding to
/// UTF-8.
/// \tparam R Type of the contiguous range.
/// \param[in] input Input range.
/// \return std::u8string containing UTF-8 code units.
/// \throw std::invalid_argument If input sequence is ill-formed or does not end
/// at the scalar value boundary.
/// \note This function depends on the global C locale.
template <std::ranges::contiguous_range R>
requires std::same_as<std::ranges::range_value_t<R>, char>
std::u8string ToUTF8String(R&& input)
{
    std::u8string output;
    char8_t temp_buffer;
    std::mbstate_t mbstate{};
    std::size_t offset = 0;
    std::size_t size = std::ranges::size(input);
    while (offset != size)
    {
        std::size_t result = std::mbrtoc8(&temp_buffer,
            std::ranges::data(input) + offset, size - offset, &mbstate);
        switch (result)
        {
            case 0:
            {
                ++offset;
                break;
            }
            case std::size_t(-3):
            {
                break;
            }
            case std::size_t(-2):
            {
                throw std::invalid_argument{
                    "Input sequence does not end at the scalar value "
                    "boundary."};
            }
            case std::size_t(-1):
            {
                throw std::invalid_argument{"Invalid input sequence."};
            }
            default:
            {
                offset += result;
                break;
            }
        }
        output.append(1, temp_buffer);
    }
    return output;
}
0
Lyberta

毎年のCppCon大会(2018や2019など)でC++当局から寄せられた一般的な答えは、独自のUTF8ライブラリを選択する必要があるというものでした。好きなものを選ぶだけであらゆる種類の味があります。 C++側のUnicodeに対する理解とサポートはまだ恥ずかしいものです。

一部の人々はC++ 23に何かがあると思いますが、今のところ公式のワーキンググループさえありません。

0
Lothar