LINUX.ORG.RU

c++ и utf-8

 , , , ,


6

6

Допустим есть файл сохранённый в UTF8. Читать я его могу исключительно std::ifstream (без std::wifstream).

После прочтения файла, я хочу иметь возможность итерироваться по utf-8 символам, и даже сравнивать их

for (size_t i = 0; i < utf8String.size(); i++) {
    if (utf8String[i] == 'ф') {
        //...
    }
}

Т.е. я хочу чтобы была возможность работать с каждым utf-8 символом, в независимости от того какой он длинны как с одним символом.

Что мне для этого нужно?

Я думал что wchar_t. Но есть такие два источника: 1. https://ru.wikipedia.org/wiki/Широкий_символ 2. https://stackoverflow.com/questions/17871880/should-i-use-wchar-t-when-using-...

Которые вроде как говорят что это не очень хорошо.

Еще я заметил (да на ++ уже пишу и не первый год), но заметил только сейчас, т.к.раньше просто не думал про это ибо не было нужно:

что std::wstring wstr = L"добро"; //да, система дебиан 8, x86-64, исходник сохранен в utf8.

в общем wstr==«добро» в оперативной памяти будет представлено не в utf-8 а в неведомой кодировке. Но по которой можно итерироваться и сравнивать.

Буква 'д' из этой строки будет иметь следующий байт-код 00110100 00000100 00000000 00000000 (wchar_t)

При этом если бы 'д' была в utf8 то она должна была бы иметь такие байты 11010000 10110100 {00000000 00000000} - в скобках хвост который как бы не имеет отношение к коду 'д', но заполняет тип wchar_t.

Вопросы: Можно ли как-то сделать так чтобы компилятор (g++) видя wchar_t c = L'д' или whchar_t c = 'д' - конструировал utf8 букву в wchar_t типе, а не в непонятной кодировке. И кстати в какой кодировке он её кодирует по умолчанию?

Есть ли какой-то способ прочитать преобразовать utf-8 строку, хранимую в std::string в std::wstring так чтобы после такого преобразования содержимое этой wstring можно было бы корректно вывести в std::wcout?

Я нашел такой способ Преобразование std::string в std::wstring

Но во первых он какой-то страншный и не очень понятный, какие-то шаблоны актуализируются...

А самое главное в моём debian8 и gcc4.3 нет такого хедера include <codecvt>

Поэтому у кого поновее пакетная база, и кому не лень, пожалуйста из ссылки выше запустите пример. Сделайте std::string str(«добро») и преобразуйте её в std::wstring Нужно чтобы после преобразования wstring строка корректно выводилась в std::wcout и можно было побуквенно (а не побайтово) итерироваться по ней, и посимвольно сравнивать.

В общем, т.к. у меня нет этого codecvt я сделал преобразование руками (да можно красивее, переносимее (учитывать порядок байт), при вызове из main не выходить за границы строки и т.д. и т.п. - не суть): https://pastebin.com/4E3nuNcM

и вот если приблизительно таким методом конструировать std::wstring которая содержит utf-8 можно будет итерироваться по ней (да, я знаю что utf8 может быть длиннее чем размер wchar_t, но у меня будет набор латиницы и кириллицы из utf8 документа), можно будет сравнивать с символами, но к сожалению не так wstr[0] = 'ы' а только с заранее созданными символами, подобным методом как строка создавалась. wstr[0] == wcharSymbol.

Это не удобно, а еще такая строка не может корректно выводится на std::wcout.

Т.е. заключительный вопрос - можно ли как-то пользуя нативный wchar_t работать посимвольно с utf-8 в c++ в линукс, имея полный ф-л такой как итерации, посимвольное сравнение, корректный вывод в std::wcout.

Если нельзя - есть ли возможно какие-то сторонние (причем легковесные библиотеки, код которых можно включить в проект, и собрать статически, и чтобы места не много тратили) которые предоставляют некий тип wideChar, полностью совместимый и с std::wcout и со всей stl(конейнерами, алгоритмами) и при этом желательно кросплатформенная?

