Nullptr có thể được chuyển đổi thành uintptr_t không? Trình biên dịch khác nhau không đồng ý


10

Hãy xem xét chương trình này:

#include <cstdint>
using my_time_t = uintptr_t;

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

Không thể biên dịch với 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

nhưng clang (9.0.1) và gcc (9.2.1) "ăn" mã này mà không có bất kỳ lỗi nào.

Tôi thích hành vi MSVC, nhưng nó có được xác nhận theo tiêu chuẩn không? Nói cách khác, đó là lỗi trong clang / gcc hay có thể diễn giải tiêu chuẩn rằng đây là hành vi đúng từ gcc / clang?


2
Tôi đọc nó như là khởi tạo sao chép từ một kiểu kiểu hàm. Điều đó sau đó được trình biên dịch diễn giải là một trong các phôi C ++ "ngay cả khi nó không thể được biên dịch". Có thể có sự không nhất quán giữa các trình biên dịch về cách diễn viên được diễn giải
wreckgar23

Theo như tôi biết thì MSVC v19.24 không hỗ trợ chế độ C ++ 11. Ý bạn là C ++ 14 hay C ++ 17 thay vào đó?
quả óc chó ngày

Câu trả lời:


5

Theo tôi, MSVC không tuân thủ tiêu chuẩn.

Tôi đang căn cứ câu trả lời này trên C ++ 17 (bản nháp N4659), nhưng C ++ 14 và C ++ 11 có từ ngữ tương đương.

my_time_t(nullptr)là một biểu thức postfix và bởi vì my_time_tlà một loại và (nullptr)là một biểu thức trong danh sách khởi tạo được ngoặc đơn, nó hoàn toàn tương đương với một biểu thức truyền rõ ràng. ( [expr.type.conv] / 2 )

Các diễn viên rõ ràng cố gắng thử một vài diễn viên C ++ cụ thể khác nhau (có phần mở rộng), đặc biệt là reinterpret_cast. ( [expr.cast] /4.4 ) Các diễn viên đã thử trước đó reinterpret_castconst_caststatic_cast(với các phần mở rộng và cũng kết hợp), nhưng không ai trong số này có thể std::nullptr_tchuyển thành một loại tích phân.

Nhưng reinterpret_cast<my_time_t>(nullptr)nên thành công vì [expr.reinterpret.cast] / 4 nói rằng giá trị của loại std::nullptr_tcó thể được chuyển đổi thành loại tích phân như thể reinterpret_cast<my_time_t>((void*)0), điều đó là có thể bởi vì my_time_t = std::uintptr_tphải là loại đủ lớn để biểu thị tất cả các giá trị con trỏ và trong điều kiện này cùng một đoạn tiêu chuẩn cho phép chuyển đổi void*thành một loại tích phân.

Điều đặc biệt lạ là MSVC không cho phép chuyển đổi nếu sử dụng ký hiệu truyền thay vì ký hiệu chức năng:

const my_time_t t = (my_time_t)nullptr;

1
Vâng. Lưu ý rằng static_castđặc biệt có một số trường hợp có ý định bẫy thang đúc kiểu C (ví dụ: đúc kiểu C đến một cơ sở mơ hồ là một hình dạng xấu static_castthay vì một reinterpret_cast), nhưng không áp dụng ở đây.
TC

my_time_t(nullptr)theo định nghĩa giống như (my_time_t)nullptr, vì vậy MSVC chắc chắn sai khi chấp nhận cái này và từ chối cái kia.
Richard Smith

2

Mặc dù tôi không thể tìm thấy đề cập rõ ràng nào trong Tiêu chuẩn Dự thảo C ++ này (từ 2014) rằng việc chuyển đổi từ std::nullptr_tloại tích phân bị cấm, nhưng cũng không có đề cập đến việc chuyển đổi như vậy được cho phép!

Tuy nhiên, trường hợp chuyển đổi từ std::nullptr_tsang bool được đề cập rõ ràng:

4.12 Chuyển đổi Boolean
Một giá trị của số học, liệt kê không theo tỷ lệ, con trỏ hoặc con trỏ thành loại thành viên có thể được chuyển đổi thành giá trị của loại bool. Giá trị 0, giá trị con trỏ null hoặc giá trị con trỏ null được chuyển thành false; bất kỳ giá trị nào khác được chuyển đổi thành true. Đối với khởi tạo trực tiếp (8.5), một giá trị của loại std :: nullptr_t có thể được chuyển đổi thành một giá trị của loại bool; giá trị kết quả là sai.

Hơn nữa, vị trí duy nhất trong tài liệu dự thảo này có chuyển đổi từ std::nullptr_tloại tích phân được đề cập, là trong phần "reinterpret_cast":

