Bisakah nullptr dikonversi menjadi uintptr_t? Kompiler yang berbeda tidak setuju

10

Pertimbangkan program ini:

#include <cstdint>
using my_time_t = uintptr_t;

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

Gagal mengompilasi dengan 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

tapi dentang (9.0.1) dan gcc (9.2.1) "makan" kode ini tanpa kesalahan.

Saya suka perilaku MSVC, tetapi apakah itu dikonfirmasi oleh standar? Dengan kata lain apakah bug di dentang / gcc atau dimungkinkan untuk menafsirkan standar bahwa ini adalah perilaku yang benar dari gcc / dentang?

pengguna1244932
sumber
2
Saya membaca ini sebagai salinan inisialisasi dari pemeran gaya-fungsi. Itu kemudian akan ditafsirkan oleh kompiler sebagai salah satu pemain C ++ "bahkan jika itu tidak dapat dikompilasi". Mungkin ada ketidakkonsistenan di antara kompiler mengenai bagaimana pemeran diinterpretasikan
wreckgar23
Sejauh yang saya ketahui MSVC v19.24 tidak mendukung mode C ++ 11. Apakah maksud Anda C ++ 14 atau C ++ 17?
walnut

Jawaban:

5

Menurut pendapat saya MSVC tidak berperilaku sesuai standar.

Saya mendasarkan jawaban ini pada C ++ 17 (draft N4659), tetapi C ++ 14 dan C ++ 11 memiliki kata-kata yang setara.

my_time_t(nullptr)adalah ekspresi-postfix dan karena my_time_tmerupakan tipe dan (nullptr)merupakan ekspresi tunggal dalam daftar initializer yang di-kurung, itu persis sama dengan ekspresi cast yang eksplisit. ( [expr.type.conv] / 2 )

Pemeran eksplisit mencoba beberapa pemeran C ++ spesifik yang berbeda (dengan ekstensi), khususnya juga reinterpret_cast. ( [expr.cast] /4.4 ) Gips yang dicoba sebelumnya reinterpret_castadalah const_castdan static_cast(dengan ekstensi dan juga dalam kombinasi), tetapi tidak ada yang dapat dilemparkan std::nullptr_tke tipe integral.

Tetapi reinterpret_cast<my_time_t>(nullptr)harus berhasil karena [expr.reinterpret.cast] / 4 mengatakan bahwa nilai tipe std::nullptr_tdapat dikonversi ke tipe integral seolah-olah oleh reinterpret_cast<my_time_t>((void*)0), yang mungkin karena my_time_t = std::uintptr_tharus merupakan tipe yang cukup besar untuk mewakili semua nilai pointer dan dalam kondisi ini paragraf standar yang sama memungkinkan konversi void*ke tipe integral.

Sangat aneh bahwa MSVC mengizinkan konversi jika notasi cor daripada notasi fungsional digunakan:

const my_time_t t = (my_time_t)nullptr;
kenari
sumber
1
Ya. Perhatikan bahwa static_castsecara khusus ada beberapa kasus yang dimaksudkan untuk menjebak tangga cor gaya-C (misalnya, cor gaya-C ke basis yang ambigu lebih buruk static_castdaripada reinterpret_cast), tetapi tidak ada yang berlaku di sini.
TC
my_time_t(nullptr)secara definisi sama (my_time_t)nullptr, jadi MSVC tentu salah menerima dan menolak yang lain.
Richard Smith
2

Meskipun saya tidak dapat menemukan penyebutan eksplisit dalam Standar Draf C ++ Kerja ini (mulai 2014) bahwa konversi dari std::nullptr_tke tipe integral dilarang, ada juga tidak menyebutkan bahwa konversi semacam itu diperbolehkan!

Namun, kasus konversi dari std::nullptr_tke bool adalah eksplisit disebutkan:

4.12 Konversi Boolean
Nilai awal aritmatika, enumerasi yang tidak dicentang, penunjuk, atau penunjuk ke tipe anggota dapat dikonversi ke nilai awal tipe bool. Nilai nol, nilai pointer nol, atau nilai pointer anggota null dikonversi menjadi false; nilai lain dikonversi menjadi true. Untuk inisialisasi langsung (8.5), nilai dari tipe std :: nullptr_t dapat dikonversi ke nilai awal tipe bool; nilai yang dihasilkan salah.

