Допускают ли строгие правила псевдонимов в C ++ 20 `reinterpret_cast` между стандартными символами Unicode c ++ и подчеркивающими типами? - PullRequest
2 голосов
/ 02 июня 2019

Должны ли C++20 строгие правила псевдонимов [basic.lval] / 11 произвольно разрешать следующее ...

  1. приведение между char* и char8_t*
string str = "string";
u8string u8str { (char8_t*) &*str.data() }; // c++20 u8string

u8string u8str2 = u8"zß水?"
string str2 { (char*) u8str2.data() };
приведение между uint32_t*, uint_least32_t* и char32_t*
vector<uint32_t> ui32vec = { 0x007a, 0x00df, 0x6c34, 0x0001f34c };
u32string u32str { (char32_t*) &*ui32vec.data(), ui32vec.size() };

u32string u32str2 = U"zß水?"
vector<uint32_t> ui32vec2 { (uint32_t*) &*u32str2.begin(),
                            (uint32_t*) &*u32str2.end() };
приведено между uint16_t*, uint_least16_t* и char16_t*
vector<uint16_t> ui16vec = { 0x007a, 0x00df, 0x6c34, 0xd83c, 0xdf4c };
u16string u16str { (char16_t*) &*ui16vec.data(), ui16vec.size() };

u16string u16str2 = u"zß水\ud83c\udf4c"
vector<uint16_t> ui16vec2 { (uint16_t*) &*u16str2.begin(),
                            (uint16_t*) &*u16str2.end() };

Обновление

Basic_String Constructor Перегрузка (6)

template< class InputIt >    
basic_string( InputIt first, InputIt last,
              const Allocator& alloc = Allocator() );

векторный конструктор перегрузка (4)

template< class InputIt >    
vector( InputIt first, InputIt last,
        const Allocator& alloc = Allocator() );

Интересно, будет ли хорошо с LegacyInputIterator конструкторов? ...

char* и char8_t* как LegacyInputIterator
string str = "string";
u8string u8str {   str.begin(),   str.end()  };
u8string u8str { &*str.begin(), &*str.end()  };

u8string u8str2 = u8"zß水?"
string str2 {   u8str2.begin(),   u8str2.end() };
string str2 { &*u8str2.begin(), &*u8str2.end() };
uint32_t*, uint_least32_t* и char32_t* as LegacyInputIterator
vector<uint32_t> ui32vec = { 0x007a, 0x00df, 0x6c34, 0x0001f34c };
u32string u32str {   ui32vec.begin(),   ui32vec.end() };
u32string u32str { &*ui32vec.begin(), &*ui32vec.end() };

u32string u32str2 = U"zß水?"
vector<uint32_t> ui32vec2 { u32str2.begin(),
                            u32str2.end() };
vector<uint32_t> ui32vec2 { &*u32str2.begin(),
                            &*u32str2.end() };
uint16_t*, uint_least16_t* и char16_t* as LegacyInputIterator
vector<uint16_t> ui16vec = { 0x007a, 0x00df, 0x6c34, 0xd83c, 0xdf4c };
u16string u16str {   ui16vec.begin(),   ui16vec.end() };
u16string u16str { &*ui16vec.begin(), &*ui16vec.end() };

u16string u16str2 = u"zß水\ud83c\udf4c"
vector<uint16_t> ui16vec2 { u16str2.begin(),
                            u16str2.end() };
vector<uint16_t> ui16vec2 { &*u16str2.begin(),
                            &*u16str2.end() };

Ответы [ 3 ]

7 голосов
/ 02 июня 2019

В строке типов char*_t нет специальных правил наложения имен.Поэтому применяются стандартные правила .И эти правила не имеют исключений для преобразования между базовыми типами.

Так что большая часть того, что вы сделали, это UB.Единственный случай, который не является UB, это char из-за его особой природы.Фактически вы можете прочитать байты char8_t как массив char.Но вы не можете сделать обратное, считывая байты массива char как char8_t.

Теперь эти типы являются полностью конвертируемыми друг в друга.Таким образом, вы можете конвертировать значения в этом массиве в другой тип в любое время.

Все это, как говорится, в реальных реализациях эти вещи почти наверняка будут работать.Ну, до тех пор, пока они этого не сделают, потому что вы пытались изменить одну вещь через вещь, которой она не должна изменяться, и компилятор не перезагружал измененное значение, потому что предполагал, что оно не могло быть изменено.Так что на самом деле, просто используйте правильный, значимый тип.

1 голос
/ 02 июня 2019

Так же, как мы находимся на той же странице, приведение в стиле C (T*) expression эквивалентно reinterpret_cast<T*>(expression) ( [expr.cast] /4.4), что эквивалентно static_cast<T*>(static_cast<void*>(expression))( [expr.reinterpret.cast] / 7 ).Это никак не влияет на значение указателя, так как они не взаимозаменяемы с указателем.(См. [expr.static.cast] / 13 и [basic.compound] / 4 ).