Кстати в презренной винде на этом же наборе символов utf8 (латиница и кириллица) - всё очень хорошо (плохо там будет когда потребуется символ длиннее 2 байт) - т.к. там wchar_t это 2 байта, т.е. он как раз отлично соотвествует, и в wcout тоже выводится :)

Просьба не флудить а по конкретике писать :)

★★★★★

Последнее исправление: bonta (всего исправлений: 3)

Из коробки - нет. Видел очень легковесные реализации итерации по utf8(получение длинны строки в символах, обход посимвольно, поиск) от всяких учёных. Но сходу нагуглить сейчас почему-то не удалось. В любом случае, это либо полные реализации типа icu, либо решения каких то подзадач, типа определения длинны строки в видимых символах.

Кстати, в xercesc вроде была вменяемая реализация строк(вот только не помню, своя или через icu), если вдруг кто-то ещё использует xml в 2018ом...

Вся прелесть uft-8 в том, что пока нетерминалы лежат в подмножестве ascii, можно парсить и передавать в виде дерева обьектов всякие json'ы на уровень выше, в надежде, что уровень выше, таки умеет работать с юникодом.

pon4ik ★★★★★
()

Вся проблема с utf-8 в том, что люди привыкли работать с текстом как с чем-то особенным, а не просто массивом объектов. Юзай итераторы и все будет окей.

anonymous
()
Ответ на: комментарий от MKuznetsov

Кстати тут, на ЛОР, кто-то уже делал довольно толковую и быструю библиотеку с такими-же целями - просто поспрашайте.

Может речь про http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
Я пользуюсь этой реализацией. Доволен и скоростью, и удобством.

andreyu ★★★★★
()

О, отличная тема, подписался.

Пы Сы Вот поэтому-то некоторые сразу предпочитают брать QtCore с QString и кодеками из коробки.

hobbit ★★★★★
()

Юникод не w_char. Утф8 например однобайтный char в норме. Короче читай это http://userguide.icu-project.org/strings к чему столько слов вообще. С wchar_t нельзя работать с юникодом, это говно. Поэтому в винде не было нормального юникода.

anonymous
()
Ответ на: комментарий от hobbit

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

anonymous
()
Ответ на: комментарий от hobbit

Угу. Так я и полюбил Qt в программах без GUI. ICU ещё жирнее, чем QtCore. А все его фичи нужны далеко не всегда.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)

В общем L"что-то-там" мой компилятор на x86-64 преобразует в utf-16 кодировку.

Нашел очень легковесную либу, для работы которой нужно всего один хедер подключить (а сама либа в 4 файлах всего идет) http://utfcpp.sourceforge.net

с её помощью я получил то что хотел. Преобразование выглядит так:

wstring toUtf8(const string &str) {
    std::wstring ret;
    vector<wchar_t> utf16;
    utf8::utf8to16(str.begin(), str.end(), back_inserter(utf16));
    ret = std::wstring(utf16.begin(), utf16.end());
    /*if (ret == L"школа") {
        std::wcout << ret << std::endl;
        std::wcout
                << ByteMap( (char*)(&ret.front()), ret.size()*sizeof(wchar_t)) << std::endl;
        std::wstring _nativeWstr = L"школа";
        std::wcout
                << ByteMap( (char*)(&_nativeWstr.front()), _nativeWstr.size()*sizeof(wchar_t)) << std::endl;

    }*/
    return ret;
}

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

А еще похоже это и кросплатформенно. В общем всего в пару строк и высокоуровнево, и либа по коду очень короткая и простая.

bonta ★★★★★
() автор топика
Последнее исправление: bonta (всего исправлений: 1)
Ответ на: комментарий от anonymous

А что нужно вне винды? Однобайтовые строки и iconv при необходимости?

Вот мне как-то городить платформозависимые костыли в элементарных вещах неохота.

hobbit ★★★★★
()
Ответ на: комментарий от bonta

