user1244932
user1244932

Reputation: 8092

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?

Upvotes: 10

Views: 2659

Answers (3)

Adrian Mole
Adrian Mole

Reputation: 51825

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.

Upvotes: 2

walnut
walnut

Reputation: 22152

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;

Upvotes: 6

Serge Ballesta
Serge Ballesta

Reputation: 148900

All are standard conformant (ref. draft n4659 for C++).

nullptr is defined in [lex.nullptr] as:

The pointer literal is the keyword nullptr. It is a prvalue of type std::nullptr_t. [ Note: ..., a prvalue of this type is a null pointer constant and can be converted to a null pointer value or null member pointer value.]

Even if notes are non normative, this one makes clear that for the standard, nullptr is expected to be converted to a null pointer value.

We later find in [conv.ptr]:

A null pointer constant is an integer literal with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; .... A null pointer constant of integral type can be converted to a prvalue of type std::nullptr_t.

Here again what is required by the standard is that 0 can be converted to a std::nullptr_t and that nullptr can be converted to any pointer type.

My reading is that the standard has no requirement on whether nullptr can be directly converted to an integral type or not. From that point on:

  • MSVC has a strict reading and forbid the conversion
  • Clang and gcc behaves as if an intermediary void * conversion was involved.

Upvotes: 0

Related Questions