cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2907 [expr.const] uninitialized glvalue of type `std::nullptr_t` appears in the constant expression #215

Open xmh0511 opened 1 year ago

xmh0511 commented 1 year ago

Full name of submitter (unless configured in github; will be published with the issue): Jim X

Consider this example

int main() {
    std::nullptr_t np; // #1
    constexpr void *p1 = np;  // #2
}

According to [dcl.init.general] p12

If no initializer is specified for an object, the object is default-initialized.

[basic.types.general] p9

Arithmetic types ([basic.fundamental]), enumeration types, pointer types, pointer-to-member types ([basic.compound]), std​::​nullptr_­t, and cv-qualified versions of these types are collectively called scalar types.

Hence, [dcl.init.general] p7.3 applies here

To default-initialize an object of type T means:

  • [...]
  • Otherwise, no initialization is performed.

So, the variable at #1 has no initialization. Then, at #2, the full-expression of the initialization comprises: lvalue-to-rvalue conversion to np([conv.lval]), and null pointer conversion([conv.ptr])

[conv.lval] p3 says

The result of the conversion is determined according to the following rules:

  • If T is cv std​::​nullptr_­t, the result is a null pointer constant ([conv.ptr]).

The lvalue-to-rvalue conversion to a glvalue of type cv std​::​nullptr_­t seems not to care whether the the glvalue is initialized. However, [basic.indet] says

When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced ([expr.ass]).

If an indeterminate value is produced by an evaluation, the behavior is undefined except in the following cases

Moreover, np is not usable in constant expressions according to [expr.const] p2, [expr.const] p3, and [expr.const] p4. So, the evaluation of the full-expression of the initialization at least violates [expr.const] p5

an operation that would have undefined behavior as specified in [intro] through [cpp], excluding [dcl.attr.assume];

an lvalue-to-rvalue conversion unless it is applied to

  • a non-volatile glvalue that refers to an object that is usable in constant expressions, or

However, GCC and Clang both accept this example, only msvc rejects it.

Suggested resolution

Anyway, [conv.lval] p3.1 implies that the result of lvalue-to-rvalue conversion to any glvlaue of type std::nullptr_t(regardless of whether it is initialized or uninitialized), the result is always a null pointer constant.

[expr.const] p5.9 and [basic.indet] p2 should have the special specification for std::nullptr_t.

frederick-vs-ja commented 1 year ago

I think we can just modify [expr.const] p5.9 as indicated:

  • (5.9.2) ... ~;~ , or
  • (5.9.3) a glvalue of type cv std::nullptr_t;

I believe that [conv.lval] p3 already implies that lvalue-to-rvalue conversion to a glvalue of type cv std​::​nullptr_­t never produces an indeterminate value, so [basic.indet] p2 seems unrelated to me.

xmh0511 commented 1 year ago

I think we can just modify [expr.const] p5.9 as indicated:

  • (5.9.2) ... ~;~ , or
  • (5.9.3) a glvalue of type cv std::nullptr_t;

I believe that [conv.lval] p3 already implies that lvalue-to-rvalue conversion to a glvalue of type cv std​::​nullptr_­t never produces an indeterminate value, so [basic.indet] p2 seems unrelated to me.

Yes, that's the suggested resolution.

so [basic.indet] p2 seems unrelated to me.

No, [basic.indet] uniformly applies all objects with automatic or dynamic storage duration that has no initialization performed. a gvalue of std::nullptr_t in this case does denote that object.

jensmaurer commented 2 months ago

CWG2907

Since we never look at the value of a std::nullptr_t object, it's irrelevant whether its value is indeterminate or erroneous.

t3nsor commented 2 months ago

Needs cv

t3nsor commented 2 months ago

Or maybe not quite. I don't know what we want to do about the volatile std::nullptr_t case.

jensmaurer commented 2 months ago

Added cv; [conv.lval] has it, too.