Lebih jauh, satu - satunya tempat dalam draft dokumen ini di mana konversi dari std::nullptr_tke tipe integral disebutkan, ada di bagian "reinterpret_cast":

5.2.10 Menafsirkan kembali cast
...
(4) Suatu pointer dapat secara eksplisit dikonversi ke tipe integral yang cukup besar untuk menahannya. Fungsi pemetaan ditentukan oleh implementasi. [Catatan: Ini dimaksudkan untuk tidak mengejutkan bagi mereka yang tahu struktur pengalamatan mesin yang mendasarinya. - end note] Nilai tipe std :: nullptr_t dapat dikonversi ke tipe integral; konversi memiliki arti dan validitas yang sama dengan konversi (batal *) 0 ke tipe integral. [Catatan: Reinterpret_cast tidak dapat digunakan untuk mengonversi nilai tipe apa pun menjadi tipe std :: nullptr_t. - catatan akhir]

Jadi, dari dua pengamatan ini, salah satu bisa (IMHO) cukup dugaan bahwa MSVCcompiler benar.

EDIT : Namun, penggunaan "notasi fungsional" Anda sebenarnya dapat menyarankan sebaliknya! The MSVCcompiler tidak memiliki masalah menggunakan cor C-gaya, misalnya:

uintptr_t answer = (uintptr_t)(nullptr);

tetapi (seperti dalam kode Anda), ia mengeluh tentang ini:

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

Namun, dari Draf Standar yang sama:

5.2.3 Konversi tipe eksplisit (notasi fungsional)
(1) Sebuah specifier tipe sederhana (7.1.6.2) atau specename-specifier (14.6) diikuti oleh daftar ekspresi yang dipatenkan membangun nilai dari tipe yang ditentukan diberikan daftar ekspresi. Jika daftar ekspresi adalah ekspresi tunggal, ekspresi konversi tipe adalah setara (dalam definisi, dan jika didefinisikan dalam arti) dengan ekspresi cast yang sesuai (5.4). ...

"Ekspresi pemeran yang sesuai (5,4)" dapat merujuk pada pemeran gaya-C.

Adrian Mole
sumber
0

Semua adalah konforman standar (ref. Draft n4659 untuk C ++).

nullptr didefinisikan dalam [lex.nullptr] sebagai:

Pointer literal adalah kata kunci nullptr. Ini adalah nilai dari tipe std :: nullptr_t. [Catatan: ..., nilai awal jenis ini adalah konstanta penunjuk nol dan dapat dikonversi ke nilai penunjuk nol atau nilai penunjuk anggota nol.]

Sekalipun nota non normatif, nota ini memperjelas bahwa untuk standar, nullptrdiharapkan dikonversi ke nilai pointer nol .

Kami kemudian menemukan di [conv.ptr]:

Konstanta penunjuk nol adalah bilangan bulat integer dengan nilai nol atau nilai dari tipe std :: nullptr_t. Konstanta penunjuk nol dapat dikonversi ke jenis penunjuk; .... Konstanta penunjuk nol dari tipe integral dapat dikonversi ke nilai awal tipe std :: nullptr_t.

Di sini sekali lagi apa yang diperlukan oleh standar adalah yang 0dapat dikonversi menjadi std::nullptr_tdan yang nullptrdapat dikonversi ke jenis pointer apa pun.

Bacaan saya adalah bahwa standar tidak memiliki persyaratan apakah nullptrdapat langsung dikonversi ke tipe integral atau tidak. Sejak saat itu:

  • MSVC memiliki pembacaan yang ketat dan melarang konversi
  • Dentang dan gcc berperilaku seolah-olah void *konversi perantara terlibat.
Serge Ballesta
sumber
1
Saya pikir ini salah. Beberapa konstanta penunjuk nol adalah bilangan bulat integer dengan nilai nol, tetapi nullptrbukan karena memiliki tipe non-integral std::nullptr_t. 0 dapat dikonversi ke std::nullptr_tnilai, tetapi tidak ke literal nullptr. Ini semua disengaja, std::nullptr_tadalah jenis yang lebih terbatas untuk mencegah konversi yang tidak diinginkan.
MSalters
@ MSalters: Saya pikir Anda benar. Saya ingin menulis ulang dan melakukannya dengan salah. Saya telah mengedit posting saya dengan komentar Anda. Terima kasih untuk bantuannya.
Serge Ballesta