Convertir entre std::u8string y std::string

14 minutos de lectura

C ++ 20 agregado char8_t y std::u8string para UTF-8. Sin embargo, no existe una versión UTF-8 de std::cout y las API del sistema operativo en su mayoría esperan char y conjunto de caracteres de ejecución. Por lo tanto, todavía necesitamos una forma de convertir entre UTF-8 y el conjunto de caracteres de ejecución.

estaba releyendo un papel char8_t y parece que la única forma de convertir entre UTF-8 y ECS es usar std::c8rtomb y std::mbrtoc8 funciones Sin embargo, su API es extremadamente confusa. ¿Alguien puede proporcionar un código de ejemplo?

  • Sin embargo, no existe una versión UTF-8 de std::cout“- std::wcout?

    – TrebledJ

    7 de abril de 2019 a las 6:23

  • @TrebledJ Este usa un conjunto de caracteres de ejecución amplio.

    usuario3624760

    7 de abril de 2019 a las 6:24

  • En Mac y Linux, generalmente puede imprimir utf-8 directamente (creo que hay algunas distribuciones de Linux raras en las que este no es el caso) en Windows a las que debe convertir wchar_t y use wcout.

    – Alan Birtles

    7 de abril de 2019 a las 6:44

  • Quiero seguir siendo multiplataforma, así que realmente no puedo asumir qué es ECS. Mi plataforma principal es Linux, por lo que, en la práctica, yo mismo tendría una conversión sin pérdidas, pero aún quiero permanecer dentro de los límites multiplataforma.

    usuario3624760

    7 de abril de 2019 a las 6:52

  • @Lyberta “Escuché que la compatibilidad con UTF-8 se mejoró en Windows 10*”, algo. Microsoft ahora permite que se use UTF-8 como la configuración regional ANSI del usuario (pero esa característica todavía está en versión beta), de modo que ahora puede usar cadenas UTF-8 en las API ANSI, por ejemplo. Pero las API del sistema operativo y Unicode todavía se basan en UTF-16 y eso es lo que debe seguir para obtener el mejor rendimiento.

    – Rémy Lebeau

    9 de abril de 2019 a las 15:21

avatar de usuario
coste y flete

El “soporte” de UTF-8 en C++ 20 parece ser una broma de mal gusto.

La única funcionalidad UTF en la biblioteca estándar es la compatibilidad con cadenas y string_views (std::u8string, std::u8string_view, std::u16string, …). Eso es todo. No hay compatibilidad con la biblioteca estándar para la codificación UTF en expresiones regulares, formateo, E/S de archivos, etc.

En C ++ 17 puede, al menos, tratar fácilmente cualquier dato UTF-8 como datos ‘char’, lo que hace posible el uso de std::regex, std::fstream, std::cout, etc. sin pérdida de rendimiento

En C++20 las cosas cambiarán. ya no puedes escribir por ejemplo std::string text = u8"..."; Será imposible escribir algo como

std::u8fstream file; std::u8string line; ... file << line;

ya que no hay std::u8fstream.

Incluso el nuevo C++20 std::format no admite UTF en absoluto, porque simplemente faltan todas las sobrecargas necesarias. No puedes escribir

std::u8string text = std::format(u8"...{}...", 42);

Para empeorar las cosas, no hay conversión simple (o conversión) entre std::string y std::u8string (o incluso entre const char* y const char8_t*). Entonces, si desea formatear (usando std::format) o ingresar/salir (std::cin, std::cout, std::fstream, …) datos UTF-8, debe copiar internamente todas las cadenas. – Eso será un asesino de rendimiento innecesario.

Finalmente, ¿qué uso tendrá UTF sin entrada, salida y formato?

  • printf( "%s", (char const *)u8"ひらがな" ); — esto funciona para la impresión. Por ahora.

    – Chef Gladiador

    29 de noviembre de 2019 a las 12:25

  • @ChefGladiator lo hace? ¿Cómo sabes que el programa en el otro extremo del printf realmente decodifica tu transmisión como utf8?

    – espectros

    16 de marzo de 2020 a las 9:39

  • @spectras mi punto exactamente. Para ser explícito: soy perfectamente consciente de prog, lang. no funciona con suposiciones. O hackeos. O comportamiento del compilador no documentado.

    – Chef Gladiador

    17 de marzo de 2020 a las 10:14

  • @ChefGladiator charN_t los tipos tienen nombres horribles porque no tienen nada que ver con “personajes”. La asignación de valores escalares Unicode a glifos tampoco es trivial. estoy trabajando en un propuesta con nombres propios y tipos fuertes.

    usuario3624760

    18 de marzo de 2020 a las 2:49

  • Qué pena que utf8 no se pueda integrar directamente en std::string. Esa es una de las muchas razones por las que tengo una clara preferencia por Qt sobre la biblioteca estándar (y STL).

    – Kiruahxh

    27 oct 2020 a las 20:27

