Как я могу универсально (читай — кроссплатформенно) использовать non-ASCII characters в строковых литералах в C++?
Предположим, я решил написать
const char* str = "✓";
Что произойдёт при компиляции такой программы?
Начнём с того, что стандарт языка говорит, что набор допустимых символов в исходных файлах определяется реализацией:
ISO/IEC 14882:2014 (C++14)
2.2 Phases of translation [lex.phases]
[...]
The set of physical source file characters accepted is implementation-defined
[...]
Далее говорится, что любой символ, не входящий в basic source character set, заменяется соответствующим universal-character-name (в виде \uXXXX):
(там же)
Any source file character not in the
basic source character set (2.3) is replaced by the universal-character-name that designates that character.
(An implementation may use any internal encoding, so long as an actual extended character
encountered in the source file, and the same extended character expressed in the source file as a
universal-character-name (i.e., using the \uXXXX notation), are handled equivalently except where this
replacement is reverted in a raw string literal.)
Т.е. если реализация допускает само наличие символа '✓' в исходном файле, то он будет преобразован к \u2713. Но это будет сделано только в том случае, если, повторюсь, файл с таким символом вообще допустимо подавать на вход реализации (будь то строковый литерал или что-то ещё — в данном случае это неважно).
Получается, единственным универсальным методом будет замена '✓' на \u2713:
const char* str = "\u2713";
Но так ли это?
В пятой фазе трансляции можно обнаружить следующий текст:
Each source character set member in a character literal or a string literal, as well as each escape
sequence and universal-character-name in a character literal or a non-raw string literal, is converted to
the corresponding member of the execution character set (2.13.3, 2.13.5); if there is no corresponding
member, it is converted to an implementation-defined member other than the null (wide) character
Т.е. наш \u2713 будет сконвертирован в соответствующий член execution character set'а, или, если подобного преобразования не существует, в оставленный на усмотрение реализации символ, отличный от NULL'а.
Execution character set, в свою очередь, содержит все символы basic source character set'а, некоторые управляющие последовательности и набор дополнительных символов, специфичных для локали:
ISO/IEC 14882:2014 (C++14)
2.3 Character sets [lex.charset]
The basic execution character set and the basic execution wide-character set shall each contain all the
members of the basic source character set, plus control characters representing alert, backspace, and carriage
return, plus a null character (respectively, null wide character), whose value is 0. For each basic execution
character set, the values of the members shall be non-negative and distinct from one another. In both the
source and execution basic character sets, the value of each character after 0 in the above list of decimal
digits shall be one greater than the value of the previous. The execution character set and the execution
wide-character set are implementation-defined supersets of the basic execution character set and the basic
execution wide-character set, respectively. The values of the members of the execution character sets and
the sets of additional members are locale-specific.
Короче говоря, будет \u2713 входить в execution character set любой платформы гарантированно сказать нельзя — всё зависит от ситуации (локаль etc).
Однако в случае использования префикса u8 \u2713 всегда будет конвертироваться к UTF-8 encoded code point'у:
ISO/IEC 14882:2014 (C++14)
2.13.3 Character literals [lex.ccon]
A character literal that begins with u8, such as u8’w’, is a character literal of type char, known as a UTF-8
character literal. The value of a UTF-8 character literal is equal to its ISO 10646 code point value, provided
that the code point value is representable with a single UTF-8 code unit (that is, provided it is in the C0
Controls and Basic Latin Unicode block)
(да, тут речь идёт о character literal, а не о string literal, но подобную цитату конкретно про строковые литералы я что-то не нашёл — наверное, оно как-то вытекает из данного определения)
Т.е. единственный кросс-платформенный способ использовать non-ASCII символы в строковых литералах — это
const char* str = u8"\u2713";
А что же насчёт wide string literal'ов? Могу ли я написать вот так?
const wchar_t* str = L"✓";
Или хотя бы вот так
const wchar_t* str = L"\u2713";
Первый вариант, по идее, отметается сразу же (по той же причине, почему и "✓" — в общем случае мы не можем надеяться на implementation-defined набор допустимых physical source file characters).
Второй вариант отметается из-за того, что он опирается на понятие execution wide-character set'а, который, как уже было заметно по цитате, приведённой ранее, может и не содержать маппинга для данного символа вовсе.
ISO/IEC 14882:2014 (C++14)
2.13.3 Character literals [lex.ccon]
The value of a wide-character literal containing a single c-char has value equal
to the numerical value of the encoding of the c-char in the execution wide-character set, unless the c-char
has no representation in the execution wide-character set, in which case the value is implementation-defined
Всё ли я правильно сказал?
Я знаю, что тут есть знатоки стандарта — помогите, пожалуйста, разобраться
__>параллельно вопрос: а как гарантировать, что если в текстовом редакторе среды разработки (того же vs) я вижу символ "✓", то и всюду в скомпилированной программе он будет выводиться в нужном представлении? можно ли для этого использовать подход:
__>
__>const std::wstring = L"✓";
__>
__>?
Гарантированно — никак, потому что
The set of physical source file characters accepted is implementation-defined
Но раз речь идёт только об одном окружении (редактор и компилятор, встроенные в Visual Studio), то... Я всё равно не могу посоветовать использовать wide-string.
Их проблема в том, что стандарт не гарантирует на их тему практически ничего! В какую кодировку будут преобразованы символы такой строки неизвестно, какого размера будет каждый из её элементов зависит от реализации (он вообще может быть равен 1 байту, как и char).
Здравствуйте, _hum_, Вы писали:
__>параллельно вопрос: а как гарантировать, что если в текстовом редакторе среды разработки (того же vs) я вижу символ "✓", то и всюду в скомпилированной программе он будет выводиться в нужном представлении?
Таких гарантий нет.
__>
можно ли для этого использовать подход:
__>
Здравствуйте, FrozenHeart, Вы писали:
FH>Как я могу универсально (читай — кроссплатформенно) использовать non-ASCII characters в строковых литералах в C++?
Универсально — вообще держать подобные ресурсы в отдельных файлах, а не внутри кода.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, FrozenHeart, Вы писали:
FH>Получается, единственным универсальным методом будет замена '✓' на \u2713: FH>
FH>const char* str = "\u2713";
FH>
FH>Но так ли это?
Это нужно для того, чтобы файлы проекта с кодом были на 100% совместимы с кодировкой ASCII (латиница) и при этом была возможность использовать в самой программе юникод получаемый из строковых литералов C++. Но как уже сказал кроссплатформа давно перешла на UTF-8, потому не сказать, чтобы эта совместимость особо нужна. Хотя это давно на самом деле не так уж и давно по сравнению с историей C++. Потому сами решайте каких стандартов придерживаться, которые ввели более 10 лет назад, более 20 лет назад, более 30 лет назад, более 40 лет назад и так далее.
Здравствуйте, FrozenHeart, Вы писали:
FH>Что значит "готов отказаться"? Я привёл цитаты из стандарта, которые указывают на то, что использование non-ASCII символов в строковых литералах -- это уже implementation-defined поведение.
Есть спецификация языка, то есть документ описывающий его стандарт, и его реализации в виде наборов утилит в которые так же входит компилятор. Как известно разные производители компиляторов отступают в некоторых деталях от стандарта. Более того существуют ещё древние компиляторы, но это явно не про C++14. Опять же не забываем о настройках компилятора, можно ведь так настроить, что не скомпилируется даже то, что по стандарту, то есть поставить запрет на некие возможности. Поэтому у производителей компиляторов есть свои документы, в которых описано как они это видят. Иными словами всё равно нельзя игнорировать особенности и настройки компиляторов, даже если действуете по стандарту по которому в теории должны создаваться все компиляторы.
FH>Т.е. единственный кросс-платформенный способ использовать non-ASCII символы в строковых литералах — это
Для кроссплатформы понадобится прописать поведение в зависимости от платформы, например:
CMake (от англ. cross platform make) — это кроссплатформенная система автоматизации сборки программного обеспечения из исходного кода. CMake не занимается непосредственно сборкой, a лишь генерирует файлы управления сборкой из файлов CMakeLists.txt:
Makefile в системах Unix для сборки с помощью make;
файлы projects/solutions (.vcproj/.sln) в Windows для сборки с помощью Visual C++;
проекты XCode в Mac OS X
qmake- это утилита из состава Qt, которая помогает облегчить процесс сборки приложения на разных платформах. qmake автоматически генерирует make-файлы, основываясь на информации в файлах проекта(*.pro).
(информация из руководства)
qmake – программное средство, с помощью которого упрощается процесс сборки проекта при разработке для разных платформ. qmake автоматизирует создание файла сборки, так как требуется только несколько строчек информации для создания каждого такого файла. qmake может быть использован для любого программного проекта, независимо от того, написан ли он на Qt или нет.
qmake создает файл сборки, не требуя от разработчика вносить изменения в файл проекта.
Опять же если так волнует переносимость, тогда лучше использовать C++03, а не C++14. Сейчас уже разговор пошёл не о том, что нельзя скомпилировать программу в принципе, а о том, что у конкретного человека это может не получиться. Не поставили указание компилятору использовать новый стандарт, отключили поддержку UTF-8, и так далее.
Здравствуйте, FrozenHeart, Вы писали:
FH>Файловая система в том или ином виде должна ведь присутствовать всё равно. Вы же как-то подавали исходные файлы на вход компилятору?
С ленточки грузил в ОЗУ...
FH>Впрочем, всё это оффтоп. Буду признателен, если ответите на обозначенный в ОП-посте вопрос.
Так что за вопрос? С++ сформулирован в неких абстрактных терминах. Исходники — это именованные последовательости текстовых символов просто, а способы задания отображения имён на последовательности и представления последовательностей вроде как не ооваривается. Так что тут всё от платформы и транслятора зависит.
Про "по существу" тебе ответили же.
1) Если устроит набор из двух-трёх популярных компиляторов и семи-восьми платформ, то можно utf-8 исходники юзать, например.
2) Если не устраивает, то лучше такие тексты вообще вынести в отдельные файлы ресурсов и не зависить от доступных компилятору способов представления последовательностей букв в исходнике.
В общем, IMHO, ты путаешь последовтельность букв в текстах доступных коду и пследовательность букв, доступную компилятору в виде исходного текста.
Если их не путать, то проблем не будет, IMHO...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, FrozenHeart, Вы писали:
FH>Это да, но если всё же?
Ну ты же понимаешь, что на какой-то платформе не будет даже шрифта для отображения твоих литералов? Полностью универсально и полностью кроссплатформенно ты этого не сделаешь, так что только частные случаи, большинство из которых покрывается всего несколькими компиляторами.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Ops>Ну ты же понимаешь, что на какой-то платформе не будет даже шрифта для отображения твоих литералов?
Это уже другая история. Я говорю не о выводе информации в stdout / файлы / etc, а о самой возможности использования non-ASCII символов в строковых литералах.
Ops>Универсально — вообще держать подобные ресурсы в отдельных файлах, а не внутри кода.
Точно! все языки (оригинал-перевод) в отдельные файлы! дешево и удобно
Здравствуйте, FrozenHeart, Вы писали:
FH>Как я могу универсально (читай — кроссплатформенно) использовать non-ASCII characters в строковых литералах в C++?
UTF-8 (от англ. Unicode Transformation Format, 8-bit — «формат преобразования Юникода, 8-битный») — одна из общепринятых и стандартизированных кодировок текста, которая позволяет хранить символы Юникода, используя переменное количество байт (от 1 до 6).
Стандарт UTF-8 официально закреплён в документах RFC 3629 и ISO/IEC 10646 Annex D. Кодировка нашла широкое применение в UNIX-подобных операционных системах и веб-пространстве. Сам же формат UTF-8 был изобретён 2 сентября 1992 года Кеном Томпсоном и Робом Пайком и реализован в Plan 9. В качестве BOM использует последовательность байт EF16, BB16, BF16 (что у неё самой является трёхбайтовой реализацией символа FEFF16).
Одним из преимуществ является совместимость с ASCII — любые их 7-битные символы отображаются как есть, а остальные выдают пользователю мусор (шум). Поэтому в случае, если латинские буквы и простейшие знаки препинания (включая пробел) занимают существенный объём текста, UTF-8 даёт выигрыш по объёму по сравнению с UTF-16.
А дальше уже вопрос в том, как программа будет это воспринимать. Объясню на примере Qt. Предположим у нас все файлы в кодировке UTF-8, на самом деле сейчас в нормальных проектах везде так и есть. И у нас такой код:
setWindowTitle("✖");
Как и сказано выше вместо вывода крестика у нас будет мусор. Но если напишем вот так:
setWindowTitle(QString::fromUtf8("✖"));
то крестик будет выведен в заголовке правильно. И если QString это пользовательский тип, то "✖" ничто иное как строковый литерал C++ находящийся в файле с кодировкой UTF-8. Давайте пойдём чуть дальше и сделаем так:
setWindowTitle(QString::fromUtf8("✓"));
а так же
setWindowTitle(QString::fromUtf8("\u2713"));
отрабатывают правильно выводя в заголовке галочку. Теперь берём первый пример:
value Escape-последовательность
новая строка \n
горизонтальная табуляция \t
вертикальная табуляция \v
Удаление предыдущего символа \b
возврат каретки \r
Подача страницы (для перехода к началу следующей страницы) \f
Звуковой сигнал (звонок) \a
обратная косая черта \\
вопросительный знак ? или \?
одинарная кавычка \'
двойная кавычка \"
нуль-символ \0
восьмеричный \ooo
шестнадцатеричный \xhhh
Юникод (UTF-8) \uxxxx
Юникод (UTF-16) \Uxxxxxxxx
и
Символ обратной косой черты (\) — это символ продолжения строки, если он стоит в конце строки. Если символ обратной косой черты требуется использовать как символьный литерал, необходимо ввести две косые черты подряд (\\). Дополнительные сведения о символе продолжения строки см. в разделе Фазы трансляции.
Иначе говоря дело не в компиляторах, так как компиляторы microsoft, gcc (mingw) и прочие современные отработают как надо.
Проблема не здесь
const char* str = "✓";
а вот здесь
const char* str = "✓";
Опять же какая это проблема, если всё работает именно так, как и задумано. Проблема не в строковых литералах C++ и не в кодировке UTF-8, проблема в присвоении всего этого к char*. Сразу же возникают вопросы, как определить конец строки, как определить кодировку? Ответы очевидны, но как раз это и говорит о том, что так лучше не делать.
FH>Предположим, я решил написать FH>const char* str = "✓"; FH>Что произойдёт при компиляции такой программы?
Всё скомпилируется и новые стандарты C++ здесь вообще ни причём, на старых тоже будет работать.
Здравствуйте, FrozenHeart, Вы писали:
FH>Это уже другая история. Я говорю не о выводе информации в stdout / файлы / etc, а о самой возможности использования non-ASCII символов в строковых литералах.
IMHO, это определяется тем подмножеством распространённых на платформе представлений текста, которое поддерживает компилятор...
А что ты понимаешь под "кроссплатфрменно"? Если, например, при передаче с платформы на платформу происходит перекодировка из UTF16 в UTF8, то это оно?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
E>IMHO, это определяется тем подмножеством распространённых на платформе представлений текста, которое поддерживает компилятор...
Эм. Судя по приведённым мной цитатам из стандарта, использование \uXXXX-нотации как раз и позволяет избежать неоднозначности, связанной с различием в подмножествах символов, поддерживаемых той или иной реализацией.
E>А что ты понимаешь под "кроссплатфрменно"?
Имеет одинаковое ожидаемое поведение во всех реализациях, поддерживающих C++11 / C++14.
E>Если, например, при передаче с платформы на платформу происходит перекодировка из UTF16 в UTF8, то это оно?
Какой передаче?
V>Кроссплатформенно это кодировка UTF-8.
Эм. А UTF-16 или UTF-32 — уже нет?
V>А дальше уже вопрос в том, как программа будет это воспринимать
Не программа, а реализация. Я же не о run-time поведении говорю, а о трансляции.
V>Предположим у нас все файлы в кодировке UTF-8, на самом деле сейчас в нормальных проектах везде так и есть
Что такое "нормальный проект"? А если, например, в исходниках ядра Linux'а пара файлов сохранено не в UTF-8, то это уже не нормальный проект?
V>И если QString это пользовательский тип, то "✖" ничто иное как строковый литерал C++ находящийся в файле с кодировкой UTF-8.
Да, это действительно строковый литерал, который, согласно описанным Вами условиям, находится в файле, сохранённом в кодировке UTF-8. Но это вовсе не отменяет того факта, что перед выполнением программы она должна сначала пройти все фазы трансляции, которые описаны в стандарте, а именно они, как я и указал в ОП-посте, делают подобную конструкцию implementation-defined.
V>Иначе говоря дело не в компиляторах, так как компиляторы microsoft, gcc (mingw) и прочие современные отработают как надо
Не понял, как Вы сделали такой вывод.
V>Опять же какая это проблема, если всё работает именно так, как и задумано.
С чего это вы взяли? Потому что на каких-то версиях компиляторов с какими-то определёнными флагами компиляции, кодировками файлов и локалями всё выглядит так, как будто всё работает, как и ожидается? Ну так это вовсе не показатель.
V>Проблема не в строковых литералах C++ и не в кодировке UTF-8, проблема в присвоении всего этого к char*
Я ничего не понял.
V>Всё скомпилируется и новые стандарты C++ здесь вообще ни причём, на старых тоже будет работать.
На основании каких данных Вы сделали такой вывод?
V>Но как уже сказал кроссплатформа давно перешла на UTF-8, потому не сказать, чтобы эта совместимость особо нужна.
Получается, стандарт языка с Вами не согласен.
V>Хотя это давно на самом деле не так уж и давно по сравнению с историей C++. Потому сами решайте каких стандартов придерживаться, которые ввели более 10 лет назад, более 20 лет назад, более 30 лет назад, более 40 лет назад и так далее.
Я хочу придерживаться текущего стандарта (ISO/IEC 14882:2014 или, иначе говоря, C++14), цитаты из которого я и приводил в ОП-посте.
Здравствуйте, FrozenHeart, Вы писали:
FH>Я хочу придерживаться текущего стандарта (ISO/IEC 14882:2014 или, иначе говоря, C++14), цитаты из которого я и приводил в ОП-посте.
А я использую ISO/IEC 14882:2003 (C++03), а до этого использовал ISO/IEC 14882:1998 (C++98). Однако мои высказывания относятся и к другим опубликованным стандартам.
Year C++ Standard Informal name
1998 ISO/IEC 14882:1998 C++98
2003 ISO/IEC 14882:2003 C++03
2011 ISO/IEC 14882:2011 C++11
2014 ISO/IEC 14882:2014 C++14
FH>Что такое "нормальный проект"? А если, например, в исходниках ядра Linux'а пара файлов сохранено не в UTF-8, то это уже не нормальный проект?
На это уже написал выше, если готовы отказаться от UTF-8 в коде программы, то просто используйте формат:
Юникод (UTF-8) \uxxxx
Юникод (UTF-16) \Uxxxxxxxx
В этом случае будет неудобно читать код, но зато останется совместимость с ASCII. Впрочем я уже повторяюсь. А что касается отдельных файлов для хранения ресурсов таких как строки, то обычно их основное назначение вовсе не борьба с кодировками, а способ локализации приложения.
Здравствуйте, FrozenHeart, Вы писали:
E>>Если, например, при передаче с платформы на платформу происходит перекодировка из UTF16 в UTF8, то это оно? FH>Какой передаче?
Ну исходники как-то должны же попасть на целевую платформу? Или ты рассчитываешь на кросс-компилятор?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
V>А я использую ISO/IEC 14882:2003 (C++03), а до этого использовал ISO/IEC 14882:1998 (C++98). Однако мои высказывания относятся и к другим опубликованным стандартам.
Какие цитаты? Пока я увидел лишь описание частных случаев из Вашей практики.
V>На это уже написал выше, если готовы отказаться от UTF-8 в коде программы, то просто используйте формат: V>
Что значит "готов отказаться"? Я привёл цитаты из стандарта, которые указывают на то, что использование non-ASCII символов в строковых литералах -- это уже implementation-defined поведение.
E>Ну исходники как-то должны же попасть на целевую платформу? Или ты рассчитываешь на кросс-компилятор?
Вы намекаете на то, что какой-то платформой, на которой должны собираться исходники, не будет поддерживаться UTF-8?
В общем, вопрос-то простой -- верный ли мои рассуждения из ОП-поста или нет? Если нет, то можно попросить опровергнуть их цитатами из стандарта, пожалуйста?
Здравствуйте, FrozenHeart, Вы писали:
FH>Вы намекаете на то, что какой-то платформой, на которой должны собираться исходники, не будет поддерживаться UTF-8?
Я собирал С на платформе, где файлов не было, а не то, что utf-8...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
E>Я собирал С на платформе, где файлов не было, а не то, что utf-8...
Файловая система в том или ином виде должна ведь присутствовать всё равно. Вы же как-то подавали исходные файлы на вход компилятору?
Впрочем, всё это оффтоп. Буду признателен, если ответите на обозначенный в ОП-посте вопрос.
Здравствуйте, FrozenHeart, Вы писали:
FH>Всё ли я правильно сказал?
Да, на мой взгляд почти всё верно, но есть одно упущение в самом начале:
FH>Предположим, я решил написать FH>
FH>const char* str = "✓";
FH>
FH>Что произойдёт при компиляции такой программы?
Прежде, чем ответить на этот вопрос нужно задаться другим вопросом: что произойдёт при сохранении этой строчки кода на физическом носителе? Думаю вы согласитесь, что представление символа '✓' в виде последовательности байтов в файле на разных платформах может быть разным, т.е. implementation-defined. Все остальные вопросы связанные с представлением строк логически вытекают из этого положения. Если бы этот формат файлов был фиксированным, то для редактирования файлов пришлось бы создавать специальные редакторы взамен общетекстовых.
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, FrozenHeart, Вы писали:
FH>>Всё ли я правильно сказал? BFE>Да, на мой взгляд почти всё верно, но есть одно упущение в самом начале:
FH>>Предположим, я решил написать FH>>
FH>>const char* str = "✓";
FH>>
FH>>Что произойдёт при компиляции такой программы?
BFE>Прежде, чем ответить на этот вопрос нужно задаться другим вопросом: что произойдёт при сохранении этой строчки кода на физическом носителе? Думаю вы согласитесь, что представление символа '✓' в виде последовательности байтов в файле на разных платформах может быть разным, т.е. implementation-defined. Все остальные вопросы связанные с представлением строк логически вытекают из этого положения. Если бы этот формат файлов был фиксированным, то для редактирования файлов пришлось бы создавать специальные редакторы взамен общетекстовых.
параллельно вопрос: а как гарантировать, что если в текстовом редакторе среды разработки (того же vs) я вижу символ "✓", то и всюду в скомпилированной программе он будет выводиться в нужном представлении? можно ли для этого использовать подход: