handle NB comments concerning nullptr

Jens Gustedt (INRIA France)

JeanHeyd Meneide (https://thephd.dev)

2023-01-08

org: ISO/IEC JCT1/SC22/WG14 document: N3077
target: IS 9899:2023 version: 1
date: 2023-01-08 license: CC BY

US 9

This makes nullptr a null pointer constant, as well as 0, and (void*)0, but it seems to leave out (void*)nullptr which seems like a minor oversight.

Add (void*)nullptr to the list of allowed forms of a null pointer constant.

No this was not an oversight, but a choice. Already in C17 (void*)NULL is not always a null pointer constant. If NULL is (void*)0 this expands to

(void*)((void*)0)

which not a null pointer constant. The property of being so is bound to a specific syntactic derivation, namely a void* cast of an integer constant expression of value 0.

For example we have

unsigned* a = true ? (signed*)0 : (void*)0;          // invalid, initializer expression is (signed*)0
unsigned* b = true ? (signed*)0 : (void*)1;          // valid,   initializer expression is (void*)0
unsigned* c = true ? (signed*)0 : (void*)((void*)0); // valid,   initializer expression is (void*)0

As well gcc as clang diagnose the line for a but not for b or c.

The idea with the proposed wording for C23 is to make C’s type system stricter with this new constant nullptr and its corresponding type. We wanted that any modification of the type of nullptr (by cast) would strip away the property of being a null pointer constant.

Although we would not be strongly opposed to make a change, here, we prefer it as it is and do not propose any action.

US 10, US 24 and US 22

Prerequisite for a resolution of any of these.

These comments all have in common that they suggest to introduce a special conversion rule to nullptr_t from null pointer constants.

6.3.2.4 nullptr_t

1 The type nullptr_t may be converted to void, bool, or to a pointer type. The; the result is a void expression, false, or a null pointer value, respectively.

2 TheA null pointer constant or a value of type nullptr_t may be converted to itselfnullptr_t.

For completeness, they would also require an addition to the rule for explicit conversions.

6.5.4 Cast operators

4 A pointer type shall not be converted to any floating type. A floating type shall not be converted to any pointer type. The type nullptr_t shall not be converted to any type other than void, bool or a pointer type. No type other than nullptr_t shall be converted to nullptr_t. If the target type is nullptr_t, the cast expression shall be a null pointer constant or have type nullptr_t.

US 10 and US 24

US 10

Introduces incompatible semantics with C++ regarding what can be converted to nullptr_t type in the following example (rejected in C, accepted in C++):

void func(nullptr_t);
func(0);

This example should be accepted, as in C++.

US 24

Introduces a different kind of incompatible semantics with C++ regarding the following example (rejected in C, accepted in C++):

nullptr_t val;
val = 0;

These examples should be accepted, as in C++.

Possible resolution

Both comments depend actually on the status of simple assignment. A possible resolution would be

6.5.16.1 Simple assignment

Constraints

1 One of the following shall hold125):

— the left operand has an atomic, qualified, or unqualified version of the nullptr_t type and the type of the right operand is a null pointer constant or its type is nullptr_t126);

— the left operand is an atomic, qualified, or unqualified pointer, and the type of the right operand is nullptr_t;

— the left operand is an atomic, qualified, or unqualified bool, and the type of the right operand is nullptr_t;

— the left operand is an atomic, qualified, or unqualified pointer, and the type of the right operand is a null pointer constant or its type is nullptr_t; or

— the left operand has type atomic, qualified, or unqualified bool, and the right operand is a pointer or its type is nullptr_t.

US 22

Introduces incompatible semantics with C++ regarding behavior of the following examples (rejected in C, accepted in C++):

nullptr_t val;
(void)(1 ? val : 0);
(void)(1 ? nullptr : 0);

This example should be accepted, as in C++.

Since C and C++ largely disagree on the semantics of the conditional operator (lvalue/rvalue status and result type) the possible use of that operator in common code for both languages is very much restricted, anyhow, and cannot be recommended.

If WG14 still wishes to adapt the property in question it would go as follows.

6.5.14 Conditional operator

Constraints

3 One of the following shall hold for the second and third operands122):

both operands haveone operand has nullptr_t type and the other is a null pointer constant or has nullptr_t type;

Semantics

7 If both the second and third operands are pointers, the result type is a pointer to a type qualified with all the type qualifiers of the types referenced by both operands; if one is a null pointer constant (other than a pointer) or has type nullptr_t and the other is a pointer, the result type is the pointer type; if both the second and third operands have nullptr_t type, the result also has that typeone operand has nullptr_t type and the other is a null pointer constant or has nullptr_t type, the result has nullptr_t type. Furthermore, if both operands are pointers to compatible types or to differently qualified versions of compatible types, the result type is a pointer to an appropriately qualified version of the composite type; if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void.

Polls

  1. Does WG14 want to resolve NB comments US 10 and US 24 as indicated above?

  2. Does WG14 want to resolve NB comments US 22 as indicated above?

NEN/NL5

The nullptr predefined keyword and nullptr_t type was added, representing a separate way to access a null pointer constant. This may be redundant and unnecessary and the problems it addresses not suitably sufficient to justify keeping it. Users voiced concern over keeping it in the C Standard, and a few audited existing codebases and existing implementations to see if there was any need beyond just settling on an existing null pointer constant such as (void*)0. Additional users found existing practice where 0 and 0L were being used as the null pointer constant (e.g., in definitions of NULL) for embedded chips like those employed by U.S. vendor [REDACTED]. While some vendors responded positively to being encouraged to change from using 0 and 0L to (void*)0, others either did not respond or rejected outright the idea that they would change their NULL macro from 0 to (void*)0. Some platforms use a special __null even for their non-C++ platforms. Other vendors, such as [REDACTED], used 0/0L explicitly unless a macro turned on to opt into a newer (void*)0 definition of the macro. It is noted these users and implementations were in the vast minority, even though they do definitively exist. Given this additional information, poll the C Standards Committee again if the nullptr changes from N3042 should remain or be removed completely from the C Standard.

