Проблема с преобразованием std::filesystem::path в std::string в C++

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

#include <filesystem>

int main()
{
    const char* dir = "D:\\Music";
    std::vector<std::string> musicList;

    for (const auto& entry : std::filesystem::recursive_directory_iterator(dir))
    {
        if (entry.is_regular_file())
        {
            musicList.emplace_back(entry.path().string());
        }
    }
}

Проблема возникает в entry.path().string() при обработке строк типа L"D:\\Music\\suki\\Angel Note - 月明かりは優しく・・・.mp3". Программа завершается с ошибкой, указывающей на:

_STD_BEGIN
// We would really love to use the proper way of building error_code by specializing
// is_error_code_enum and make_error_code for __std_win_error, but because:
//   1. We would like to keep the definition of __std_win_error in xfilesystem_abi.h
//   2. and xfilesystem_abi.h cannot include <system_error>
//   3. and specialization of is_error_code_enum and overload of make_error_code
//      need to be kept together with the enum (see limerick in N4950 [temp.expl.spec]/8)
// we resort to using this _Make_ec helper.
_NODISCARD inline error_code _Make_ec(__std_win_error _Errno) noexcept { // make an error_code
    return { static_cast<int>(_Errno), _STD system_category() };
}

[[noreturn]] inline void _Throw_system_error_from_std_win_error(const __std_win_error _Errno) {
    _THROW(system_error{ _Make_ec(_Errno) });  // Here occur error!
}
_STD_END

Я скомпилировал код в Visual Studio 2022, стандарт C++ — C++17.

После расследования я упростил проблему:

#include <filesystem>

int main()
{
    std::filesystem::path path = L"・";
    auto str = path.string();
}

Аналогичные проблемы возникли в path.string(). После дальнейшего упрощения с помощью L"\u30FB" я обнаружил, что символ представлен как "\u30FB".

Хотя path.wstring(), path.u8string() и другие преобразования строк работают хорошо, мне нужен char* для таких API, как ImGui::Text(str) или API FMOD. Попытки преобразовать wstring в string с помощью codecvt, Win32 API или ICU приводили к искаженному тексту, например "・":

#include <filesystem>
#include <Windows.h>

std::string ws2s(const std::wstring& wstr)
{
    int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
    std::string str;
    str.reserve(len);
    WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, str.data(), len, nullptr, nullptr);
    return str;
}

int main()
{
    std::filesystem::path path = L"\u30FB";
    auto str = ws2s(path.wstring());
}

В результате строка была "・" вместо "\u30FB".

Есть ли надежный способ эффективно справиться с этой ситуацией?


Хорошо, я нашел проблему. Это кодировка, используемая в интерфейсе отладки VS. Он неправильно отображает UTF-8. Например, содержимое моего vector<string> имеет формат UTF-8, но интерфейс отладки показывает искаженный текст, как и содержимое vec[0]. Все, что мне нужно сделать, это добавить ,s8 к vec[0] в окне просмотра. Это заставляет дисплей отладки правильно отображать содержимое UTF-8.
О, Microsoft, почему ты настаиваешь на UTF-16? Разве UTF-8 недостаточно хорош?

Какая кодировка ожидается ImGui::Text? Поддерживает ли он вообще символы, отличные от ascii? Если он ожидает utf-8, почему бы не просто использовать path.u8string()?

Alan Birtles 15.07.2024 15:48

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

Botje 15.07.2024 15:58

Когда вы проверяете std::string, который, по вашему мнению, содержит UTF-8, не проверяйте его, распечатывая, это часто печатается неправильно, особенно в Windows. Вместо этого посмотрите на фактические байты (скажем, в отладчике) и посмотрите, являются ли они ожидаемой кодировкой UTF-8.

john 15.07.2024 16:40

Вы говорите, что текст «искажен», но это именно то, что я ожидал увидеть для представления символа, отличного от ASCII, в UTF-8, отображаемого на стандартной кодовой странице Windows.

Craig 15.07.2024 20:16

«Мне нужен char* для таких API, как ImGui::Text(str)» — вы можете использовать u8string().c_str(), чтобы получить кодировку UTF-8 char*. Кстати, у вашей функции ws2s() есть утечка памяти. Вам необходимо delete[] массив char[], который вы выделяете, прежде чем return использовать std::string. Лучше всего вообще не выделять char[], вместо этого вы можете заранее определить размер std::string, а затем преобразовать его непосредственно в его буфер памяти, используя неконстантный std::string::data() метод.

Remy Lebeau 15.07.2024 22:48

@Алан Бертлз std::filesystem::path path(L"\u30FB"); авто u8str = path.u8string(); ImGui::Text(u8str.c_str()); Я получаю искаженные символы в u8str и отображаю '?' в ИмГуи. Возможно, мне нужно включить соответствующий ttf в ImGui::IO, но u8str по-прежнему показывает искаженные символы (в отладчике).

guruguru 16.07.2024 14:40

@john О, вообще-то во время отладки я обнаружил, что результат path.string() искажен

guruguru 16.07.2024 14:43

@Remy Lebeau Да, u8string работает хорошо, но проблема в том, как мне преобразовать char8_t* в char* для использования в API? О, спасибо, что напомнили мне о функции ws2s. На самом деле это было создано ChatGPT, и я не присматривался. Это действительно недостаточно строго, хаха.

guruguru 16.07.2024 14:50

@guruguru достаточно простого приведения типов от char8_t* к char*.

Remy Lebeau 16.07.2024 16:42

@Remy Lebeau Действительно, я пробовал const char8_t* ch8 = u8"\u1234"; символ* ch = (char*)ch8; и это сработало хорошо. std::u8string u8str = u8"\u1234"; std::string str = (char*)u8str.c_str(); тоже нормально работало вот так.

guruguru 16.07.2024 16:55

@guruguru, вам следует использовать reinterpret_cast вместо приведения C-style (тем более, что вы отбрасываете константность!), например: const char* ch = reinterpret_cast<const char*>(ch8);. Но в любом случае в последнем случае я бы использовал либо std::string str(reinterpret_cast<const char*>(u8str.c_str()), u8str.size());, либо std::string str(u8str.begin(), u8str.end());. Хотя на самом деле вам вообще не нужен std::string: std::u8string u8str = u8"\u1234"; const char* ch = reinterpret_cast<const char*>(u8str.c_str());

Remy Lebeau 16.07.2024 19:07

@Remy Лебо Действительно, это правда. Но могу ли я использовать const char* ch = std::bit_cast<const char*>(ch8);?

guruguru 17.07.2024 13:57

@guruguru У меня нет опыта работы с bit_cast, поэтому я не могу на этот вопрос ответить.

Remy Lebeau 17.07.2024 18:22
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
13
130
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Программа аварийно завершает работу, поскольку std::filesystem::path::string выдает исключение, а ваш код его не перехватывает. Это проблема с кодировкой. Добавьте это в начало вашей программы, и проблема должна быть решена:

static constexpr char localeName[] = "ja_JP.utf-8";

// Instruct the C standard library that Japanese will be used with UTF-8 encoding
std::setlocale(LC_ALL, localeName);

// Instruct the C++ standard library that Japanese will be used with UTF-8 encoding, for example in std::string, std::ostream
std::locale::global(std::locale(localeName));

// Use the system locale (language and encoding) when printing data to std::cout
// Note that if your system is using a different encoding than UTF-8, like CP932, the C++ standard library will implicitly do a conversion.
std::cout.imbue(std::locale{""});

У меня была аналогичная проблема с boost::filesystem::path, и это решило проблему.

Обратите внимание, что часть кодирования является наиболее важной. В MSVC это также должно решить проблему:

static constexpr char localeName[] = ".utf-8";

Вот полная демонстрация с этим кодом:

#include <iostream>
#include <filesystem>
#include <locale>

#define LOG(x) std::cout << #x " = " << x << '\n'

int main()
{
    std::locale::global(std::locale{".utf-8"});
    // use system encoding - language neutral
    std::locale sysLoc{std::locale{"C"}, "", std::locale::ctype};
    std::cout.imbue(sysLoc);
    std::cerr.imbue(sysLoc);
    
    for (const auto& dir_en : std::filesystem::directory_iterator{"."})
    {
        LOG(dir_en);
        LOG(dir_en.path());
        LOG(dir_en.path().string());
        std::cout << "---------------\n";
    }
}

Я получил следующие результаты:

C:\Users\marekR22\Downloads\MyDir>dir
 Volume in drive C has no label.
 Volume Serial Number is 5608-EF1A

 Directory of C:\Users\marekR22\Downloads\MyDir

07/16/2024  03:58 PM    <DIR>          .
07/16/2024  03:58 PM    <DIR>          ..
07/16/2024  03:56 PM                47 Angel Note - 月明かりは優しく・・・.txt
07/16/2024  03:55 PM               526 main.cpp
               2 File(s)            573 bytes
               2 Dir(s)   7,074,545,664 bytes free

C:\Users\marekR22\Downloads\MyDir>cl /std:c++20 /EHcs /O2 /D NDEBUG /utf-8 main.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.39.33523 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

main.cpp
Microsoft (R) Incremental Linker Version 14.39.33523.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj

C:\Users\marekR22\Downloads\MyDir>chcp
Active code page: 852

C:\Users\marekR22\Downloads\MyDir>main.exe
dir_en = ".\\Angel Note - ????????.txt"
dir_en.path() = ".\\Angel Note - ????????.txt"
dir_en.path().string() = .\Angel Note - ????????.txt
---------------
dir_en = ".\\main.cpp"
dir_en.path() = ".\\main.cpp"
dir_en.path().string() = .\main.cpp
---------------
dir_en = ".\\main.exe"
dir_en.path() = ".\\main.exe"
dir_en.path().string() = .\main.exe
---------------
dir_en = ".\\main.obj"
dir_en.path() = ".\\main.obj"
dir_en.path().string() = .\main.obj
---------------