Actualmente, std::c8rtomb y std::mbrtoc8 son las únicas interfaces proporcionadas por el estándar que permiten la conversión entre la codificación de ejecución y UTF-8. Las interfaces son incómodas. Fueron diseñados para coincidir con interfaces preexistentes como std::c16rtomb y std::mbrtoc16. La redacción agregada al estándar C++ para estas nuevas interfaces coincide intencionalmente con la redacción en el estándar C para las funciones relacionadas preexistentes (con suerte, estas nuevas funciones eventualmente se agregarán a C; todavía necesito continuar con eso). La intención de hacer coincidir la redacción estándar de C, por confusa que sea, es garantizar que cualquier persona familiarizada con la redacción de C reconozca que el char8_t Las interfaces funcionan de la misma manera.

cppreference.com tiene algunos ejemplos para las versiones UTF-16 de estas funciones que deberían ser útiles para comprender el char8_t variantes.

  • Mientras tanto, cppreference ha agregado la especificación completa de <cuchar>como debería contener cada C++20 std lib.

    – Chef Gladiador

    17 de marzo de 2020 a las 10:29


avatar de usuario
lotario

La respuesta común dada por las autoridades de C++ en la convención anual CppCon (como en 2018 y 2019) fue que debería elegir su propia biblioteca UTF8 para hacerlo. Hay de todo tipo de sabores, solo elige el que más te guste. Todavía hay vergonzoso poco entendimiento y soporte para Unicode en el lado de C++.

Algunas personas esperan que haya algo en C++23, pero hasta ahora ni siquiera tenemos un grupo de trabajo oficial.

  • Nosotros “ni siquiera tenemos un grupo de trabajo oficial” como en SG16 que existe desde hace más de un año?

    – Davis arenque

    21 de noviembre de 2019 a las 1:35

  • Que broma. ¿Por qué agregarlo en el estándar entonces en primer lugar si solo obtenemos un borrador incompleto rudimentario que rompe el código existente?

    – Sebastián Hoffmann

    7 de julio de 2020 a las 11:57

  • Ahora, con C ++ 23 a solo 4 meses, puedo prometerles que no se avecina ninguna mejora. Con la esperanza de C ++ 26 o C ++ 29 … o nunca jamás.

    – Lotario

    14 de agosto a las 19:12

avatar de usuario
chef gladiador

Actualización 2022 ABR 19

// warnings but prints ok on LINUX
// g++ prog.cc  -Wall -Wextra -std=c++2a
//
// clang++ prog.cc -Wall -Wextra -std=c++2a
// lot of warnings but prints OK on LINUX
//
#include <cassert>
#include <clocale>
#include <cstdio>
#include <cstdlib>  // MB_CUR_MAX
#include <cuchar>

#undef P_
#undef P

#define P_(F_, X_) printf("\n%4d : %32s => " F_, __LINE__, #X_, (X_))
#define P(F_, X_) P_(F_, X_)

/*
  using mbstate_t = ... see description ...
  using size_t = ... see description ...

  in the standard but not implemented yet by any of the three

  size_t mbrtoc8(char8_t* pc8, const char* s, size_t n, mbstate_t* ps);
  size_t c8rtomb(char* s, char8_t c8, mbstate_t* ps);
*/

