Кодировки

Как надо работать с кодировками текста в свете стандарта С++17? Интересует в первую очередь преобразование кириллицы между cp-1251 и utf-8. Но также интересен и общий случай.

В этой теме на форуме что-то подобное обсуждается, но этот код под MSVC++ под стандарт С++17 не компилируется — говорит депрекейтед и советует обратиться к функциям из WinAPI.

А хотелось бы универсальное, кроссплатформенное решение без привлечения сторонних библиотек (т.е. в пределах С++ и STL). Возможно?

Как надо работать с кодировками текста в свете стандарта С++17?

Никак. C++ по факту, не поддерживает Unicode, а значит только латиница, всё остальное будет плавать.

но этот код под MSVC++ под стандарт С++17

MSVC++ не поддерживает C++17, разве что чуть-чуть. std::codecvt_utf8 теперь deprecated, так что не нужно его использовать, если есть расчет на использование более новых компиляторов.

А хотелось бы универсальное, кроссплатформенное решение без привлечения сторонних библиотек (т.е. в пределах С++ и STL). Возможно?

Без сторонних библиотек — только самописный велосипед.

Никак. C++ по факту, не поддерживает Unicode

А как же

std::ofstream(L"Теперь можно открывать файлы с юникодными именами.txt") << u8"Ура!";

источник

А как же char32_t, chat16_t, wchar_t? Первые два, как я понимаю, прямо соответствуют UTF-32 b UTF-16.

И еще (драфт стандарта C++17, п. 25.4.1.4):

1 The class codecvt<internT, externT, stateT> is for use when converting from one character encoding to another, such as from wide characters to multibyte characters or between wide character encodings such as Unicode and EUC.
2 The stateT argument selects the pair of character encodings being mapped between.
3 The specializations required in Table 69 (25.3.1.1.1) convert the implementation-defined native character set. codecvt<char, char, mbstate_t> implements a degenerate conversion; it does not convert at all. The specialization codecvt<char16_t, char, mbstate_t> converts between the UTF-16 and UTF-8 encoding forms, and the specialization codecvt <char32_t, char, mbstate_t> converts between the UTF-32 and UTF-8 encoding forms. codecvt<wchar_t, char, mbstate_t> converts between the native character sets for narrow and wide characters. Specializations on mbstate_t perform conversion between encodings known to the library implementer. Other encodings can be converted by specializing on a user-defined stateT type. Objects of type stateT can contain any state that is useful to communicate to or from the specialized do_in or do_out members.

Вроде как механизм есть. Только как им пользоваться?

Хорошо (в смысле плохо), если невозможно из UTF-8 перекодировать в cp-1251, то как UTF-8 (или другую мультибайтовую кодировку, в которую можно преобразовать UTF-8) вывести на консоль?

А как же

Это вообще ни о том. Вот дам строку UTF-8, найди в ней все цифры. Или перекодируй её, например, в UTF-16 BE или в UTF-32 стандартными средствами C++.

А как же char32_t, chat16_t, wchar_t?

В моей библиотеке, например, используется std::vector<uint32_t> для реализации работы с Юникодом. Значит ли это, что vector — это Unicode?
Ну есть тип для символов, а поддержки того, что это за символы — нет. Нет работы с этими символами. Да даже специализации для потоко нет для этих символов. Например, какой-нибудь iswdigit даже с wchar_t без указания локали всё равно не сможет ничего сделать. Да и с локалью тоже хрень получается. Посмотри, например, на Java, Qt, iconv, Glib::ustring, они как раз поддерживают Unicode. Видите разницу?

Вроде как механизм есть.

Он уже устарел. Появился в C++11, устарел в C++17. Замены нет. А получилось так, потому что это фигня лютая.

вывести на консоль?

Консоль Windows не поддерживает добром Unicode. А под Linux, например, терминал, скорее всего, и так будет работать с UTF-8 по дефолту, так что там такой проблемы нет.
Вообще, просто делается один раз инструмент для перекодировки (или берется готовый), который умеет работать с разными кодировками. Работаем в какой-то одной кодировке, а читаем и выводим в той, в которой надо.
Например:
my_utf32string str = get_utf8_line(«path»);//my_utf32string может строить utf32 строку из utf8
//Работаем во всей программе с кодировкой utf32
write_to(std::cout, my_native_string(str));
//my_native_string умеет строить нужную строку для текущей платформы
//ну или явно указывать:
//write_to(std::cout, my_utf8string(str));

Надеюсь, идея понятна. Внутри программы работаем в одной кодировке, а всё сообщение с внешним миром должно настраиваться. Кодировки, локали, и т.д. Назовём это Input Output Layer for Application.

Вот дам строку UTF-8, ... Или перекодируй её, например, в UTF-16 BE или в UTF-32 стандартными средствами C++.