А зачем использовать какие-то левые либы? Вы в курсе, что в стандартной библиотеке уже всё есть. Из справочника

#include <iostream>
#include <vector>
#include <clocale>
#include <cwchar>
 
void print_as_wide(const char* mbstr)
{
    std::mbstate_t state = std::mbstate_t();
    std::size_t len = 1 + std::mbsrtowcs(NULL, &mbstr, 0, &state);
    std::vector<wchar_t> wstr(len);
    std::mbsrtowcs(&wstr[0], &mbstr, wstr.size(), &state);
    std::wcout << "Wide string: " << &wstr[0] << '\n'
               << "The length, including '\\0': " << wstr.size() << '\n';
}
 
int main()
{
    std::setlocale(LC_ALL, "en_US.utf8");
    const char* mbstr = u8"z\u00df\u6c34\U0001f34c"; // or u8"zß水🍌"
    print_as_wide(mbstr);
}
А кодировка wchar_t вообще не должна волновать, он обязан быть достаточно большим.

pavlick ★★
()
Ответ на: комментарий от hobbit

UTF-8 везде подходит, когда нужно что-то в другой кодировке конвертишь соответственно.

anonymous
()
Ответ на: комментарий от pavlick

он обязан быть достаточно большим

Не обязан. Его размер, кроме как от желания левой пятки разработчика компилятора, ни от чего не зависит.

SkyMaverick ★★★★★
()
Ответ на: комментарий от pavlick

Мне кажется этот пример из справочника который вы привели - ужасен, си (нулы, взятие адресов) и си++ (cout) в одном коде. Хорошо когда или ++ или Си, но не все в перемешку.

Да и в добавок какие-то странные типы, я как-то не хочу юзать код который не понимаю :) а чтобы понять нужно лезть в справочник.

Код от найденой либы явно красивее и проще пользовать. За счет выского уровня он понятен без манов.

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

Классная штука.

bonta ★★★★★
() автор топика
Ответ на: комментарий от peregrine

ну setlocale() можно на sync_with_stdio(), заменить. А по остальному спорно. Я вот как посмотрю на какой-нибудь std::chrono или std::filesystem, так вздрагиваю, столько всяких сущностей напридумывали для простых вроде бы манипуляций. А с другой стороны - как лелко работать с opendir()/readdir(). Ну понятно, что для абстрагирования от деталей, но всё же - в меру должна быть вся эта мишура.

std::fs::directory_iterator::directory_entry::path.c_str()
красиво, элегантно, лаконично )). А когда через chrono пытался в первый раз время узнать - изматерился весь.

pavlick ★★
()

в общем wstr==«добро» в оперативной памяти будет представлено не в utf-8 а в неведомой кодировке. Но по которой можно итерироваться и сравнивать.

Буква 'д' из этой строки будет иметь следующий байт-код 00110100 00000100 00000000 00000000 (wchar_t)

Эта «неведомая» кодировка называется utf-32. Это единственная кодировка unicode, в которой все символы имеют одинаковую ширину и возможен индексный доступ.

У остальных (utf-8, utf-16) ширина символа варьируется, и возможен только последовательный (итеративный) доступ.

Проще всего в памяти хранить именно ее, а преобразовывать в/из utf-8 при вводе и выводе.

German_1984 ★★
()
Ответ на: комментарий от pavlick

Тут нет работы с файлами. Да и почему не использовать для этих целей

#include <fstream>
А в wchar_t юникод по стандарту влезать не обязан. Начиная с C++11 (7 лет назад вышел, уже новорожденные дети в первый класс пошли с тех пор), есть char32_t куда он обязан влезать. И пользоваться им не сложнее. Ну а лаконичность крестов это отдельная тема.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 2)

Конвертируй внутри в utf32.

invy ★★★★★
()
Ответ на: комментарий от peregrine

кресты могут быть лаконичными, достаточно ввести дополнительный слой абстракции, который обеспечит эту лаконичность (ценою производительности, конечно)

anonymous
()
Ответ на: комментарий от hobbit

