#охужэтотстандарт, или кодировкопроблемы
От: FrozenHeart  
Дата: 08.04.16 15:33
Оценка: 12 (2)
Как я могу универсально (читай — кроссплатформенно) использовать 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


Всё ли я правильно сказал?

Я знаю, что тут есть знатоки стандарта — помогите, пожалуйста, разобраться
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.