Вот, собственно, и в чем был мой вопрос.

Вроде как механизм есть.

Он уже устарел. Появился в C++11, устарел в C++17.

Стоп. Про то, что механизм есть, я говорил, ссылаясь уже на новый стандарт.

Надеюсь, идея понятна. Внутри программы работаем в одной кодировке, а всё сообщение с внешним миром должно настраиваться.

Это давно понятно. Вопрос в том, как сделать этот IO Layer средствами C++17. Универсально и переносимо.

Вот, собственно, и в чем был мой вопрос.

Не, вопрос был таков:

Интересует в первую очередь преобразование кириллицы между cp-1251 и utf-8.

Ответ короткий — велосипед.

Стоп. Про то, что механизм есть, я говорил, ссылаясь уже на новый стандарт.

codecvt_utf8 и codecvt_utf16 устарели.
Там есть еще специализации codecvt для char16_t, char32_t, wchar_t, можете поиграться.
http://ru.cppreference.com/w/cpp/locale/codecvt

А я подожду, когда это будет более хорошо сделано.

Вот дам строку UTF-8, найди в ней все цифры. Или перекодируй её, например, в UTF-16 BE или в UTF-32 стандартными средствами C++.

Не скажу, что совсем без велосипедов. И даже не скажу, что всё полностью корректно. Но этот код будет работать в 99% случаев. А если поработать напильником, то и в 100%. По крайней мере, ввод из файла строки в UTF-8 и перекодировка в UTF-32 сделан полностью стандартными, не «устаревшими», методами C++17. Жопа случилась, когда дело дошло до манипуляций со строкой типа u32string. Между прочим, стандартной.

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

using namespace std;

using codepoint_t = char32_t;
typedef basic_ifstream<codepoint_t, char_traits<codepoint_t>> u8ifstream;

locale::id codecvt<codepoint_t, char, mbstate_t>::id = 0;

wchar_t u32_to_wchar(char32_t c32) {
    if (c32 & 0xffff0000)
        throw out_of_range("codepoint exceed 16 bits");
    return (wchar_t)(c32 & 0xffff);
}

wstring u32string2wstring(const u32string& u32str) {
    size_t len = u32str.size();
    wstring wstr;
    wstr.reserve(len);
    for (char32_t c32 : u32str) {
        wstr.push_back(u32_to_wchar(c32));
    }
    return move(wstr);
}

wostream& operator <<(wostream& wos, char32_t c32) {
    if (c32 < 0x10000) {
        return wos << (wchar_t)c32;
    }
    // make surrogate pair
    c32 -= 0x10000;
    char16_t lo10 = (c32 & 0x03ff) | 0xd800;
    char16_t hi10 = ((c32 >> 10) & 0x03ff) | 0xdc00;

    return wos << (wchar_t)lo10 << (wchar_t)hi10;
}

wostream& operator <<(wostream& wos, const u32string& s) {
    for (char32_t c32 : s)
        wos << c32;
    return wos;
}

int main() {
    u8ifstream ifs;         // UTF-8 input file stream 

    ifs.open("utf8.txt");
    if (ifs) {
        try {
            locale loc_ru866("Russian_Russia.866");
            locale loc_ru1251("Russian_Russia.1251");
            locale loc_utf8(locale(), new codecvt<codepoint_t, char, mbstate_t>);

            wcout.imbue(loc_ru866);     // prepare wcout for output to Windows console (cp866)
            ifs.imbue(loc_utf8);        // prepare stream for input UTF-8

            u32string u32str;

            getline(ifs, u32str);       // get data
            ifs.close();

            // test: some comparisons
            int digit_cnt = 0, alpha_cnt = 0, blank_cnt = 0;
            for (char32_t c : u32str) {
                wcout << L"[" << c << L"]";
                if (iswdigit(u32_to_wchar(c))) {
                    ++digit_cnt;
                    wcout << L" - is digit";
                }
                if (iswalpha(u32_to_wchar(c))) {
                    ++alpha_cnt;
                    wcout << L" - is alpha";
                }
                if (iswblank(u32_to_wchar(c))) {
                    ++blank_cnt;
                    wcout << L" - is blank";
                }
                wcout << L"\n";
            }
            wcout << L"В строке обнаружено:\n"
                L"\tцифр:     " << digit_cnt << L"\n"
                L"\tбукв:     " << alpha_cnt << L"\n"
                L"\tпробелов: " << blank_cnt << endl;


            wcout << u32str << endl;
        }
        catch (const exception& e) {
            cerr << "exception: " << e.what() << endl;
            return 1;
        }
    }
    else {
        cerr << "file not found: utf8.txt" << endl;
    }
    return 0;
}

Но этот код будет работать в 99% случаев.

1) Без локали работает? )))
2) Какие-нибудь циферки Тай Тхам поддерживает? )))