namespace {
constexpr inline auto bad_size = ((size_t)-1);

// https://en.wikipedia.org/wiki/UTF-8
// a compile time constant not intrinsic function
constexpr inline int UTF8_CHAR_MAX_BYTES = 4;

#ifdef STANDARD_CUCHAR_IMPLEMENTED
template <size_t N>
 auto char_star(const char8_t (&in)[N]) noexcept {
  mbstate_t state;
  constexpr static int out_size = (UTF8_CHAR_MAX_BYTES * N) + 1;

  struct {
    char data[out_size];
  } out = {{0}};
  char* one_char = out.data;
  for (size_t rc, n = 0; n < N; ++n) {
    rc = c8rtomb(one_char, in[n], &state);
    if (rc == bad_size) break;
    one_char += rc;
  }
  return out;
}
#endif //  STANDARD_CUCHAR_IMPLEMENTED

template <size_t N>
auto char_star(const char16_t (&in)[N]) noexcept {
  mbstate_t state;
  constexpr static int out_size = (UTF8_CHAR_MAX_BYTES * N) + 1;

  struct final {
    char data[out_size];
  } out = {{0}};
  char* one_char = out.data;
  for (size_t rc, n = 0; n < N; ++n) {
    rc = c16rtomb(one_char, in[n], &state);
    if (rc == bad_size) break;
    one_char += rc;
  }
  return out;
}

template <size_t N>
auto char_star(const char32_t (&in)[N]) noexcept {
  mbstate_t state;
  constexpr static int out_size = (UTF8_CHAR_MAX_BYTES * N) + 1;

  struct final {
    char data[out_size];
  } out = {{0}};
  char* one_char = out.data;
  for (size_t rc, n = 0; n < N; ++n) {
    rc = c32rtomb(one_char, in[n], &state);
    if (rc == bad_size) break;
    one_char += rc;
  }
  return out;
}

}  // namespace
#define KATAKANA "片仮名"
#define KATAKANA8 u8"片仮名"
#define KATAKANA16 u"片仮名"
#define KATAKANA32 U"片仮名"

int main(void) {
  P("%s", KATAKANA);  // const char *
  // lot of warnings but ok output
  P("%s", KATAKANA8);  // const char8_t *

  /*
  garbled or no output
  P( "%s",  KATAKANA16 ); // const char16_t *
  P( "%s" , KATAKANA32 ); // const char32_t *
  */

  setlocale(LC_ALL, "en_US.utf8");

  // no can do as there is no standard <cuchar> yet
  // P( "%s", char_star(KATAKANA8).data );  // const char8_t *
  P("%s", char_star(KATAKANA16).data);  // const char16_t *
  P("%s", char_star(KATAKANA32).data);  // const char32_t *
}

Actualización 2021 MAR 19

Pocas cosas han (no) pasado. __STDC_UTF_8__ ya no es y <cuchar> todavía no está implementado por ninguno de “los Tres”.

Probablemente mucho mejor código que coincida con este hilo es AQUÍ.

Actualización 2020 MAR 17

std::c8rtomb y std::mbrtoc8 aún no se proporcionan.

2019 NOV

std::c8rtomb y std::mbrtoc8 aún no son proporcionados por los futuros compiladores listos para C++ 20 creados por “The 3”, para permitir la conversión entre la codificación de ejecución y UTF-8. Se describen en el estándar C++20.

Puede ser subjetivo, pero c8rtomb() no es una interfaz “incómoda”, para mí.

CAJA DE VARITAS

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

He comentado y documentado líneas que no se compilan a día de hoy.

avatar de usuario
vitalidad

AFAIK C ++ aún no proporciona instalaciones para dicha conversión. Sin embargo, recomendaría no usar std::u8string en primer lugar porque tiene un soporte deficiente en el estándar y no es compatible con ninguna API del sistema (y probablemente nunca lo será por razones de compatibilidad). En la mayoría de las plataformas normal char las cadenas ya son UTF-8 y en Windows con MSVC puede compilar con /utf-8 que le brindará compatibilidad con Unicode portátil en los principales sistemas operativos.

avatar de usuario
YShmidt

VS 2019

  ostream& operator<<(ostream& os, const u8string& str)
    {
        os << reinterpret_cast<const char*>(str.data());
        return os;
    }

Para configurar la consola en uso UTF-8 https://github.com/MicrosoftDocs/cpp-docs/issues/1915#issuecomment-589644386

Aquí está el código que debe cumplir con C++20. Dado que ningún compilador actualmente (marzo de 2020) implementa las funciones de conversión definidas en el documento, decidí no limitarme con lo que está implementado actualmente y usar la especificación completa de C++20. Así que en lugar de tomar std::basic_string o std::basic_string_view Tomo rangos de unidades de código. El valor de retorno es menos general pero es trivial cambiarlo para tomar el rango de salida en su lugar. Esto se deja como ejercicio para el lector.

/// \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;
}

  • En el primero ToUTF8String sobrecarga, creo que su manejo de la -2 caso no es del todo correcto. En ese caso, no se almacena ningún valor en temp_bufferpor lo que el agregado incondicional a output después de que la instrucción switch sea incorrecta. De lo contrario, ¡este código me parece bastante bueno!

    – Tom Honerman

    20 de marzo de 2020 a las 2:51

¿Ha sido útil esta solución?