wchar_t это и есть платформозависимый костыль.

anonymous
()
Ответ на: комментарий от German_1984

Эта «неведомая» кодировка называется utf-32. Это единственная кодировка unicode, в которой все символы имеют одинаковую ширину и возможен индексный доступ.

Символы - нет, только codepoint-ы

anonymous
()
Ответ на: комментарий от SkyMaverick

Не обязан. Его размер, кроме как от желания левой пятки разработчика компилятора, ни от чего не зависит.

Размер wchar_t должен быть совместим с поддерживаемыми локалями:

Type wchar_­t is a distinct type whose values can represent distinct codes for all members of the largest extended character set specified among the supported locales.

http://eel.is/c draft/basic.fundamental#5.sentence-1

Если у тебя в locale можно задавать UTF-16, то (один) wchar_t должен вмещать любой «символ» из UTF-16. high и low-surrogate не являются «символами» в UTF-16, только вся пара является «символом». Тогда wchar_t обязан быть как минимум 21 бит длиной.

Это то место, где MSVC нарушает стандарт.

anonymous
()
Ответ на: комментарий от anonymous

Символы - нет, только codepoint-ы

Для большинства практических нужд нормализованного юникода вполне достаточно (все общеупотребительные символы со всякими умляутами доступны как базовые, то есть состоящие из одного codepoint'а). Так что можно просто запретить в своей программе обработку нормализованного юникода с композитными символами, а после чтения строки извне всегда её нормализовывать. Тогда юникод в UTF-32 всегда будет иметь 4 байта на символ.

anonymous
()
Ответ на: комментарий от German_1984

http://icu-project.org/docs/papers/unicode_wchar_t.html

Вообще, utf32 слишком жручая. Я не вижу причин не использовать uchar c utf8, отличная экономия памяти. При условии, что uft8 в системной локали, работать с ней одно удовольствие. А символы всё равно только угадывать даже с uchar32. Как часто тебе необходим индексный доступ? С юникодом ты просто запоминаешь позицию нужных кодпоинтов и вычитываешь их по мере необходимости, достаточно запомнить что там вполне может быть несколько байт в символе.

anonymous
()
Ответ на: комментарий от anonymous

Что значит запретить обработку? Зачем тогда нужен юникод?

anonymous
()
Ответ на: комментарий от anonymous

Кстати интересно что делать с модифицирующими кодпоинтами, просто считать их отдельным символом?

anonymous
()
Ответ на: комментарий от anonymous

Что значит запретить обработку?

Тут выбор за автором: падать, сообщать об ошибке, заменять композитный символ на какой-нибудь другой (если по каким-то причинам обрабатывать строку всё же надо — например, отображать строку для просмотра человеком). В качестве костыля можно воспользоваться тем, что размер codepoint'а в текущем стандарте всего 21 бит (и вряд ли сильно возрастёт в ближайшие 100 лет), поэтому можно приватно кодировать композитные символы у себя в программе 32-битными codepoint'ами с установленным старшим битом как смещение в некоторой внутренней таблице символов. Символы в таблицу заносить по мере поступления. Тут надо понимать, что я не защищаю необходимость работать с символами фиксированной длины. Но если кому-то надо, то возможность такая есть.

Кстати интересно что делать с модифицирующими кодпоинтами,просто считать их отдельным символом?

По-хорошему запретить (то есть отказываться обрабатывать строки в которых после нормализации остаются эти самые модифицирующие кодпойнты). Сами по себе они особо не нужны, но приводят к забавному эффекту, когда при конкатенации нормализованных строк результат не является нормализованным, если в начале второй строки стоит кодпойнт-модификатор (там он считается отдельным символом).

Зачем тогда нужен юникод?

Вопрос риторический. Юникод представляет собой комитетскую разработку в самом худшем смысле этого слова. Они отказались от простой модели: строка есть набор символов из некоторого конечного (и не слишком большого) множества и вместо этого изобрели ущербную типографскую систему, где можно делать композитные символы из базовых. При том, что возможности этой системы весьма слабые: если людям надо типографский продукт, то они не пользуются текстовыми файлами, а берут офис, TeX и т.п. А там нет проблемы сделать какого-нибудь символа-уродца средствами самой системы.

Как следствие, слишком усложнённый стандарт приводит к куче программ, которые с ним не полностью совместимы и эта ситуация вряд ли исправится, поскольку стандарты ради стандартов никому не интересны кроме пуристов.

Вообщем, ситуация с юникодом весьма показательна: стандарту уже почти 30 лет, а люди до сих пор перетирают, как его правильно использовать.

Ну и частичное соответствие стандарту не является, вообщем-то, чем-то необычным: нет ни одного дистибутива Linux сертифицированного на соответствие Single Unix Specification. Если начать копать, то наверняка найдутся хотя бы мелкие несоответствия. Что, вообщем, не мешает этими самыми дистибутивами пользоваться.

anonymous
()
Ответ на: комментарий от anonymous

Вообщем, ситуация с юникодом весьма показательна: стандарту уже почти 30 лет, а люди до сих пор перетирают, как его правильно использовать.

С C++ такая же история.

anonymous
()

Неведомая кодировка это UTF-32. Перекодировать из UTF-8 в UTF-32 очень просто, это несколько десятков строк. Почитай статью на википедии, этого должно хватить.

Legioner ★★★★★
()
Ответ на: комментарий от anonymous

Вообщем, ситуация с юникодом весьма показательна: стандарту уже почти 30 лет, а люди до сих пор перетирают, как его правильно использовать.

С C++ такая же история.

Но ведь стандарты С++ штампуют каждый год. И современный С++ не похож на С++ 30-летней давности. А перетирают в основном неосиляторы. Лично я вообще детей хочу от С++. Лучше языка нет и не будет (в обозримом будущем).

anonymous
()
Ответ на: комментарий от Legioner

спасибо, я подумал что utf16 изначально, но да это 32. Все верно

bonta ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Но ведь стандарты С++ штампуют каждый год.

Хрюникод сейчас тоже не 30-летней давности.

anonymous
()
Ответ на: комментарий от peregrine

ехал auto через auto? или ты о старых крестах, где тип итератора иногда не вмещается в 80 символов (включая отступы)?

кресты предоставляют огромную свободу для разработчика, в этом их преимущество и беда. можно писать код в абсолютно любом стиле и парадигме, обмазываться паттернами или наоборот играться в лоу-левел императивщину. это отличный инструмент в умелых руках. а то, что существует огромная кодовая база неподдерживаемого уродливого говна - это не вина языка... карандашом, например, тоже можно нарисовать непонятную фигню, но никому в голову не придёт ругать за это карандаш.

anonymous
()
Ответ на: комментарий от anonymous

Не, я про то что в почти любом другом ЯП в библиотеке юникодная строка будет называться либо string либо ustring. В C++ это практически гарантированно будет UnicodeString, если авторы не придумают UnicodeUTF32String. Ну и всякое стандартное говно, вроде

std::fs::directory_iterator::directory_entry::path.c_str()
Которое уже приводили выше.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от peregrine

Не, я про то что в почти любом другом ЯП в библиотеке юникодная строка будет называться либо string либо ustring. В C++ это практически гарантированно будет UnicodeString, если авторы не придумают UnicodeUTF32String.

Есть такое. Это объясняется возрастом языка и желанием сохранять обратную совместимость, т.к. кода написано много и ломать его ради красивостей никто не станет. Понятное дело, что у новых языков такой проблемы нет и там сразу можно назвать все красиво, правда лет через n-дцать он либо станет похож на С++, либо будет как питон, где об обратной совместимости речи не идет.

anonymous
()
Ответ на: комментарий от anonymous

Так неймспейсы придумали как раз чтобы всё было красиво, а их юзкейс стал несколько другим из-за разрешения на

using namespace
Хотя бы для стандартной библиотеки запретили бы.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.