LINUX.ORG.RU

wstring.substr в разных реализациях

 ,


1

3

Случайно наткнулся на то, что в clang-овой реализации libc++ (по крайней мере в MacOS) при работе с русской юникодной строкой в wstring ее метод substr ничего не выдает. Меняем текст на английский во всем том же юникоде - substr работает:

#include <iostream>

int main() {
    ::setlocale(LC_ALL, "");
    std::wstring s1(L"test");
    std::wstring s2(L"тест");
    std::wcout << s1.substr(2, 1); // s
    std::wcout << s2.substr(2, 1); // <пусто>
}

Компилируем пример в gcc: выдается sс

Прочитал старый срач по поводу реализации юникода в C++ Есть ли жизнь на lua (комментарий) , но сути проблем работы substr с wstring так и не понял.

Понятно, что есть библиотека ICU, но хотелось бы понять из-за чего это может происходить?

Проблема не в substr, а в том, что у std::wcout неправильная локаль (дефолтная «C» видимо).

std::wcout.imbue(std::locale(""));

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

А вызов сишной setlocale() можешь убрать.

Deleted
()

Лучше не трать время и используй ICU, ибо этим выдающимся ублюдством с локалью в плюсах нельзя пользоваться вообще никак. std::locale() делать нельзя, поскольку оно зависит от LC_MESSAGES. У меня, например, оно C, чтобы приложения не переводились на недоязык (но это не значит что текст на недоязыке дозволено проглатывать). Имя локали явно задать тоже нельзя, потому что в разных системах оно разное.

На самом деле ICU нужен не так часто, а только когда ты действительно работаешь с текстом - разбиваешь на символы, сравниваешь без учёта регистра и т.д. В обычных программах с utf-8 легко работается как с байтами - литералы в ascii, всяким substr на вход передаются результаты .find() и т.д.

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

Спасибо, с

std::wcout.imbue(std::locale(""));

в clang все заработало. Все-таки непонятно, почему локаль должна устанавливать в коде программы, а не должна этим рулить операционная система по определению

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

А нет, вру

С clang проблема решилась, а программа, собранная в gcc теперь вываливается с ошибкой:

terminate called after throwing an instance of 'std::runtime_error' what(): locale::facet::_S_create_c_locale name not valid Abort trap: 6

widgetii
() автор топика

wstring как часть стандарта к юникоду никакого отношения не имеет.

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

Странно, на Linux работает нормально. Можно попробовать:

::setlocale(LC_ALL, "");
std::wcout.imbue(std::locale());

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

#include <locale> в программе уже был

::setlocale(LC_ALL, "");
std::wcout.imbue(std::locale());

теперь и в gcc работает.

Вот скажите, как с этим шаманством писать портируемый код, не зависящий от компилятора и ОС?

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

Вру.. Опять - gcc заработал, шланг перестал в консоль русский выводить.

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

Вот скажите, как с этим шаманством писать портируемый код, не зависящий от компилятора и ОС?

ICU

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

Ха! Это же чертовы локали. ICU правильно советуют, если ты работаешь со строками на разных языках.

А в тексте программы, в том числе в литералах, ничего кроме latin-1 не надо писать, хуже будет. Все строковые ресурсы в отдельные файлы, ведь у файлов тоже есть кодировка и литерал L"АБВГДЕ" может быть и одним и другим. И подключать какую нибудь библиотеку локализации, для С это gettext, для ++ тоже можно использовать вероятно

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

Странно, у меня в линуксе с gcc работает без ошибок, но при этом выводит '?' вместо кириллицы. Похоже под разными ОС и с разными компиляторами это по разному работает.

Вот такой код работает одинаково под macOS + clang и Linux + gcc:

#include <iostream>

int main() {
    std::ios_base::sync_with_stdio(false);
    std::wcout.imbue(std::locale(""));

    std::wstring s1(L"test");
    std::wstring s2(L"тест");
    std::wcout << s1.substr(2, 1) << std::endl; // s
    std::wcout << s2.substr(2, 1) << std::endl; // <пусто>
}

Ссылки:

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

как с этим шаманством писать портируемый код, не зависящий от компилятора и ОС?

Никак, wchar_t по определению привязан к платформе.

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

не знаю, я пишу CMakefileList.txt - и проблем не бывает обычно

И не будет, т.к. cmake ничего не знает о таком имени файла.

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

Не не путаю, просто не обратил внимание что исключени, я что то решил, что ошибка компиляции

Silerus ★★★★
()
Последнее исправление: Silerus (всего исправлений: 1)
12 апреля 2019 г.

В Unicode UTF-8 символы могут иметь разную длинну:

Английские символы в юникод занимают 1 байт и имеют обратную совместимость, со старой 7-битной английской кодировкой, и с основными на ней региональными 8-битными кодировками. Например в cp-1251 (кириллицей windows), английские буквы имеют теже коды, что и в старой 7-биной кодировке, а значит и в юникод.

А вот русские символы в unicode занимают в памяти 2 байта (как и все регионально-зависимые сиволы): один байт для кода таблицы с русскими символами и ещё один код самого символа.

Когда вы пишите: s2.substr(2, 1); вы берёте третий БАЙТ строки. для «test» это символ «s», а для «тест» это половина символа «е», которая не может быть выведена на экран, поэтому - пусто.

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

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

А в тексте программы, в том числе в литералах, ничего кроме latin-1

Т.е. называть переменную, например, aéroport — норм?

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.