99% — это про не совсем корректное преобразование utf-32 в wchar_t в функции u32_to_wchar().

Для преобразования utf-8 в utf-32 локаль не нужна. И то, и то — юникод.

Со строками u32string, как и с символами char32_t — жопа :( Я так и не нашёл функций сопоставления (isalpha сотоварищи) для char32_t. А там как бы не все так просто, учитывая компизитные символы. Так что с циферками Тай Тхам в utf-32 обломись :(

А вот с wchar_t вроде как всё нормально, по заверениям MS. Функции isw* работают даже без локали. Но есть и версии с локалью: _isw*_l. Правда не пойму зачем там локаль, если это utf-16 (или ucs-2?). И что-то не нашел точной информации как в wchar_t от MS (который 16-битный) обстоят дела с суррогатными парами. По ходу их там нет.

По ходу их там нет.

емнип, то так оно и есть.

Со строками u32string, как и с символами char32_t — жопа

С char16_t — тоже.

Для преобразования utf-8 в utf-32 локаль не нужна.

Для преобразования и стандартная библиотека не нужна.
Конвертер utf8 <-> utf32 пишется за несколько часов с нуля, даже если до этого с utf8 никогда не работал (личный опыт это доказывает).

А вот с wchar_t вроде как всё нормально, по заверениям MS

Если только под windows, то зачем стандартная библиотека? Там есть всякие MultiByteToWideChar для преобразований. Под линуксом, например, wchar_t — это utf32, так что там работать будет иначе, и никаких суррогатных пар. Вся соль пользования стандартными средствами — кроссплатформа и гарантия работы. Пока с этим жопа, я предпочитаю иные средства. ICU, например. :)

Для преобразования и стандартная библиотека не нужна. Конвертер utf8 <-> utf32 пишется за несколько часов с нуля

Согласен. Но это уже свой велосипед, а разговор шел про стандартизированные средства.

Под линуксом, например...

Под линуксом тоже не всё так гладко, как хотелось бы. Но получше, чем в винде. Кстати, (1) преобразование utf8 <-> utf32 из STL будет работать (кроссплатформа и гарантия работы) и (2) интересно, циферки Тай Тхам в utf32 будут определяться?

Меня, собственно, заинтересовал вопрос что сделали в С++17 для поддержки Юникода. Оказалось, что нормальной поддержки как не было, так и нет. Печалька.

что сделали в С++17 для поддержки Юникода

Пообещали сделать когда-нибудь. Разве этого мало? )))

циферки Тай Тхам в utf32 будут определяться?

Вряд ли. Заметьте, в любом уважающем себя фреймворке есть свои строки и функции для работы с ними, потому как в C++ ничего путного для этого нет.

А вот пример с ICU и Тай Тхам:

#include <iostream>
#include <unicode/uchar.h>

int main()
{
    UChar32 dig = L'\x1A80';//цифра 0
    UChar32 alp = L'\x1A43';//буква la
    std::cout << static_cast<bool>(u_isdigit(dig)) << std::endl;//1
    std::cout << static_cast<bool>(u_isalpha(alp)) << std::endl;//1
    std::cout << static_cast<bool>(u_isdigit(alp)) << std::endl;//0
    std::cout << static_cast<bool>(u_isalpha(dig)) << std::endl;//0
}

http://rextester.com/XFB26450

Поразительно, правда? )))

Спасибо, что уделили столько сил моей маленькой проблеме. Некоторые ответы я получил, некоторые вопросы отпали в процессе. Локали, которые работают с потоками — это классно. Никогда бы не додумался, что можно utf8 на лету конвертить в utf32. Но если не из файла? Можно как-то сделать функцию-конвертер на основе codecvt? Или это будет работать только через локаль? Я с этими фасетами-локалями пока до конца так и не разобрался :-\

Огорчило, что с юникодом все равно надо велосипедить. А, между прочим, на носу 2018-й уже.

Пример с ICU — это вообще здорово. Только Ваш код, MasterOfAlteran, на rextester компилится только под g++. Остальные плюсовые компиляторы дают ошибку. Понятно, что мой MSVC++ о ICU — ни сном, ни духом. Следовательно надо качать исходники, компилить и цеплять либу к своему проекту. Очень часто это как из пушки по воробьям. Но заметочку себе поставлю. Спасибо еще раз.

Ну так для гцц и шланга тоже надо цеплять icu, просто под linux с этим проблем нет, как и с кучей других библилиотек. :)

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

Пожалуйста, оформляйте исходный код в соответствии с правилами разметки. Для того, чтобы вставить код в комментарий, скопируйте его в текстовое поле ниже, после чего выделите то, что скопировали и нажмите кнопку «код» в панели инструментов. Иначе ваш код может принять нечитаемый вид.

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.