Как я могу универсально (читай — кроссплатформенно) использовать 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
Всё ли я правильно сказал?
Я знаю, что тут есть знатоки стандарта — помогите, пожалуйста, разобраться
Здравствуйте, FrozenHeart, Вы писали:
FH>Как я могу универсально (читай — кроссплатформенно) использовать non-ASCII characters в строковых литералах в C++?
Универсально — вообще держать подобные ресурсы в отдельных файлах, а не внутри кода.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, 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>Получается, единственным универсальным методом будет замена '✓' на \u2713: FH>
FH>const char* str = "\u2713";
FH>
FH>Но так ли это?
Это нужно для того, чтобы файлы проекта с кодом были на 100% совместимы с кодировкой ASCII (латиница) и при этом была возможность использовать в самой программе юникод получаемый из строковых литералов C++. Но как уже сказал кроссплатформа давно перешла на UTF-8, потому не сказать, чтобы эта совместимость особо нужна. Хотя это давно на самом деле не так уж и давно по сравнению с историей C++. Потому сами решайте каких стандартов придерживаться, которые ввели более 10 лет назад, более 20 лет назад, более 30 лет назад, более 40 лет назад и так далее.
Здравствуйте, 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...
Файловая система в том или ином виде должна ведь присутствовать всё равно. Вы же как-то подавали исходные файлы на вход компилятору?
Впрочем, всё это оффтоп. Буду признателен, если ответите на обозначенный в ОП-посте вопрос.