C:\Users\marekR22\Downloads\MyDir>chcp 65001
Active code page: 65001

C:\Users\marekR22\Downloads\MyDir>main.exe
dir_en = ".\\Angel Note - 月明かりは優しく・・・.txt"
dir_en.path() = ".\\Angel Note - 月明かりは優しく・・・.txt"
dir_en.path().string() = .\Angel Note - 月明かりは優しく・・・.txt
---------------
dir_en = ".\\main.cpp"
dir_en.path() = ".\\main.cpp"
dir_en.path().string() = .\main.cpp
---------------
dir_en = ".\\main.exe"
dir_en.path() = ".\\main.exe"
dir_en.path().string() = .\main.exe
---------------
dir_en = ".\\main.obj"
dir_en.path() = ".\\main.obj"
dir_en.path().string() = .\main.obj
---------------

C:\Users\marekR22\Downloads\MyDir>chcp 932
Active code page: 932

C:\Users\marekR22\Downloads\MyDir>main.exe
dir_en = ".\\Angel Note - 月明かりは優しく・・・.txt"
dir_en.path() = ".\\Angel Note - 月明かりは優しく・・・.txt"
dir_en.path().string() = .\Angel Note - 月明かりは優しく・・・.txt
---------------
dir_en = ".\\main.cpp"
dir_en.path() = ".\\main.cpp"
dir_en.path().string() = .\main.cpp
---------------
dir_en = ".\\main.exe"
dir_en.path() = ".\\main.exe"
dir_en.path().string() = .\main.exe
---------------
dir_en = ".\\main.obj"
dir_en.path() = ".\\main.obj"
dir_en.path().string() = .\main.obj
---------------

Обратите внимание: когда моя кодовая страница не поддерживает японские символы, печатается ? (без сбоев). После того, как я изменил кодовую страницу на 65001 (которая представляет UTF-8), печатаются правильные японские символы. Он также отлично работает, когда используется японская кодовая страница 932.

Спасибо за ваш ответ. Я попробовал ваш код, и он работает очень хорошо, не вызывая никаких исключений. Однако есть еще небольшая проблема. Когда я проверяю строку, преобразованную из path.string(), в отладчике, я обнаруживаю, что она все еще искажена, хотя символы в пути отображаются правильно.

guruguru 16.07.2024 14:29

Кроме того, у меня есть еще один вопрос. Я использовал японские символы, поэтому мне нужно установить ja_JP.utf-8. Что, если строка содержит несколько языков, например корейский или другие? Кроме того, некоторые имена файлов могут содержать символы, отличные от UTF-8, например, закодированные в GBK. Как мне с этим справиться? Как правильно обработать текстовый набор, содержащий несколько кодировок символов?

guruguru 16.07.2024 14:30

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

Marek R 16.07.2024 14:43

В Windows с MSVC static constexpr char localeName[] = ".utf-8"; будет работать, но в системах POSIX этот локаль не будет правильно проанализирован (к сожалению, существует разница в названии локали между платформами).

Marek R 16.07.2024 14:46

Этот подход может решить проблему только кроссплатформенности, но в настоящее время меня не беспокоят платформы, отличные от Windows. Возможно, я мог бы использовать Win32 для решения этой проблемы, но я не знаком с Win32, и способ, которым C++ решает эту проблему, прост. На данный момент небольшая проблема заключается в том, что path.string() приводит к искажению текста в среде отладки, хотя я еще не пробовал выводить его результаты, поэтому не уверен, окажет ли это какое-либо влияние.

guruguru 16.07.2024 15:01

На всякий случай я упомянул другие платформы. Просто попробуйте, я уверен, что это сработает.

Marek R 16.07.2024 15:07

Все в порядке. попробую сделать.

guruguru 16.07.2024 15:13

@guruguru смотрите обновление ответа. Я запускаю этот код на своей машине.

Marek R 16.07.2024 16:07

О, я получил тот же результат на своем компьютере. Он справляется вполне хорошо. На самом деле это просто проблема с кодировкой консоли. Установка UTF-8 обеспечивает правильное отображение, а использование setlocale предотвращает исключения во время выполнения. Я попробовал применить ваше решение с помощью API FMOD, который передал искаженный текст (я проверил значения в отладчике), но он все равно работал нормально (воспроизводя музыку). Я подозреваю, что это может быть проблема с кодировкой на странице отладки VS, вызывающая искаженное отображение содержимого отладки, тогда как использование wstring отображает его правильно. И на самом деле лучше использовать UTF-16 для передачи параметров в Win API.

guruguru 16.07.2024 16:31

Другие вопросы по теме