iostream и локализация
От: SergH Россия  
Дата: 09.03.22 07:27
Оценка:
Привет, а кто-нибудь пытался использовать iostream для чего-то более сложного, чем hello world? Я чего-то видимо совсем не понимаю...

Начать с того, что функции конвертации в и из utf-8 мне написать удалось и они вроде даже работают.
Но выглядят они так:

wstring utf82w(const string& external) {
    codecvt_utf8<wchar_t> cc;
    wstring internal(external.size(), '\0');    
    const char* from_next;
    wchar_t* to_next;
    mbstate_t mb{};
    cc.in(mb, &external[0], &external[external.size()], from_next,
              &internal[0], &internal[internal.size()], to_next);
    internal.resize(to_next - &internal[0]);
    return internal;
}


Это поправленная копипаста из cppreference-а, идея понятная, но зачем же это так уродливо?

Дальше, codecvt_utf8 депрекейтед с 2017-го. Вместо него ничего не предложено, остался codecvt, который просто так не создать, у него конструктор приватный. Но его вроде бы можно получить через use_facet у локали... Это выглядит как-то так:

string w2utf8(const u32string& internal) {
    const auto& cc = use_facet<codecvt<char32_t, char, mbstate_t>>(locale("C"));
    string external(internal.size() * cc.max_length(), '\0');
    ...


Это работает. Но тут важно угадать подходящую локаль. Пока я писал locale("en_US.UTF-8"), ловил сегфолт при первом вызове метода сс. При этом локаль en_US.UTF-8 существует: для несуществующих он кидает исключение "locale::facet::_S_create_c_locale name not valid" при попытке их создать. Но вот конвертировать в UTF-8 она, видимо, не умеет...

Но может быть можно обойтись без извращений и просто писать в wcout юникод? А он сам умный? Если бы.

Вот такая программа:

#include <iostream>
#include <string>
#include <locale>
#include <codecvt>

using namespace std;

int main() {
    wstring ws1(L"абвгды");
    // cout << endl;                       -- волшебная строчка! 
    wcout << ws1 << endl;
    wcout.imbue(locale("C"));
    wcout << ws1 << endl;
    wcout.imbue(locale("en_US.UTF-8"));
    wcout << ws1 << endl;
    // wcout.imbue(locale("ru_RU.UTF-8")); -- тут я ловлю исключение, видимо такой локали у меня почему-то нет
    // wcout << ws1 << endl;
    wcout.imbue(locale(wcout.getloc(), new codecvt_utf8<wchar_t>)); // может ему кодека не хватает и надо явно прописать?
    wcout << ws1 << endl; 
}


Выводит на чистом транслите

abvgdy`
abvgdy`
abvgdy`
abvgdy`


Обратите внимание на букву ы!

А если раскомментировать волшебную строчку, то, конечно, случится волшебство. Вот такое:


01234K
01234K
01234K
01234K


И здесь понимание происходящего окончательно оставляет меня.

Единственное светлое пятно на фоне этого безнадёжного мрака -- utf8, кажется, нормально работает с обычным cout-ом, во всяком случае русские буковки рисует без проблем.

Расскажите пожалуйста, может есть какие-то гайды, как с этим нормально работать? Или стоит просто забыть о существовании стандартной библиотеки локализации навсегда?
Делай что должно, и будь что будет
Re: iostream и локализация
От: kov_serg Россия  
Дата: 09.03.22 08:11
Оценка: 24 (1)
Здравствуйте, SergH, Вы писали:

SH>Привет, а кто-нибудь пытался использовать iostream для чего-то более сложного, чем hello world? Я чего-то видимо совсем не понимаю...

А что вам мешает вызвать setlocale(LC_ALL, "en_US.UTF-8"); в начале?

SH>А если раскомментировать волшебную строчку, то, конечно, случится волшебство.

SH>И здесь понимание происходящего окончательно оставляет меня.
C++ он такой. Cмотрите в сторону
https://www.cplusplus.com/reference/cwchar/fwide
https://www.cplusplus.com/reference/cstdio/freopen
Re[2]: iostream и локализация
От: SergH Россия  
Дата: 09.03.22 08:28
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>А что вам мешает вызвать setlocale(LC_ALL, "en_US.UTF-8"); в начале?


Видимо непонимание связи. Я думал, достаточно установить локаль потоку, и дальше он как-нибудь сам.
Помогло, спасибо!

А как угадать, какая локаль доступна?
Я пытался найти стандартный способ получить их список, но не нашёл.
И как-то можно контролировать, во что именно превратится юникод?

_>C++ он такой. Cмотрите в сторону

_>https://www.cplusplus.com/reference/cwchar/fwide

Понял, то есть перемешивать использование cout и wcout нельзя.
Делай что должно, и будь что будет
Re[3]: iostream и локализация
От: kov_serg Россия  
Дата: 09.03.22 09:14
Оценка: 8 (1)
Здравствуйте, SergH, Вы писали:

SH>А как угадать, какая локаль доступна?

SH>Я пытался найти стандартный способ получить их список, но не нашёл.
в линухах locale -a
в винде наверное как-то так:
#include <stdio.h>
#include <windows.h>

BOOL CALLBACK fne(LPWSTR wloc,DWORD flags,LPARAM userdata) {
    enum { loc_size=128 }; char loc[loc_size+1];
    loc[ WideCharToMultiByte(CP_UTF8,0,wloc,wcslen(wloc),loc,loc_size,0,0) ]=0;
    printf("%s\n",loc); return TRUE;
}

int main(int argc,char** argv) {
    EnumSystemLocalesEx(fne,LOCALE_ALL,0,0);
    return 0;
}

SH>И как-то можно контролировать, во что именно превратится юникод?
Хз я своими велосипедами пользуюсь, они более предсказуемы.
Re: iostream и локализация
От: Bill Baklushi СССР  
Дата: 09.03.22 09:32
Оценка: +1
SergH:

SH>Начать с того, что функции конвертации в и из utf-8 мне написать удалось и они вроде даже работают.


Конвертацию между UTF8 и UTF16 можно сделать используя битовые трюки, без всяких локалей и библиотечных функций.
Встречал такой код на стековервлоу и успешно использовал.

Для конвертации национальных кодировок в/из UTF8 UTF16 можно использовать MultiByteToWideChar/WideCharToMultiByte для windows.
Для linux — libiconv.
Модератор-националист Kerk преследует оппонентов по политическим мотивам.
Отредактировано 09.03.2022 11:24 Bill Baklushi . Предыдущая версия .
Re[2]: iostream и локализация
От: kov_serg Россия  
Дата: 09.03.22 09:50
Оценка:
Здравствуйте, Bill Baklushi, Вы писали:

BB>SergH:


SH>>Начать с того, что функции конвертации в и из utf-8 мне написать удалось и они вроде даже работают.


BB>Конвертацию между UTF8 и UTF16 можно сделать используя битовые трюки, без всяких локалей и библиотечных функций.

BB>Встречал такой код на стековервлоу и успешно использовал.
У меня такой файл валяется со времен института.
// utf.h
#pragma once

#include <string>
using std::string;
using std::wstring;

enum { utf_max_len=6 };

// raw utf
int uc_to_utf(int w, char *p);
int utf_to_uc(const char *p,int *w,int lim);
int could_be_utf(const char *s,int len);

// utf utils
string uc2utf(const wstring &s);
bool   utf2uc(wstring& res,const string& src);
int    checkutf(const string &s);

// utf.cpp
#include "utf.h"

int uc_to_utf(int w, char *p) {
  int r,n;
  if (w&0x80000000) return 0;
  if (w&0xFC000000) n=6; else
  if (w&0xFFE00000) n=5; else
  if (w&0xFFFF0000) n=4; else
  if (w&0xFFFFF800) n=3; else
  if (w&0xFFFFFF80) n=2; else { p[0]=char(w); return 1; }
  r=n; while (n>1) { p[--n]=(char)( (w & 0x3F) | 0x80 ); w>>=6; }
  p[0]=(char)( (255<<(8-r)) | w ); return r;
}
int utf_to_uc(const char *p,int *w,int lim) {
  if (lim<1) return 0;
  int z=(unsigned char)p[0];
  if ((z&0x80)==0) { if (w) *w=z; return 1; }
  int n=0, q=0x80; do { n++; q>>=1; } while (z&q);
  if ( n<2 || lim<n ) return 0;     // invalid or no room
  int r=z&(q-1);if (r==0) return 0; // prohibited coding 
  int len=n; while (n>1) {
    z=*++p; if ((z&0xC0)!=0x80) return 0; // invalid coding
    r=(r<<6)|(z&0x3F); n--;
  }
  if (w) *w=r; return len;
}
int could_be_utf(const char *s,int len) { // -1-can not be utf, 0-could be utf, but no utf symbols, 1-valid utf and utf symbols found
  int res=-1, i=0;
  while (i<len) {
    int w, k=utf_to_uc(s+i,&w,len-i);
      if (k==0 || w>65535) return -1;
      if (k>1) res=0;
      i+=k; 
  }
  return ++res;
}
string uc2utf(const wstring &s) {
    char buf[utf_max_len+1];
    string res;
    int n=s.length();
    for(int i=0;i<n;i++) {
        int k=uc_to_utf(s[i],buf); buf[k]='\0';
        res.append(buf);
    }
    return res;
}
bool utf2uc(wstring& res,const string& src) {
    res.empty(); 
    const char *p=src.c_str(); int n=src.length();
    for(int i=0;i<n;) {
        int w=0,k=utf_to_uc(p+i,&w,n-i);
        if (k==0 || w>65535) return false; 
        i+=k; res+=wchar_t(w);
    }
    return true;
}
int checkutf(const string &s) { return could_be_utf(s.c_str(),s.length()); }
Re[4]: iostream и локализация
От: SergH Россия  
Дата: 09.03.22 09:56
Оценка: +1
Здравствуйте, kov_serg, Вы писали:

_>в линухах locale -a

_>в винде наверное как-то так:

Спасибо за способ для линукса, посмотрел, что там у меня. У меня они все называются немного иначе: ru_RU.utf8. Но всё равно locale::facet::_S_create_c_locale name not valid да и setlocale(LC_ALL, "ru_RU.utf8") не срабатывает. Но в списке есть...

Для винды да, видел такой код. Но я имел ввиду, конечно, что-то стандартное независимое из С++. Раз уж её надо указывать... Хотя может быть подразумевается, что её из какой-нибудь переменной окружения берут?

_>Хз я своими велосипедами пользуюсь, они более предсказуемы.


Да. Велосипед написать несложно. То, что он оказывается надежнее, проще и удобнее, на мой взгляд, говорит много плохого про стандартную локализацию а может и про всю iostream.
Делай что должно, и будь что будет
Re[2]: iostream и локализация
От: SergH Россия  
Дата: 09.03.22 10:05
Оценка:
Здравствуйте, Bill Baklushi, Вы писали:

BB>Конвертацию между UTF8 и UTF16 можно сделать используя битовые трюки, без всяких локалей и библиотечных функций.

BB>Встречал такой код на стековервлоу и успешно использовал.

Можно посмотреть определение utf-8 в википедии и реализовать преобразование utf-8 <--> unicode (UCS4) полностью самостоятельно Там довольно просто всё.
Возня с битами может получится не очень эффективной, но если там не сотни мегабайт, то должно прокатить.

Специально делать для utf16... вот не помню, чтобы он мне вообще был нужен.

BB>Для конвертации национальных кодировок в/из UTF8 можно использовать MultiByteToWideChar/WideCharToMultiByte для windows.

BB>Для linux — libiconv.

Нуу, да, это понятно, это я тоже умею.
Меня шокировала сложная и продуманная библиотека локализации в С++, которую я оказался не в состоянии использовать.
Так-то варианты понятно что есть.
Делай что должно, и будь что будет
Re: endl или \n
От: Sm0ke Россия ksi
Дата: 09.03.22 15:53
Оценка:
Здравствуйте, SergH, Вы писали:

При выводе на консоль через cout/wcout я решил отказаться от std::endl в одном своём проекте. Вместо этого выводил \n в явном виде, чтобы быстрее работало.
Re[2]: endl или \n
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 09.03.22 18:28
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>При выводе на консоль через cout/wcout я решил отказаться от std::endl в одном своём проекте. Вместо этого выводил \n в явном виде, чтобы быстрее работало.


endl вроде еще flush делает. А так — да, в остальном он равнозначен "\n".

И да, C++ iostreams — это не про скорость
Маньяк Робокряк колесит по городу
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.