5.2.10 Diễn giải lại
...
(4) Một con trỏ có thể được chuyển đổi rõ ràng thành bất kỳ loại tích phân nào đủ lớn để giữ nó. Hàm ánh xạ được xác định theo thực hiện. [Lưu ý: Dự định sẽ không gây ngạc nhiên cho những người biết cấu trúc địa chỉ của máy bên dưới. - lưu ý cuối] Giá trị của loại std :: nullptr_t có thể được chuyển đổi thành loại tích phân; chuyển đổi có cùng ý nghĩa và hiệu lực như chuyển đổi (void *) 0 thành loại tích phân. [Lưu ý: Không thể sử dụng reinterpret_cast để chuyển đổi giá trị của bất kỳ loại nào thành loại std :: nullptr_t. - lưu ý cuối]

Vì vậy, từ hai quan sát này, người ta có thể (IMHO) phỏng đoán một cách hợp lý rằng MSVCtrình biên dịch là chính xác.

EDIT : Tuy nhiên, việc bạn sử dụng "ký hiệu chức năng" thực sự có thể gợi ý ngược lại! Trình MSVCbiên dịch không có vấn đề gì khi sử dụng kiểu C, ví dụ:

uintptr_t answer = (uintptr_t)(nullptr);

nhưng (như trong mã của bạn), nó phàn nàn về điều này:

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

Tuy nhiên, từ cùng một Tiêu chuẩn Dự thảo:

5.2.3 Chuyển đổi loại rõ ràng (ký hiệu chức năng)
(1) Trình xác định kiểu đơn giản (7.1.6.2) hoặc kiểu chữ-specifier (14.6) theo sau là danh sách biểu thức được ngoặc đơn xây dựng một giá trị của kiểu đã chỉ định cho danh sách biểu thức. Nếu danh sách biểu thức là một biểu thức đơn, biểu thức chuyển đổi loại là tương đương (theo định nghĩa và nếu được định nghĩa theo nghĩa) với biểu thức truyền tương ứng (5.4). ...

"Biểu thức diễn viên tương ứng (5.4)" có thể đề cập đến một kiểu đúc C.


0

Tất cả đều tuân thủ tiêu chuẩn (tham khảo dự thảo n4659 cho C ++).

nullptr được định nghĩa trong [lex.nullptr] là:

Con trỏ bằng chữ là từ khóa nullptr. Đây là một giá trị của loại std :: nullptr_t. [Lưu ý: ..., một giá trị của loại này là hằng số con trỏ null và có thể được chuyển đổi thành giá trị con trỏ null hoặc giá trị con trỏ thành viên null.]

Ngay cả khi các ghi chú là không quy tắc, điều này làm rõ rằng đối với tiêu chuẩn, nullptrdự kiến ​​sẽ được chuyển đổi thành giá trị con trỏ null .

Sau này chúng tôi tìm thấy trong [conv.ptr]:

Hằng số con trỏ null là một số nguyên có giá trị bằng 0 hoặc giá trị của kiểu std :: nullptr_t. Một hằng con trỏ null có thể được chuyển đổi thành một loại con trỏ; .... Một hằng số con trỏ null của kiểu tích phân có thể được chuyển đổi thành một giá trị của kiểu std :: nullptr_t.

Ở đây một lần nữa những gì được yêu cầu bởi tiêu chuẩn là 0có thể được chuyển đổi thành a std::nullptr_tnullptrcó thể được chuyển đổi thành bất kỳ loại con trỏ nào.

Tôi đọc được rằng tiêu chuẩn không có yêu cầu về việc nullptrcó thể được chuyển đổi trực tiếp thành một loại tích phân hay không. Từ lúc đó trở đi:

  • MSVC đã đọc nghiêm ngặt và cấm chuyển đổi
  • Clang và gcc hành xử như thể một void *chuyển đổi trung gian có liên quan.

1
Tôi nghĩ rằng điều này là sai. Một số hằng con trỏ null là số nguyên có giá trị bằng 0, nhưng nullptrkhông phải vì nó có kiểu không tách rời std::nullptr_t. 0 có thể được chuyển đổi thành một std::nullptr_tgiá trị, nhưng không phải bằng chữ nullptr. Đây là tất cả có chủ ý, std::nullptr_tlà một loại hạn chế hơn để ngăn chặn chuyển đổi ngoài ý muốn.
MSalters

@MSalters: Tôi nghĩ rằng bạn đúng. Tôi muốn điều chỉnh lại và làm sai. Tôi đã chỉnh sửa bài viết của tôi với bình luận của bạn. Cảm ơn sự giúp đỡ của bạn.
Serge Ballesta ngày
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.