Изменить кодировку узкой строки или отсутствовать std::filesystem::path::imbue

У меня Windows, и я создаю std::filesystem::path из std::string. Согласно ссылке на конструктор (акцент мой):

Если тип исходного символа — char, предполагается, что исходная кодировка является собственной узкой кодировкой (поэтому преобразование в системах POSIX не выполняется).

Если я правильно понимаю, это означает, что строковое содержимое будет рассматриваться как закодированное в ANSI под Windows. Чтобы обработать его как закодированный в UTF-8, мне нужно использовать функцию std::filesystem::u8path(). См. демонстрацию: http://rextester.com/PXRH65151

Я хочу, чтобы конструктор path обрабатывал содержимое узкой строки в кодировке UTF-8. Для boost::filesystem::path я мог бы использовать метод imbue() для этого:

boost::filesystem::path::imbue(std::locale(std::locale(), new std::codecvt_utf8_utf16<wchar_t>()));

Однако я не вижу такого метода в std::filesystem::path. Есть ли способ добиться такого поведения для std::filesystem::path? Или мне нужно плевать u8path во все стороны?


person Mikhail    schedule 13.03.2017    source источник
comment
Почему вы используете строки ANSI в ОС Unicode? Windows использует Unicode с первого дня существования Windows NT в 1993 году (я думаю). Вы должны использовать u16string или wstring для компиляторов до C++11, если они остались   -  person Panagiotis Kanavos    schedule 13.03.2017
comment
@PanagiotisKanavos Нет. Мы определили UNICODE во всех наших решениях. Что вы подразумеваете под использованием ANSI? Как я использую это мое демо?   -  person Mikhail    schedule 13.03.2017
comment
native narrow encoding означает кодовую страницу ASCII, используемую локалью системы, которая отображается в Regional Settings как Language for non-Unicode programs.   -  person Panagiotis Kanavos    schedule 13.03.2017
comment
@PanagiotisKanavos правильно, это то, что, я думаю, называется ANSI. И мы не можем контролировать эти настройки на компьютерах наших пользователей.   -  person Mikhail    schedule 13.03.2017
comment
в C++ string и char — однобайтовые типы. Типы Unicode являются многобайтовыми. Символы UTF16 — это char16_t и эквивалентные строки u16string. До C++11 UNICODE означало, что wchar использовалось везде, где появлялся макрос _TCHAR. Явно используя char и string, вы используете однобайтовые (ASCII/ANSII) типы.   -  person Panagiotis Kanavos    schedule 13.03.2017
comment
Просто не делайте этого. Делайте то, что делают все программы в Windows с 1993 года — используйте типы Unicode.   -  person Panagiotis Kanavos    schedule 13.03.2017
comment
Очень удобно рассматривать std::string как кодировку UTF-8, и это уже сделано во многих местах. Мы не можем просто пересмотреть их все. Я даже не думаю, что это необходимо, потому что UTF-8 настолько прост и широко принят. Есть ли у вас какие-либо предложения о том, какой тип использовать для строк в кодировке UTF-8?   -  person Mikhail    schedule 13.03.2017
comment
То, что вы описываете, актуально для Linux и Unix сейчас для локалей США/Великобритании. Windows NT полностью перешла на Unicode еще в 1990-х годах, ориентируясь на международную аудиторию, для которой UTF8 занимал больше места, чем UTF16. Это также позволило избежать распространенной проблемы смешивания UTF8 с локализованным текстом.   -  person Panagiotis Kanavos    schedule 13.03.2017
comment
@Mikhail: Что-то не так с использованием u8path, когда вам нужно создать путь из строки UTF-8?   -  person Nicol Bolas    schedule 13.03.2017
comment
@PanagiotisKanavos, так как вы предлагаете писать кроссплатформенный код? Используете UTF-16? Мы используем для этой цели UTF-8, и я подумал, что это правильно.   -  person Mikhail    schedule 13.03.2017
comment
Возможно, вы можете использовать UTF8 вместо родной UTF16, но вы можете столкнуться с проблемами. Например, NTFS поддерживает пути Unicode. Вы можете получить их только при вызове Unicode-версии функций Win32 (заканчивающейся на W). Я думаю, что это вызывают только Unicode-версии стандартных библиотечных функций, хотя я давно не проверял это.   -  person Panagiotis Kanavos    schedule 13.03.2017
comment
@NicolBolas Да. Нам нужно вставить его везде, в то время как кажется очень естественным предположить, что path обрабатывает узкие строки как кодировку UTF-8 по умолчанию, как это работает сейчас.   -  person Mikhail    schedule 13.03.2017
comment
@PanagiotisKanavos: Весь смысл filesystem в том, что нам не нужно делать подобное кодирование для конкретной платформы. Если вы хотите использовать UTF-8 везде (и должны), вы можете это сделать.   -  person Nicol Bolas    schedule 13.03.2017
comment
@Mikhail: Вам нужно вставить path всех, как сейчас. Итак, пока вы это делаете, почему бы не использовать u8path при создании путей из строк UTF-8?   -  person Nicol Bolas    schedule 13.03.2017
comment
@NicolBolas, потому что у нас уже есть fs::path везде, и мы хотели просто изменить fs с boost::filesystem на std::filesystem на заре C++17.   -  person Mikhail    schedule 13.03.2017
comment
@NicolBolas только что заметил, что path использует wchar_t в Windows, поэтому проблема может заключаться в том, как для преобразования одной кодировки Unicode в другую.   -  person Panagiotis Kanavos    schedule 13.03.2017


