Bug 101681

Summary: PMF comparison to nullptr is not considered a constexpr inside a template argument
Product: gcc Reporter: Drea Pinski <pinskia>
Component: c++Assignee: Not yet assigned to anyone <unassigned>
Status: UNCONFIRMED ---    
Severity: normal CC: StevenSun2021, webrown.cpp
Priority: P3 Keywords: rejects-valid
Version: 12.0   
Target Milestone: ---   
See Also: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100313
Host: Target:
Build: Known to work:
Known to fail: Last reconfirmed:
Bug Depends on:    
Bug Blocks: 55004, 101603    

Description Drea Pinski 2021-07-29 21:05:58 UTC
Take:
template <bool> struct Z { };

struct C
{
    void f();
    Z<&C::f == nullptr> z;
};
---- CUT ---
This should be accepted in C++11 or newer.
Comment 1 Steven Sun 2021-08-06 19:08:43 UTC
The following program compiles. https://godbolt.org/z/aTvchYxYW

```
struct C {
  void f() {}
  static_assert(__builtin_constant_p(&C::f));
  static_assert(!__builtin_constant_p(&C::f == nullptr)); // not nonzero yet
};

static_assert(__builtin_constant_p(&C::f == nullptr)); // nonzero now

struct D {
  void f() {}
  static_assert(__builtin_constant_p(&C::f == nullptr));
  static_assert(!__builtin_constant_p(&D::f == nullptr));
};

static_assert(__builtin_constant_p(&C::f == nullptr));
static_assert(__builtin_constant_p(&D::f == nullptr));

```


Looks that the `&C::f` is known to be constexpr right after the function was parsed.

But only when the class completely parsed, its value was assigned. We can then compare it to nullptr.


To make code in comment0 accepted, we need some kind of `not null' mark on the expression tree. 0ne possible way is to assign the `&C::f` in advance, right after it was parsed.
Comment 2 Steven Sun 2021-08-07 00:33:57 UTC
The root cause for this is that the compiler forbids constant folding when involving PMF of an incomplete class.

https://gcc.gnu.org/git?p=gcc.git;a=blob;f=gcc/cp/expr.c;h=d16d1896f2ddd08264b389b02b9640cca332ec13;hb=refs/heads/master#l42

(gcc/cp/expr.c)
> 42   /* We can't lower this until the class is complete.  */
> 43         if (!COMPLETE_TYPE_P (DECL_CONTEXT (member)))
> 44           return cst;


If we comment this `if`, the constant folding will succeed at


(gcc/cp/expr.c)
> 67             expand_ptrmemfunc_cst (cst, &delta, &pfn);
> 68             cst = build_ptrmemfunc1 (type, delta, pfn);


solving everything.
Comment 3 Steven Sun 2021-08-07 00:43:14 UTC
By the way, in the current design, the class definition is passed twice in order we can see every member data/function declaration before parsing NSDMI and member functions.

The class is complete after parsing all declaration, which means `&C::f == nullptr` can reduce to false since that.

So, under current design, the following code compiles on GCC.
https://godbolt.org/z/fMTsf4KoM

```
struct C {
  C() {
    static_assert(&C::f != 0);  // complete type
  }
  void f() noexcept(&C::f != 0) {
    static_assert(&C::f != 0);  // complete type
  }
  static_assert(__builtin_constant_p(&C::f));        // incomplete type
  static_assert(!__builtin_constant_p(&C::f == 0));  // incomplete type
};

static_assert(&C::f != 0);  // complete type

```
Comment 4 Drea Pinski 2022-01-11 18:40:09 UTC
Note completeness of the class does not matter either:
template <bool> struct Z { };
struct C
{
    void f();
};
static_assert ((&C::f == 0) == false,"");
Z<&C::f == 0> z;

--- CUT ---
The static_assert passes just fine but the template is still rejected.