Other than repeating the confusing state of affairs of null pointer constants in the field, this NB comment does not seem to add new information to the usefulness (or not) of the nullptr feature itself.

In the contrary, the given arguments make it clear that we owe our users a new portable tool that allows them to circumvent all the ambiguities of the term “null pointer constant” and to overcome the lack of cooperation of some implementations to improve the situation.

All of this has been discussed at length during the year-long process in WG14. In particular, an initial proposal that would have gone merely in a direction of replacing the use of “integer constant expressions of value zero” by (void*)0 has not been seen favorable by WG14, which explicitly wanted a version that is better suitable for cross language use in C and C++ and that explicitly expressed a need for the type nullptr_t as it exists in C++.

Since there is no new information, here, we think that the found consensus should not be questioned.

US 25

This footnote can’t be implemented because it would require the implementation to inspect the value of an object as an assignment constraint at compile time. Consider:

nullptr_t lhs_val;
nullptr_t rhs_val = (nullptr_t)(void *)1; // UB
lhs_val = rhs_val; // Expects a constraint violation here per the footnote but nothing normative

I think the footnote should read that it’s undefined behavior rather than a constraint violation.

This refers to the footnote

126) The assignment of an object of type nullptr_t with a value of another type, even if the value is a null pointer constant, is a constraint violation.

First, the assessment that the definition of rhs_val is UB is wrong. In fact, the initializer performs a cast from a pointer value to nullptr_t. According to the constraints in 6.5.4 p4 for casts

No type other than nullptr_t shall be converted to nullptr_t.

So this is a constraint violation, already, and would remain so even with the modifications to conversions discussed above.

Also nothing in that footnote suggests that it would be a constraint violation to do the assignment to lhs_val, since rhs_val has the same type as lhs_val, namely nullptr_t.

(Also, the example in the comment suggest the UB to have already happened during initialization or assignment to rhs_val. Discussing any further behavior of the code after UB is not very productive.)

So we think that no action is needed for this NB comment.

US 23

Introduces incompatible semantics with C++ regarding the behavior of the following examples (accepted in C, rejected in C++):

nullptr_t val;
bool b1 = val;
bool b2 = nullptr;

These examples should be rejected, as in C++.

Indeed these examples are accepted in C23 as proposed. This has been a diligent choice.

C traditionally allows much more implicit conversion than C++: all scalar values in C17 implicitly convert to bool. Our users can expect that this stays this way, in particular because in C a common use pattern is to have a value (being a pointer value or a null pointer) hidden inside a macro. It would be very annoying for C programmers if the status of code like

bool b3 = BASE;

would depend on BASE expanding to

Having a behavior for nullptr that would be different from all other scalars in such a context of implicit conversion to bool would be an impediment for an adoption of the feature in parts of the C community. If it is defined as a scalar, it should act as a scalar.

It seems that this property has been discussed during the processing for this feature, so we don’t think that the consensus should be questioned, here.

US 21

Introduces incompatible semantics with C++ regarding the following example (rejected in C, accepted in C++):

(nullptr_t)nullptr;

because nullptr_t is not void, bool, or a pointer type (so it cannot be cast to itself). Note, this might be editorial because the last sentence of the para says “No type other than nullptr_t shall be converted to nullptr_t” but it’s unclear because of the use of “only” in the preceding sentence.

Change the penultimate sentence to:

The type nullptr_t shall not be converted to any type other than void, bool, nullptr_t, or a pointer type.

While we would not be opposed to such a change we don’t think that it is necessary. In particular, the clause for the cast operators already seems to assume that when the target type is the same as the source type, that no conversion is actually performed.

For a different resolution see the proposed text for “Cast Operators”, above.

GB-071 and FR-073

These two comments address the same problem, namely equality operators.

GB-071

Almost all places that allow operands of type nullptr_t do so independent of whether the particular operand is a null pointer constant or another expression of that type. However, the rules for equality operators only allow comparison of a pointer that is not a null pointer constant with a nullptr_t value that is a null pointer constant, not with any other nullptr_t value. Since it seems C++ implementations allow such comparisons, disallowing them in C might not be intentional.

If it’s not desired to disallow such comparisons after all, in the last bullet point in paragraph 2, change “is a null pointer constant” to “is a null pointer constant or has type nullptr_t”. In paragraph 6, change “the other is a null pointer constant, the null pointer constant is converted” to “the other is a null pointer constant or has type nullptr_t, the null pointer constant or operand of type nullptr_t is converted”.

FR-073

There is a missing case for the comparison of pointers with type nullptr_t see accompanying document

Possible resolution

We follow the resolution proposed by AFNOR:

6.5.9 Equality operators

Constraints

– one operand is a pointer and the other is a null pointer constant or has type nullptr_t.

Semantics

6 Otherwise, at least one operand is a pointer. If one operand is a pointer and the other is a null pointer constant or has type nullptr_t, they compare equal if the former is a null pointer, the null pointer constant is converted to the type of the pointer. If one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void, the former is converted to the type of the latter.

Poll

  1. Does WG14 want to resolve NB comments GB-071 and FR-073 as indicated above?