Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can nullptr be converted to uintptr_t? Different compilers disagree

Consider this program:

#include <cstdint>
using my_time_t = uintptr_t;

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

It failed to compile with 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

but clang (9.0.1) and gcc (9.2.1) "eat" this code without any errors.

I like the MSVC behaviour, but is it confirmed by standard? In other words is it bug in clang/gcc or it is possible to interpret standard that this is right behaviour from gcc/clang?

like image 464
user1244932 Avatar asked Mar 03 '20 12:03

user1244932


2 Answers

In my opinion MSVC is not behaving standard-conform.

I am basing this answer on C++17 (draft N4659), but C++14 and C++11 have equivalent wording.

my_time_t(nullptr) is a postfix-expression and because my_time_t is a type and (nullptr) is a single expression in a parenthesized initializer list, it is exactly equivalent to an explicit cast expression. ([expr.type.conv]/2)

The explicit cast tries a few different specific C++ casts (with extensions), in particular also reinterpret_cast. ([expr.cast]/4.4) The casts tried before reinterpret_cast are const_cast and static_cast (with extensions and also in combination), but none of these can cast std::nullptr_t to an integral type.

But reinterpret_cast<my_time_t>(nullptr) should succeed because [expr.reinterpret.cast]/4 says that a value of type std::nullptr_t can be converted to an integral type as if by reinterpret_cast<my_time_t>((void*)0), which is possible because my_time_t = std::uintptr_t should be a type large enough to represent all pointer values and under this condition the same standard paragraph allows the conversion of void* to an integral type.

It is particularly strange that MSVC does allow the conversion if cast notation rather than functional notation is used:

const my_time_t t = (my_time_t)nullptr;
like image 171
walnut Avatar answered Nov 08 '22 17:11

walnut


Although I can find no explicit mention in this Working Draft C++ Standard (from 2014) that conversion from std::nullptr_t to an integral type is forbidden, there is also no mention that such a conversion is allowed!

However, the case of conversion from std::nullptr_t to bool is explicitly mentioned:

4.12 Boolean conversions
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true. For direct-initialization (8.5), a prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false.

Further, the only place in this draft document where conversion from std::nullptr_t to an integral type is mentioned, is in the "reinterpret_cast" section:

5.2.10 Reinterpret cast
...
(4) A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is implementation-defined. [ Note: It is intended to be unsurprising to those who know the addressing structure of the underlying machine. — end note ] A value of type std::nullptr_t can be converted to an integral type; the conversion has the same meaning and validity as a conversion of (void*)0 to the integral type. [Note: A reinterpret_cast cannot be used to convert a value of any type to the type std::nullptr_t. — end note ]

So, from these two observations, one could (IMHO) reasonably surmise that the MSVC compiler is correct.

EDIT: However, your use of the "functional notation cast" may actually suggest the opposite! The MSVC compiler has no problem using a C-style cast in, for example:

uintptr_t answer = (uintptr_t)(nullptr);

but (as in your code), it complains about this:

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

Yet, from the same Draft Standard:

5.2.3 Explicit type conversion (functional notation)
(1) A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed by a parenthesized expression-list constructs a value of the specified type given the expression list. If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). ...

The "corresponding cast expression (5.4)" can refer to a C-style cast.

like image 39
Adrian Mole Avatar answered Nov 08 '22 15:11

Adrian Mole