Ответы (2)


Ради производительности path не имеет глобального способа определения преобразования локали. Поскольку C++11 не имеет определенного типа для строк UTF-8, система предполагает, что любые строки char являются строками с узкими символами. Поэтому, если вы хотите использовать строки UTF-8, вы должны указать это явно, либо указав соответствующую локаль преобразования для конструктора, либо используя u8path.

person Nicol Bolas    schedule 13.03.2017
comment
Это звучит очень печально! Есть ли возможность изменить стандарт? - person Mikhail; 13.03.2017
comment
@ Михаил, нам потребовалось 24 года, чтобы получить строки UTF16 и UTF32. Иметь терпение - person Panagiotis Kanavos; 13.03.2017
comment
@Mikhail: Нет, и это хорошо. Единственный способ сделать то, о чем вы говорите, - это либо предположить, что char - это UTF-8 (что является неверным предположением), либо сделать код всех без необходимости медленнее, используя некоторые глобальные локали. - person Nicol Bolas; 13.03.2017
comment
@PanagiotisKanavos: у нас есть строки UTF-8. У нас просто нет для них отдельного типа. - person Nicol Bolas; 13.03.2017
comment
@NicolBolas не понимает, почему невозможность везде применять UTF-8 к моему коду на C++ — это хорошо. - person Mikhail; 13.03.2017
comment
@Mikhail: Вы можете применить это. Вы просто не можете использовать конструктор path для этого. И это преимущество, потому что это означает, что код всех остальных не замедляется. - person Nicol Bolas; 13.03.2017
comment
@NicolBolas подождите, path использует wchar_t в Windows? - person Panagiotis Kanavos; 13.03.2017
comment
@PanagiotisKanavos: я знаю об этом. Он использует кодировку UTF-16, хранящуюся в виде строки wchar_t. Но это его внутренняя кодировка. Суть path в том, что вам не должно быть интересно, какая внутренняя кодировка; вы предоставляете строку пути в любой кодировке, которая вам подходит, и все готово. Другие API, которые должны напрямую взаимодействовать с файловой системой, могут извлекать строку в любой кодировке, которую использует этот API. - person Nicol Bolas; 13.03.2017
comment
@NicolBolas можно привыкнуть использовать u8path по умолчанию, но проблема в чертовом автоматическом преобразовании std::string в fs::path. Если вы храните utf-8 в std::string, что действительно хорошо, все вызовы свободных функций, такие как fs::exists(std::string(filepath)) или fs::remove(std::string(filepath)), потенциально неверны. - person ceztko; 14.06.2017
comment
@ceztko: Но это легко исправить: exists(std::fs::path(filepath)) и так далее. Тяжелые случаи — это когда у вас уже есть std::string в переменной и вы хотите вставить ее. Вы должны знать, что API принимает path. Но с этим ничего не поделаешь; до тех пор, пока C++ не имеет определенного типа для строки UTF-8, вы должны предоставить какой-то способ их различать. - person Nicol Bolas; 14.06.2017
comment
@NicolBolas: А? Вы уверены, что не имели в виду exists(std::fs::u8path(filepath))? Потому что, как было сказано, уже есть (к сожалению) автоматическое преобразование из std::string в fs::path - person ceztko; 14.06.2017
comment
@ceztko: Достаточно честно. Но моя общая точка зрения заключалась в том, что, поскольку вам в любом случае нужно выполнить явное преобразование, вы также можете использовать правильное явное преобразование. Трудная часть - это когда вам вообще не нужно использовать явное преобразование. - person Nicol Bolas; 14.06.2017

Мое решение этой проблемы состоит в том, чтобы полностью связать std::filesystem с другим пространством имен с именем std::u8filesystem с классами и методами, которые обрабатывают std::string как кодировку UTF-8. Классы наследуют свои соответствующие в std::filesystem с тем же именем, без добавления какого-либо поля или виртуального метода, чтобы обеспечить полную совместимость API/ABI. Полное доказательство концептуального кода здесь, протестировано только на Windows и пока не завершено. Следующий фрагмент показывает основную работу помощника:

std::wstring U8ToW(const std::string &string);

namespace std
{
    namespace u8filesystem
    {

    #ifdef WIN32
        class path : public filesystem::path
        {
        public:
            path(const std::string &string)
                : fs::path(U8ToW(path))
            {
            }

            inline std::string string() const
            {
                return filesystem::path::u8string();
            }
        }
    #else
        using namespace filesystem;
    #endif
    }
}
person ceztko    schedule 29.09.2017