Чи можна nullptr перетворити на uintptr_t? Різні компілятори не згодні


10

Розглянемо цю програму:

#include <cstdint>
using my_time_t = uintptr_t;

int main() {
    const my_time_t t = my_time_t(nullptr);
}

Не вдалося компілювати з msvc v19.24:

<source>(5): error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'my_time_t'
<source>(5): note: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
<source>(5): error C2789: 't': an object of const-qualified type must be initialized
<source>(5): note: see declaration of 't'

Compiler returned: 2

але clang (9.0.1) та gcc (9.2.1) "з'їдають" цей код без будь-яких помилок.

Мені подобається поведінка MSVC, але це підтверджено стандартом? Іншими словами, це помилка в clang / gcc чи можливо інтерпретувати стандарт, що це правильна поведінка від gcc / clang?


2
Я читаю це як ініціалізацію копіювання з акторського стилю. Потім компілятор інтерпретується як один із казів C ++, "навіть якщо його неможливо скомпілювати". Можливо, між компіляторами є непослідовність того, як інтерпретується
акторський склад

Наскільки мені відомо, MSVC v19.24 не підтримує режим C ++ 11. Ви мали на увазі натомість C ++ 14 або C ++ 17?
волоський горіх

Відповіді:


5

На мій погляд, MSVC не відповідає стандартам.

Я базую цю відповідь на C ++ 17 (проект N4659), але C ++ 14 і C ++ 11 мають рівнозначні формулювання.

my_time_t(nullptr)- це постфікс-вираз, а тому, що my_time_tє типом і (nullptr)є одиничним виразом у списку ініціалізаторів, скоплених у скобках, він точно еквівалентний явному виразному формулюванню. ( [expr.type.conv] / 2 )

Явний виступ намагається виконати кілька різних специфічних C ++ (з розширеннями), зокрема також reinterpret_cast. ( [expr.cast] /4.4 ) Запробовані раніше роликиreinterpret_cast є ( const_castі static_castз розширеннями, а також у комбінації), але жодне з них не може привести std::nullptr_tдо цілісного типу.

Але це reinterpret_cast<my_time_t>(nullptr)має досягти успіху, оскільки [expr.reinterpret.cast] / 4 говорить, що значення типу std::nullptr_tможна перетворити на інтегральний тип як би за допомогою reinterpret_cast<my_time_t>((void*)0), що можливо, тому що він my_time_t = std::uintptr_tповинен бути типом достатньо великим, щоб представляти всі значення вказівника, і за цієї умови той же стандартний абзац дозволяє перетворити void*на інтегральний тип.

Особливо дивно, що MSVC дозволяє перетворювати, якщо використовуються нотації замість функціональних позначень:

const my_time_t t = (my_time_t)nullptr;

1
Так. Зауважте, що static_castзокрема є деякі випадки, призначені для захоплення сходів у стилі С (наприклад, відливання стилю С у неоднозначну основу є неправомірно сформованим, static_castа не reinterpret_cast), але жодне тут не застосовується.
ТК

my_time_t(nullptr)за визначенням те саме (my_time_t)nullptr, що і MSVC, безумовно, неправильно приймати одне і відхиляти інше.
Річард Сміт

2

Хоча я не можу знайти чітких згадок у цьому робочому проекті стандарту C ++ (з 2014 року), що перетворення з std::nullptr_tна цілісний тип заборонено, також не згадується, що таке перетворення дозволено!

Однак, в разі переходу від std::nullptr_tTo bool буде явно згадано:

4.12 Булеві перетворення
Перше значення арифметичного, нескореного перерахування, вказівника або вказівника на тип члена може бути перетворене в перше значення типу bool. Нульове значення, нульове значення вказівника або значення вказівника нульового члена перетворюється на помилкове; будь-яке інше значення перетворюється на істинне. Для прямої ініціалізації (8.5), первісне значення типу std :: nullptr_t може бути перетворене в первісне значення bool типу; отримане значення хибне.