Так что да, нам нужно взглянуть на [basic.lval] / 11 , чтобы посмотреть, может ли он быть псевдонимом.Ссылка должна иметь тип, подобный следующему:

  • динамический тип объекта,
  • тип, который является типом со знаком или без знака, соответствующим динамическому типуобъекта или
  • a char, unsigned char или std::byte type.

Что не так.Хотя char8_t имеет базовый тип unsigned char, он не похож на этот тип.

Так, например:

unsigned char uc = 'a';

// Represents address of uc
unsigned char* uc_ptr = &uc;

// Still holds the address of uc, not a char8_t
char8_t* c8_ptr = reinterpret_cast<char8_t*>(uc_ptr);

char8_t c8 = *c8_ptr;  // UB, as `char8_t` is not `cv unsigned char`.

Хотя из-за [basic.ОСНОВЫ] / 6 , в котором говорится:

Базовый тип, для которого указан целочисленный тип со знаком или без знака в качестве базового типа, имеет то же представление объекта [...]

Вы можете сделать reinterpret_cast<unsigned char*>(pointer-to-char8_t) и иметь все значения равными, но это единственный случай (а также char* тогда char без знака, иначе они могут сравниться неравно (Даже для значений <128)).Для всех других типов вы можете использовать это правило для <code>memcpy:

// Assuming std::is_same_v<uint32_t, uint_least32_t>
vector<uint32_t> ui32vec = { 0x007a, 0x00df, 0x6c34, 0x0001f34c };
u32string u32str(ui32vec.size(), U'\x00');
std::memcpy(u32str.data(), ui32vec.data(), ui32vec.size() * sizeof(uint32_t));

u32string u32str2 = U"zß水?"
vector<uint32_t> ui32vec2(u32str2.size(), U'\x00');
std::memcpy(u32str2.data(), ui32vec2.data(), u32str2.size() * sizeof(uint32_t));
1 голос
/ 02 июня 2019

В стиле C не то же самое, что reinterpret_cast.

Стандартные разделы, я думаю, имеют отношение к вашему вопросу:

6.7.1.9 : Тип char8_t обозначает отдельный тип, базовым типом которого является unsigned char. Типы char16_t и char32_t обозначают различные типы, базовыми типами которых являются uint_least16_t и uint_least32_t, соответственно, в.

7.2.1.11 : Если программа пытается получить доступ к сохраненному значению объекта через glvalue, тип которого не похож ([conv.qual]) на один из следующих типов, поведение не определено :

1. динамический тип объекта,

2. тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта, или

3. тип char, unsigned char или std :: byte.

  1. char8_t*-->char* Да.
    Потому что char является одним из типов, которые все объекты могут быть преобразованы в. Но стандарт не гарантирует, что преобразованные (разыменованные) значения равны для разных типов. char может быть подписано или нет, а char8_t не подписано. char8_t*-->unsigned char* действителен, но не должен гарантировать это, потому что он все еще различен. Но учитывая, что это базовый тип char8_t, он должен быть, я думаю?
  2. char*-->char8_t* Нет .
    Согласно 6.7.1.9 эти типы различны. Хотя может быть аргумент, что часть ", чей базовый тип - unsigned char " может применяться с явно разрешенным * 7.21 * в 7.2.1.11.3, но я не думаю, что это будет правильной интерпретацией и быть отличительным должно быть решающим фактором. Это подтверждается следующей цитатой из комментария в предложении P0482R6 - char8_t: тип для символов и строк UTF-8 (редакция 6 - 2018-11-09) (более поздняя редакция не найдена) ):

    Наконец, обработка строк UTF-8 в настоящее время подвергается пессимизации оптимизации из-за выражений glvalue типа char, которые потенциально могут навязывать объекты других типов. Использование отдельного типа, который не разделяет это поведение псевдонимов, может позволить дальнейшую оптимизацию компилятора.

  3. uint32_t*<-->char32_t*, uint16_t*<-->char16_t*, uint16_t*<-->uint_least16_t*, uint32_t*<-->uint_least32_t*, uint_least32_t<-->char32_t, uint_least16_t<-->char16_t: Нет .
    Все эти пары различны, поэтому 7.2.1.11.1 не применяется, и ни один из типов не входит в 7.2.1.11.3, поэтому даже вторая часть 2. не может быть релевантной.

  4. unsigned char*-->char8_t* Нет .
    По тому же аргументу, что и в 2. Это не T*->T* приведение, которое явно разрешено.

  5. char8_t*-->unsigned char* Да .
    Потому что unsigned char тоже один из разрешенных типов согласно 7.2.1.11.3. Но я все равно утверждаю, что стандарт не гарантирует, что (разыменованные) преобразованные значения будут равны. Но учитывая, что это базовый тип char8_t, у него нет других вариантов, кроме как быть равным, я полагаю?

...