Крім того, єдине місце в цьому проекті документа, де std::nullptr_tзгадується перетворення на інтегральний тип, - у розділі "Повторний_випуск":

5.2.10 Повторна інтерпретація виклику
...
(4) Вказівник може бути явно перетворений у будь-який інтегральний тип, достатньо великий для його утримання. Функція відображення визначена реалізацією. [Примітка. Він призначений не дивувати тих, хто знає структуру адреси базової машини. - кінцева примітка] Значення типу std :: nullptr_t можна перетворити на інтегральний тип; перетворення має те саме значення та обґрунтованість, що й перетворення (void *) 0 на інтегральний тип. [Примітка: Повторний_каст не можна використовувати для перетворення значення будь-якого типу у тип std :: nullptr_t. - кінцева примітка]

Отже, з цих двох спостережень можна (ІМХО) обгрунтовано припустити, що MSVCкомпілятор правильний.

РЕДАКТУВАННЯ : Однак ваше використання "функціональної нотації" може насправді говорити про зворотне! У MSVCкомпілятора немає жодних проблем із використанням перекладеного в C-стилі, наприклад:

uintptr_t answer = (uintptr_t)(nullptr);

але (як у вашому коді), він скаржиться на це:

uintptr_t answer = uintptr_t(nullptr); // error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'uintptr_t'

І все-таки з того самого проекту стандарту:

5.2.3 Явне перетворення типів (функціональне позначення)
(1) Специфікатор простого типу (7.1.6.2) або специфікатор імені типу (14.6) з подальшим укрупненим списком виразів будує значення вказаного типу, заданого списком виразів. Якщо список виразів є одиничним виразом, вираз перетворення типу еквівалентний (у визначеності та якщо визначений у значенні) відповідному виразному формулюванню (5.4). ...

"Відповідний вираз амплуа (5.4)" може стосуватися амплуа в стилі С.


0

Всі вони відповідають стандартним стандартам (посилання на проект n4659 для C ++).

nullptr визначається в [lex.nullptr] як:

Літерал покажчика - це ключове слово nullptr. Це перше значення типу std :: nullptr_t. [Примітка: ..., першозначення цього типу є нульовою константою вказівника і може бути перетворена в значення нульового вказівника або значення нульового вказівника члена.]

Навіть якщо примітки ненормативні, це дає зрозуміти, що для стандарту, nullptrяк очікується, буде перетворено в нульове значення вказівника .

Пізніше ми знаходимо в [conv.ptr]:

Константа нульового покажчика - це ціле число, що знаходиться в прямому значенні, зі значенням нуль або первинним значенням типу std :: nullptr_t. Нульова константа вказівника може бути перетворена в тип вказівника; .... Нульова константа вказівника інтегрального типу може бути перетворена в первісне значення типу std :: nullptr_t.

Знову ж таки, що вимагається стандартом, це те, що 0можна перетворити на a std::nullptr_tі що nullptrможе бути перетворено в будь-який тип вказівника.

Моє читання полягає в тому, що стандарт не вимагає того, чи nullptrможна безпосередньо перетворити його на інтегральний тип чи ні. З цього моменту:

  • MSVC суворо читає і забороняє конвертувати
  • Clang і gcc поводяться так, ніби void *було залучено посередницьку конверсію.

1
Я думаю, що це неправильно. Деякі нульові константи вказівника є цілими буквами зі значенням нуль, але nullptrце не тому, що він має неінтегральний тип std::nullptr_t. 0 може бути перетворений у std::nullptr_tзначення, але не в буквальне nullptr. Це все навмисно, std::nullptr_tє більш обмеженим типом, щоб запобігти ненавмисним перетворенням.
MSalters

@MSalters: Я думаю, що ти маєш рацію. Я хотів переробити це і зробив це неправильно. Я відредагував своє повідомлення з вашим коментарем. Дякую за твою допомогу.
Серж Баллеста
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.