[v4,1/2] c++: Initial support for P0847R7 (Deducing this) [PR102609]

Message ID ZG2WDVCdLtacVkulha-uk_Zc_jGWnffHmnXdaLViuAxMIESijh7Y6-Y-dxnq6e7njeAU47WSBVbTYkBKvY-U8G3xemu8dCalJ9bdv4eqo5E=@protonmail.com
State Accepted
Headers
Series [v4,1/2] c++: Initial support for P0847R7 (Deducing this) [PR102609] |

Checks

Context Check Description
snail/gcc-patch-check success Github commit url

Commit Message

waffl3x Nov. 5, 2023, 3:06 p.m. UTC
  Bootstrapped and tested on x86_64-linux with no regressions.

I originally threw this e-mail together last night, but threw in the
towel when I thought I saw tests failing and went to sleep. I did a
proper bootstrap and comparison and whatnot and found that there were
thankfully no regressions.

Anyhow, the first patch feels ready for trunk, the second needs at
least one review, I'll write more on that in the second e-mail though.
I put quite a lot into the commit message, in hindsight I think I may
have gone overboard, but that isn't something I'm going to rewrite at
the moment. I really want to get these patches up for review so they
can be finalized.

I'm also including my usual musings on things that came up as I was
polishing off the patches. I reckon some of them aren't all that
important right now but I would rather toss them in here than forget
about them.

I'm starting to think that we should have a general macro that
indicates whether an implicit object argument should be passed in the
call. It might be more clear than what is currently present. I've also
noticed that there's a fair amount of places where instead of using
DECL_NONSTATIC_MEMBER_FUNCTION_P the code checks if tree_code of the
type is a METHOD_TYPE, which is exactly what the aforementioned macro
does.

In build_min_non_dep_op_overload I reversed the branches of a condition
because it made more sense with METHOD_TYPE first so it doesn't have to
take xobj member functions into account on both branches. I am slightly
concerned that flipping the branch around might have consequences,
hence why I am mentioning it. Realistically I think it's probably fine
though.

I have a test prepared for diagnosing virtual specifiers on xobj member
functions, but it's got some issues so I won't be including it with the
following diagnostic patch. Diagnostics for virtual specifiers are
still implemented, it's just the test that is having trouble. I mostly
had a hard time working out edge cases, and the standard doesn't
actually properly specify what the criteria for overriding a function
is so I've been stumped on what behavior I want it to have. So for the
time being, it only diagnoses uses of virtual on xobj member functions,
while errors for final and override are handled by code that is already
present. This can result in multiple errors, but again, I don't know
how I want to handle it yet, especially since the standard doesn't
specify this stuff very well.

BTW let me know if there's anything you would prefer to be done
differently in the changelog, I am still having trouble writing them
and I'm usually uncertain if I'm writing them properly.

Alex
  

Comments

waffl3x Nov. 7, 2023, 4:24 a.m. UTC | #1
I noticed I made a bit of a mistake in grokdeclarator:find_xobj_parm, 
this code:
```
if (!parm_list || parm_list == void_list_node)
  return false;
if (TREE_PURPOSE (parm_list) != this_identifier)
  return false;
```
Can be simplified to this code:
```
if (!parm_list || TREE_PURPOSE (parm_list) != this_identifier)
  return false;
```

While working on lambda support I found that I need to find the xobj
parameter before we get to grokdeclarator, so I was going to reuse the
same code. After looking at it I realized, hey, I wonder what
void_list_node's purpose field holds. So I checked, and found that
tree.cc:build_common_tree_nodes initializes void_list_node with a
NULL_TREE for it's purpose field.

It's obviously not going to be a big deal but simpler code is better. I
will most likely fix this if I have time later just so the code doesn't
look semantically different in the lambda support patch.

Alex
  
waffl3x Nov. 7, 2023, 2:45 p.m. UTC | #2
I guess I'll be attaching all new e-mails here.

I found a new, kinda scary issue.

```
bool
start_preparsed_function (tree decl1, tree attrs, int flags)
{
  tree ctype = NULL_TREE;
  bool doing_friend = false;

  /* Sanity check.  */
  gcc_assert (VOID_TYPE_P (TREE_VALUE (void_list_node)));
  gcc_assert (TREE_CHAIN (void_list_node) == NULL_TREE);

  tree fntype = TREE_TYPE (decl1);
  if (TREE_CODE (fntype) == METHOD_TYPE)
    ctype = TYPE_METHOD_BASETYPE (fntype);
  else if (DECL_XOBJ_MEMBER_FUNC_P (decl1))
    ctype = DECL_CONTEXT (decl1);
```

```
  if (ctype && !doing_friend && !DECL_STATIC_FUNCTION_P (decl1))
    {
      /* We know that this was set up by `grokclassfn'.  We do not
         wait until `store_parm_decls', since evil parse errors may
         never get us to that point.  Here we keep the consistency
         between `current_class_type' and `current_class_ptr'.  */
      tree t = DECL_ARGUMENTS (decl1);

      gcc_assert (t != NULL_TREE && TREE_CODE (t) == PARM_DECL);
      gcc_assert (TYPE_PTR_P (TREE_TYPE (t))
                        || DECL_XOBJ_MEMBER_FUNC_P (decl1));

      cp_function_chain->x_current_class_ref
        = cp_build_fold_indirect_ref (t);
      /* Set this second to avoid shortcut in cp_build_indirect_ref.  */
      cp_function_chain->x_current_class_ptr = t;

      /* Constructors and destructors need to know whether they're "in
         charge" of initializing virtual base classes.  */
      /* SNIP IRRELEVANT */
    }
```

I made changes in this function, which suddenly sent execution into the
second code block. It seems like this would have been being bypassed
until the fix at the top of the function. Initially this was to fix a
problem with lambdas, but suddenly a lot of other stuff seems to be
breaking. I haven't run the tests yet but... I have a really bad
feeling about this.

So my concerns here are, one, this seems kind of important upon looking
at it, what kind of stuff might have been broken when this was being
bypassed that I didn't notice? And two, how in the world was it working
when this was being bypassed?

I have a hunch that some of the reinterpretation and "just works"
behavior might have had something to do with this block of code being
bypassed. I also suspect that this area will need some changes to make
by-value xobj parameters work. However, I'm a little confused at why
this block is necessary at all. Like I have noted before, when
attempting to call a by-value xobj member function, if there are no
viable conversions, the call will fail. So it's checking for that
somewhere.

normal.C: In explicit object member function 'uintptr_t S::f(this uintptr_t)':
normal.C:15:33: error: invalid type argument (have 'uintptr_t' {aka 'long unsigned int'})
   15 |   uintptr_t f(this uintptr_t n) {
      |                                 ^
normal.C: In explicit object member function 'uintptr_t S::g(this FromS)':
normal.C:18:34: error: invalid type argument (have 'FromS')
   18 |   uintptr_t g(this FromS from_s) {
      |                                  ^

But now that we are entering this code block, (when compiling
explicit-obj-by-value2.C) these errors are popping up. Why now? Why
isn't this being handled in the same place other things are? How
important is this block of code really? Is this the origin of the weird
errors where rvalue refs are being accepted for functions that take
const rvalue refs?

Is this code just setting up the 'this' pointer? I have so many guesses
and so many questions. I don't think I can just bypass it though, but
maybe I can? This is another one that feels really deep down the rabbit
hole so I would appreciate any insight that can be provided.

Anyway, I had thought that I probably just need to change how
build_over_call works to get passing to by-value xobj params to work.
But now that I've found this code and gotten these new errors to
surprise me, now I'm a little worried.

If it's just for the 'this' pointer then it's probably fine. I need to
sleep now, I hadn't planned on looking at this for as long as I did but
I got sucked in. I think tomorrow I will go back to bypassing this code
block and try to make changes to build_over_call works and see if that
does the trick. But things feel all over the place now so I'm a little
concerned about what else I might be neglecting.

Thanks,
Alex
  
Jason Merrill Nov. 9, 2023, 9:53 p.m. UTC | #3
On 11/5/23 10:06, waffl3x wrote:
> Bootstrapped and tested on x86_64-linux with no regressions.
> 
> I originally threw this e-mail together last night, but threw in the
> towel when I thought I saw tests failing and went to sleep. I did a
> proper bootstrap and comparison and whatnot and found that there were
> thankfully no regressions.
> 
> Anyhow, the first patch feels ready for trunk, the second needs at
> least one review, I'll write more on that in the second e-mail though.
> I put quite a lot into the commit message, in hindsight I think I may
> have gone overboard, but that isn't something I'm going to rewrite at
> the moment. I really want to get these patches up for review so they
> can be finalized.
> 
> I'm also including my usual musings on things that came up as I was
> polishing off the patches. I reckon some of them aren't all that
> important right now but I would rather toss them in here than forget
> about them.
> 
> I'm starting to think that we should have a general macro that
> indicates whether an implicit object argument should be passed in the
> call. It might be more clear than what is currently present. I've also
> noticed that there's a fair amount of places where instead of using
> DECL_NONSTATIC_MEMBER_FUNCTION_P the code checks if tree_code of the
> type is a METHOD_TYPE, which is exactly what the aforementioned macro
> does.

Agreed.

> In build_min_non_dep_op_overload I reversed the branches of a condition
> because it made more sense with METHOD_TYPE first so it doesn't have to
> take xobj member functions into account on both branches. I am slightly
> concerned that flipping the branch around might have consequences,
> hence why I am mentioning it. Realistically I think it's probably fine
> though.

Agreed.

> BTW let me know if there's anything you would prefer to be done
> differently in the changelog, I am still having trouble writing them
> and I'm usually uncertain if I'm writing them properly.

> 	(DECL_FUNCTION_XOBJ_FLAG): Define.

This is usually "New macro" or just "New".

> 	* decl.cc (grokfndecl): New param XOBJ_FUNC_P, for xobj member
> 	functions set DECL_FUNCTION_XOBJ_FLAG and don't set
> 	DECL_STATIC_FUNCTION_P.
> 	(grokdeclarator): Check for xobj param, clear it's purpose and set
> 	is_xobj_member_function if it is present.  When flag set, don't change
>         type to METHOD_TYPE, keep it as FUNCTION_TYPE.
> 	Adjust call to grokfndecl, pass is_xobj_member_function.

These could be less verbose; for grokfndecl it makes sense to mention 
the new parameter, but otherwise just saying "handle explicit object 
member functions" is enough.

> It needs to be noted that we can not add checking for xobj member functions to
> DECL_NONSTATIC_MEMBER_FUNCTION_P as it is used in cp-objcp-common.cc.  While it
> most likely would be fine, it's possible it could have unintended effects.  In
> light of this, we will most likely need to do some refactoring, possibly
> renaming and replacing it.  In contrast, DECL_FUNCTION_MEMBER_P is not used
> outside of C++ code, so we can add checking for xobj member functions to it
> without any concerns.

I think DECL_NONSTATIC_MEMBER_FUNCTION_P should probably be renamed to 
DECL_IOBJ_MEMBER_FUNC_P to parallel the new macro...

> @@ -3660,6 +3660,7 @@ build_min_non_dep_op_overload (enum tree_code op,
>  
>    expected_nargs = cp_tree_code_length (op);
>    if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE
> +      || DECL_XOBJ_MEMBER_FUNC_P (overload)

...and then the combination should have its own macro, perhaps 
DECL_OBJECT_MEMBER_FUNC_P, spelling out OBJECT to avoid visual confusion 
with either IOBJ/XOBJ.

Renaming the old macro doesn't need to happen in this patch, but adding 
the new macro should.

> There are a few known issues still present in this patch.  Most importantly,
> the implicit object argument fails to convert when passed to by-value xobj
> parameters.  This occurs both for xobj parameters that match the argument type
> and xobj parameters that are unrelated to the object type, but have valid
> conversions available.  This behavior can be observed in the
> explicit-obj-by-value[1-3].C tests.  The implicit object argument appears to be
> simply reinterpreted instead of any conversion applied.  This is elaborated on
> in the test cases.

Yes, that's because of:

> @@ -9949,7 +9951,8 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
>  	}
>      }
>    /* Bypass access control for 'this' parameter.  */
> -  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
> +  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE
> +	   || DECL_XOBJ_MEMBER_FUNC_P (fn))

We don't want to take this path for xob fns.  Instead I think we need to 
change the existing:

>   gcc_assert (first_arg == NULL_TREE);

to assert that if first_arg is non-null, we're dealing with an xob fn, 
and then go ahead and do the same conversion as the loop body on first_arg.

> Despite this, calls where there is no valid conversion
> available are correctly rejected, which I find surprising. The
> explicit-obj-by-value4.C testcase demonstrates this odd but correct behavior.

Yes, because checking for conversions is handled elsewhere.

> Other than this, lambdas are not yet supported,

The error I'm seeing with the lambda testcase is "explicit object member 
function cannot have cv-qualifier".  To avoid that, in 
cp_parser_lambda_declarator_opt you need to set quals to 
TYPE_UNQUALIFIED around where we do that for mutable lambdas.

> and there is some outstanding
> odd behavior where invalid calls to operators are improperly accepted.  The
> test for this utilizes requires expressions to operate though so it's possible
> that the problems originate from there, but I have a hunch they aren't
> responsible.  See explicit-obj-ops-requires-mem.C and
> explicit-obj-ops-requires-non-mem.C for those tests.

I think that's the same issue as by-value1.C above.

Though you're also missing a couple of semicolons on

> +  RRef&& operator()(this RRef&& self) { return static_cast<RRef&&>(self) }
> +  RRef&& operator[](this RRef&& self) { return static_cast<RRef&&>(self) }

> @@ -7164,6 +7164,12 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
>  	    && !mark_used (t, complain) && !(complain & tf_error))
>  	  return error_mark_node;
>  
> +	/* Exception for non-overloaded explicit object member function.
> +	   I am pretty sure this is not perfect, I think we aren't
> +	   handling some constexpr stuff, but I am leaving it for now. */
> +	if (TREE_CODE (TREE_TYPE (t)) == FUNCTION_TYPE)
> +	  return build_address (t);

Specifically, you're missing the SET_EXPR_LOCATION at the end of the 
function.  Maybe break here instead of returning?

> +// missing test for three-way-compare (I don't know how to write it)
> +// missing test for ->* (I don't know how to write it)

Following the same pattern seems to work for me, e.g. (OPERAND) <=> 0 
and (OPERAND) ->* 0.

> +template<typename T = void>
> +void do_calls()

You probably want to instantiate the templates in these testcases to 
make sure that works.

> +// It's very hard to test for incorrect successes without requires, and by extension a non dependent variable
> +// so for the time being, there are no non dependent tests invalid calls.

I don't understand the problem, you can have requires-expressions in 
non-dependent context.

Jason
  
waffl3x Nov. 10, 2023, 5:35 a.m. UTC | #4
Ahh, I should have updated my progress last night after all, it would
have saved us some time. Regardless, it's nice to see we independently
came to the same conclusions.

Side note, would you prefer I compile the lambda and by-value fixes
into a new version of this patch? Or as a separate patch? Originally I
had planned to put it in another patch, but I identified that the code
I wrote in build_over_call was kind of fundamentally broken and it was
almost merely coincidence that it worked at all. In light of this and
your comments (which I've skimmed, I will respond directly below) I
think I should just revise this patch with everything else.


On Thursday, November 9th, 2023 at 2:53 PM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 11/5/23 10:06, waffl3x wrote:
> 
> > Bootstrapped and tested on x86_64-linux with no regressions.
> > 
> > I originally threw this e-mail together last night, but threw in the
> > towel when I thought I saw tests failing and went to sleep. I did a
> > proper bootstrap and comparison and whatnot and found that there were
> > thankfully no regressions.
> > 
> > Anyhow, the first patch feels ready for trunk, the second needs at
> > least one review, I'll write more on that in the second e-mail though.
> > I put quite a lot into the commit message, in hindsight I think I may
> > have gone overboard, but that isn't something I'm going to rewrite at
> > the moment. I really want to get these patches up for review so they
> > can be finalized.
> > 
> > I'm also including my usual musings on things that came up as I was
> > polishing off the patches. I reckon some of them aren't all that
> > important right now but I would rather toss them in here than forget
> > about them.
> > 
> > I'm starting to think that we should have a general macro that
> > indicates whether an implicit object argument should be passed in the
> > call. It might be more clear than what is currently present. I've also
> > noticed that there's a fair amount of places where instead of using
> > DECL_NONSTATIC_MEMBER_FUNCTION_P the code checks if tree_code of the
> > type is a METHOD_TYPE, which is exactly what the aforementioned macro
> > does.
> 
> 
> Agreed.
> 
> > In build_min_non_dep_op_overload I reversed the branches of a condition
> > because it made more sense with METHOD_TYPE first so it doesn't have to
> > take xobj member functions into account on both branches. I am slightly
> > concerned that flipping the branch around might have consequences,
> > hence why I am mentioning it. Realistically I think it's probably fine
> > though.
> 
> 
> Agreed.

Great, I was definitely concerned about this.
 
> > BTW let me know if there's anything you would prefer to be done
> > differently in the changelog, I am still having trouble writing them
> > and I'm usually uncertain if I'm writing them properly.
> 
> > (DECL_FUNCTION_XOBJ_FLAG): Define.
> 
> 
> This is usually "New macro" or just "New".
> 
> > * decl.cc (grokfndecl): New param XOBJ_FUNC_P, for xobj member
> > functions set DECL_FUNCTION_XOBJ_FLAG and don't set
> > DECL_STATIC_FUNCTION_P.
> > (grokdeclarator): Check for xobj param, clear it's purpose and set
> > is_xobj_member_function if it is present. When flag set, don't change
> > type to METHOD_TYPE, keep it as FUNCTION_TYPE.
> > Adjust call to grokfndecl, pass is_xobj_member_function.
> 
> 
> These could be less verbose; for grokfndecl it makes sense to mention
> the new parameter, but otherwise just saying "handle explicit object
> member functions" is enough.

Will do.

> > It needs to be noted that we can not add checking for xobj member functions to
> > DECL_NONSTATIC_MEMBER_FUNCTION_P as it is used in cp-objcp-common.cc. While it
> > most likely would be fine, it's possible it could have unintended effects. In
> > light of this, we will most likely need to do some refactoring, possibly
> > renaming and replacing it. In contrast, DECL_FUNCTION_MEMBER_P is not used
> > outside of C++ code, so we can add checking for xobj member functions to it
> > without any concerns.
> 
> 
> I think DECL_NONSTATIC_MEMBER_FUNCTION_P should probably be renamed to
> DECL_IOBJ_MEMBER_FUNC_P to parallel the new macro...
> 
> > @@ -3660,6 +3660,7 @@ build_min_non_dep_op_overload (enum tree_code op,
> > 
> > expected_nargs = cp_tree_code_length (op);
> > if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE
> > + || DECL_XOBJ_MEMBER_FUNC_P (overload)
> 
> 
> ...and then the combination should have its own macro, perhaps
> DECL_OBJECT_MEMBER_FUNC_P, spelling out OBJECT to avoid visual confusion
> with either IOBJ/XOBJ.
> 
> Renaming the old macro doesn't need to happen in this patch, but adding
> the new macro should.

Sounds good, I will add it in the next revision.

> > There are a few known issues still present in this patch. Most importantly,
> > the implicit object argument fails to convert when passed to by-value xobj
> > parameters. This occurs both for xobj parameters that match the argument type
> > and xobj parameters that are unrelated to the object type, but have valid
> > conversions available. This behavior can be observed in the
> > explicit-obj-by-value[1-3].C tests. The implicit object argument appears to be
> > simply reinterpreted instead of any conversion applied. This is elaborated on
> > in the test cases.
> 
> 
> Yes, that's because of:
> 
> > @@ -9949,7 +9951,8 @@ build_over_call (struct z_candidate cand, int flags, tsubst_flags_t complain)
> > }
> > }
> > / Bypass access control for 'this' parameter. */
> > - else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
> > + else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE
> > + || DECL_XOBJ_MEMBER_FUNC_P (fn))
> 
> 
> We don't want to take this path for xob fns. Instead I think we need to
> change the existing:
> 
> > gcc_assert (first_arg == NULL_TREE);
> 
> 
> to assert that if first_arg is non-null, we're dealing with an xob fn,
> and then go ahead and do the same conversion as the loop body on first_arg.
> 
> > Despite this, calls where there is no valid conversion
> > available are correctly rejected, which I find surprising. The
> > explicit-obj-by-value4.C testcase demonstrates this odd but correct behavior.
> 
> 
> Yes, because checking for conversions is handled elsewhere.

Yeah, as I noted above I realized that just handling it the same way as
iobj member functions is fundamentally broken. I was staring at it last
night and eventually realized that I could just copy the loop body. I
ended up asserting in the body handling the implicit object argument
for xobj member functions that first_arg != NULL_TREE, which I wasn't
sure of, but it seems to work.

I tried asking in IRC if there are any circumstances where first_arg
would be null for a non-static member function and I didn't get an
answer. The code above seemed to indicate that it could be. It just
looks like old code that is no longer valid and never got removed.
Consequently this function has made it on my list of things to refactor
:^).

It does give me confidence that I made the right call seeing you point
out the same things though.

> > Other than this, lambdas are not yet supported,
> 
> 
> The error I'm seeing with the lambda testcase is "explicit object member
> function cannot have cv-qualifier". To avoid that, in
> cp_parser_lambda_declarator_opt you need to set quals to
> TYPE_UNQUALIFIED around where we do that for mutable lambdas.

Yeah, this ended up being the case as suspected, and it was exactly in
cp_parser_lambda_declarator_opt that it needed to be done. There's an
additional quirk in start_preparsed_function, which I already rambled
about a little in a previous reply on this chain, that suddenly
restored setting up the 'this' pointer. Previously, it was being
skipped because ctype was not being set for xobj member functions.
However, ctype not being set was causing the scope to not be set up
correctly for lambdas. In truth I don't remember exactly how the
problem was presenting but I do know how I fixed it.

```
  tree fntype = TREE_TYPE (decl1);
  if (TREE_CODE (fntype) == METHOD_TYPE)
    ctype = TYPE_METHOD_BASETYPE (fntype);
  else if (DECL_XOBJ_MEMBER_FUNC_P (decl1))
    ctype = DECL_CONTEXT (decl1);
```

In hindsight though, I have to question if this is the correct way of
dealing with it. As I mentioned previously, there's an additional quirk
in start_preparsed_function where it sets up the 'this' param, or at
least, it kind of looks like it? This is where my rambling was about.

```
  /* We don't need deal with 'this' or vtable for xobj member functions.  */
  if (ctype && !doing_friend &&
      !(DECL_STATIC_FUNCTION_P (decl1) || DECL_XOBJ_MEMBER_FUNC_P (decl1)))
```

My solution was to just not enter that block for xobj member functions,
but I'm realizing now that ctype doesn't seem to be set for static
member functions so setting ctype for xobj member functions might be
incorrect. I'll need to investigate it closer, I recall seeing the
second block above and thinking "it's checking to make sure decl1 is
not a static member function before entering this block, so that means
it must be set." It seems that was incorrect.

At bare minimum, I'll have to look at this again.

> > and there is some outstanding
> > odd behavior where invalid calls to operators are improperly accepted. The
> > test for this utilizes requires expressions to operate though so it's possible
> > that the problems originate from there, but I have a hunch they aren't
> > responsible. See explicit-obj-ops-requires-mem.C and
> > explicit-obj-ops-requires-non-mem.C for those tests.
> 
> 
> I think that's the same issue as by-value1.C above.
> 
> Though you're also missing a couple of semicolons on
> 
> > + RRef&& operator()(this RRef&& self) { return static_cast<RRef&&>(self) }
> > + RRef&& operator[](this RRef&& self) { return static_cast<RRef&&>(self) }

Yeah, those semicolons and it also turns out that both this and the
other failing requires test were just semantically incorrect in a few
places. I have fixed them and they will be in the next version.

I mistakenly was considering passing a rvalue ref to a const rvalue ref
as ill-formed. That was the obvious one. That's what I get for writing
tests at 7am though.

The less obvious were the address-of and comma operators. I was
puzzling over why it was specifically the lvalue ref cases that were
failing for address-of, and eventually I realized that the built-in in
address-of operator is only valid for lvalues. Once I realized that, I
realized that the comma operator's unexpected successes was also the
same thing, it was just using the built-in operators. I solved this by
checking the return type for those two cases specifically.

Finally, for the arrow operator template cases I just had a const in
the wrong place, once that was moved it worked.

Both of the tests pass now, the next version of the patch will have
them enabled. The operator related tests by far have given me the most
problems out of all the tests. I'm super glad to be done with them...

> > @@ -7164,6 +7164,12 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
> > && !mark_used (t, complain) && !(complain & tf_error))
> > return error_mark_node;
> > 
> > + /* Exception for non-overloaded explicit object member function.
> > + I am pretty sure this is not perfect, I think we aren't
> > + handling some constexpr stuff, but I am leaving it for now. */
> > + if (TREE_CODE (TREE_TYPE (t)) == FUNCTION_TYPE)
> > + return build_address (t);
> 
> 
> Specifically, you're missing the SET_EXPR_LOCATION at the end of the
> function. Maybe break here instead of returning?

I'll play around with this, now that you mention it I seem to recall
incorrect diagnostics with this so that must be why. This one just took
me a week or two to fix so once I had it I really wanted to move on to
something else. I've got a lot more cleared off so I'll come back to it
today.

> > +// missing test for three-way-compare (I don't know how to write it)
> > +// missing test for ->* (I don't know how to write it)
> 
> 
> Following the same pattern seems to work for me, e.g. (OPERAND) <=> 0
> 
> and (OPERAND) ->* 0.

I will take a crack at it, it's more writing the operator overloads
themselves that gives me brain damage. Guess I'm not done with these
tests after all.

> > +template<typename T = void>
> > +void do_calls()
> 
> 
> You probably want to instantiate the templates in these testcases to
> make sure that works.

Oh jeez, I did forget to in that test, I will fix that... man, I hope
this test isn't suddenly failing once I do that.


> > +// It's very hard to test for incorrect successes without requires, and by extension a non dependent variable
> > +// so for the time being, there are no non dependent tests invalid calls.
> 
> 
> I don't understand the problem, you can have requires-expressions in
> non-dependent context.
> 
> Jason

Yeah that's what I had thought too, but it was giving me errors. I seem
to recall inconsistent behavior with this so maybe it's a new bug? I
just wouldn't be surprised if this was actually the correct behavior
though, it seems consistent with how static_assert's are with non
dependent expressions. I will ask around, do some testing on godbolt,
maybe try to read the standard. Ultimately though I'm not really sure,
I just know I couldn't get it to work without making the operands
dependent.

To be clear, it was giving an error on the expression being tested in
the requires expression. The cases that are supposed to intentionally
fail were giving this error.

Thanks for the input, one more e-mail to respond to then back to work.

Alex
  

Patch

From e730dcba51503446cc362909fcab19361970b448 Mon Sep 17 00:00:00 2001
From: waffl3x <waffl3x@protonmail.com>
Date: Sat, 4 Nov 2023 05:35:10 -0600
Subject: [PATCH 1/2] c++: Initial support for C++23 P0847R7 (Deducing this)
 [PR102609]

This patch implements initial support for P0847R7 without diagnostics.  My goal
was to minimize changes to the existing code.  To achieve this I chose to treat
xobj member functions as static member functions, while opting into member
function handling when necessary.  This seemed to be the better choice since
most of the time they are more like static member functions.

This is achieved by inhibiting conversion of the declaration's type from
FUNCTION_TYPE to METHOD_TYPE.  Most if not everything seems to differentiate
between member functions and static member functions by inspecting the
FUNCTION_DECL's type, so forcing this is sufficient.  An xobj member function
is any member function that is declared with an xobj parameter as it's first
parameter.  This information is passed through the declarator's parameter list,
stored in the purpose of the parameter's tree_list node.  Normally this is used
to store default arguments, but as default arguments are not allowed for xobj
parameters it is fine for us to hijack it.  By utilizing this we can pass this
information from cp_parser_parameter_declaration over to grokdeclarator without
adding anything new to the tree structs.

We still need to differentiate this new function variety from static member
functions and regular functions, and since this information needs to stick to
the declaration we should select a more concrete place to store it.  Unlike the
previous hack for parameters, we instead add a flag to lang_decl_fn,  the only
modification this patch makes to any tree data-structures.  We could probably
try to stick the information in the decl's parameters somewhere, but I think a
proper flag is justified.  The new flag can be set and cleared through
DECL_FUNCTION_XOBJ_FLAG, it is invalid to use this with anything other than
FUNCTION_DECL nodes.  For inspecting the value of this flag
DECL_XOBJ_MEMBER_FUNC_P should be used, this macro is safe to use with any node
type and will harmlessly evaluate to false for invalid node types.

It needs to be noted that we can not add checking for xobj member functions to
DECL_NONSTATIC_MEMBER_FUNCTION_P as it is used in cp-objcp-common.cc.  While it
most likely would be fine, it's possible it could have unintended effects.  In
light of this, we will most likely need to do some refactoring, possibly
renaming and replacing it.  In contrast, DECL_FUNCTION_MEMBER_P is not used
outside of C++ code, so we can add checking for xobj member functions to it
without any concerns.

There are a few known issues still present in this patch.  Most importantly,
the implicit object argument fails to convert when passed to by-value xobj
parameters.  This occurs both for xobj parameters that match the argument type
and xobj parameters that are unrelated to the object type, but have valid
conversions available.  This behavior can be observed in the
explicit-obj-by-value[1-3].C tests.  The implicit object argument appears to be
simply reinterpreted instead of any conversion applied.  This is elaborated on
in the test cases.  Despite this, calls where there is no valid conversion
available are correctly rejected, which I find surprising.  The
explicit-obj-by-value4.C testcase demonstrates this odd but correct behavior.
Other than this, lambdas are not yet supported, and there is some outstanding
odd behavior where invalid calls to operators are improperly accepted.  The
test for this utilizes requires expressions to operate though so it's possible
that the problems originate from there, but I have a hunch they aren't
responsible.  See explicit-obj-ops-requires-mem.C and
explicit-obj-ops-requires-non-mem.C for those tests.

One of the more invasive changes I chose to make is in
build_min_non_dep_op_overload, I flipped the if branches because the conditions
make significantly more sense when METHOD_TYPE is tested for first.  It did not
appear that the previous ordering was for any particular purpose so I felt it
was better to flip it than make the conditions significantly more confusing.  I
did want to note this change in particular just in case.

	PR c++/102609

gcc/cp/ChangeLog:

	PR c++/102609
	Initial support for C++23 P0847R7 - Deducing this.
	* call.cc (add_candidates): Check if fn is an xobj member function.
	(build_over_call): Ditto.
	* cp-tree.h (struct lang_decl_fn::xobj_func): New data member.
	(DECL_FUNCTION_XOBJ_FLAG): Define.
	(DECL_XOBJ_MEMBER_FUNC_P): Define.
	(DECL_FUNCTION_MEMBER_P): Add check for xobj member functions.
	(enum cp_decl_spec): Add ds_this.
	* decl.cc (grokfndecl): New param XOBJ_FUNC_P, for xobj member
	functions set DECL_FUNCTION_XOBJ_FLAG and don't set
	DECL_STATIC_FUNCTION_P.
	(grokdeclarator): Check for xobj param, clear it's purpose and set
	is_xobj_member_function if it is present.  When flag set, don't change
        type to METHOD_TYPE, keep it as FUNCTION_TYPE.
	Adjust call to grokfndecl, pass is_xobj_member_function.
	(grok_op_properties): Treat xobj member functions as iobj member
	functions.
	* parser.cc (cp_parser_decl_specifier_seq): Handle "this" specifier.
	(cp_parser_parameter_declaration): When "this" decl spec present, set
	default_argument to this_identifier.
	(set_and_check_decl_spec_loc) <decl_spec_names>: Add "this".
	* tree.cc (build_min_non_dep_op_overload): Flip if branches, treat
	xobj member functions as iobj member functions.
	* typeck.cc (cp_build_addr_expr_1): Handle xobj member functions
	without overloads.

gcc/testsuite/ChangeLog:

	PR c++/102609
	Initial support for C++23 P0847R7 - Deducing this.
	* g++.dg/cpp23/explicit-obj-basic1.C: New test.
	* g++.dg/cpp23/explicit-obj-basic2.C: New test.
	* g++.dg/cpp23/explicit-obj-by-value1.C: New test.
	* g++.dg/cpp23/explicit-obj-by-value2.C: New test.
	* g++.dg/cpp23/explicit-obj-by-value3.C: New test.
	* g++.dg/cpp23/explicit-obj-by-value4.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda1.C: New test.
	* g++.dg/cpp23/explicit-obj-ops-mem-arrow.C: New test.
	* g++.dg/cpp23/explicit-obj-ops-mem-assignment.C: New test.
	* g++.dg/cpp23/explicit-obj-ops-mem-call.C: New test.
	* g++.dg/cpp23/explicit-obj-ops-mem-subscript.C: New test.
	* g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C: New test.
	* g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C: New test.
	* g++.dg/cpp23/explicit-obj-ops-non-mem.h: New test.
	* g++.dg/cpp23/explicit-obj-ops-requires-mem.C: New test.
	* g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C: New test.

Signed-off-by: waffl3x <waffl3x@protonmail.com>
---
 gcc/cp/call.cc                                |   9 +-
 gcc/cp/cp-tree.h                              |  25 +-
 gcc/cp/decl.cc                                |  44 +++-
 gcc/cp/parser.cc                              |  20 +-
 gcc/cp/tree.cc                                |  26 +-
 gcc/cp/typeck.cc                              |   6 +
 .../g++.dg/cpp23/explicit-obj-basic1.C        | 113 +++++++++
 .../g++.dg/cpp23/explicit-obj-basic2.C        |  27 +++
 .../g++.dg/cpp23/explicit-obj-by-value1.C     |  49 ++++
 .../g++.dg/cpp23/explicit-obj-by-value2.C     |  59 +++++
 .../g++.dg/cpp23/explicit-obj-by-value3.C     |  42 ++++
 .../g++.dg/cpp23/explicit-obj-by-value4.C     |  19 ++
 .../g++.dg/cpp23/explicit-obj-lambda1.C       |  11 +
 .../g++.dg/cpp23/explicit-obj-ops-mem-arrow.C |  27 +++
 .../cpp23/explicit-obj-ops-mem-assignment.C   |  26 ++
 .../g++.dg/cpp23/explicit-obj-ops-mem-call.C  |  39 +++
 .../cpp23/explicit-obj-ops-mem-subscript.C    |  39 +++
 .../cpp23/explicit-obj-ops-non-mem-dep.C      |  57 +++++
 .../cpp23/explicit-obj-ops-non-mem-non-dep.C  |  56 +++++
 .../g++.dg/cpp23/explicit-obj-ops-non-mem.h   | 202 +++++++++++++++
 .../cpp23/explicit-obj-ops-requires-mem.C     | 172 +++++++++++++
 .../cpp23/explicit-obj-ops-requires-non-mem.C | 229 ++++++++++++++++++
 22 files changed, 1266 insertions(+), 31 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-basic1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-basic2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-arrow.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-assignment.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-call.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-subscript.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem.h
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-mem.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 2eb54b5b6ed..c7ca32d1d34 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -6526,8 +6526,10 @@  add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> *args,
 
       tree fn_first_arg = NULL_TREE;
       const vec<tree, va_gc> *fn_args = args;
-
-      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn))
+      /* We can't add xobj member functions to DECL_NONSTATIC_MEMBER_FUNCTION_P,
+	 for now just check for them specifically.  */
+      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+	  || DECL_XOBJ_MEMBER_FUNC_P (fn))
 	{
 	  /* Figure out where the object arg comes from.  If this
 	     function is a non-static member and we didn't get an
@@ -9949,7 +9951,8 @@  build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
 	}
     }
   /* Bypass access control for 'this' parameter.  */
-  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
+  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE
+	   || DECL_XOBJ_MEMBER_FUNC_P (fn))
     {
       tree arg = build_this (first_arg != NULL_TREE
 			     ? first_arg
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 98b29e9cf81..901fb1f4616 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2938,8 +2938,9 @@  struct GTY(()) lang_decl_fn {
   unsigned maybe_deleted : 1;
   unsigned coroutine_p : 1;
   unsigned implicit_constexpr : 1;
+  unsigned xobj_func : 1;
 
-  unsigned spare : 9;
+  unsigned spare : 8;
 
   /* 32-bits padding on 64-bit host.  */
 
@@ -3338,14 +3339,25 @@  struct GTY(()) lang_decl {
   (LANG_DECL_FN_CHECK (NODE)->static_function)
 
 /* Nonzero for FUNCTION_DECL means that this decl is a non-static
-   member function.  */
+   member function, UNLESS it is an xobj member function.
+   This inconsistency will be fixed in the future.  */
 #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
   (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
 
+/* Simple member access, only valid for FUNCTION_DECL nodes.  */
+#define DECL_FUNCTION_XOBJ_FLAG(NODE)	\
+  (LANG_DECL_FN_CHECK (NODE)->xobj_func)
+/* Nonzero if NODE is a FUNCTION_DECL that is an xobj member function,
+   safely evaluates to false for all non FUNCTION_DECL nodes.  */
+#define DECL_XOBJ_MEMBER_FUNC_P(NODE)			\
+  (TREE_CODE (STRIP_TEMPLATE (NODE)) == FUNCTION_DECL	\
+   && DECL_FUNCTION_XOBJ_FLAG (NODE) == 1)
+
 /* Nonzero for FUNCTION_DECL means that this decl is a member function
    (static or non-static).  */
 #define DECL_FUNCTION_MEMBER_P(NODE) \
-  (DECL_NONSTATIC_MEMBER_FUNCTION_P (NODE) || DECL_STATIC_FUNCTION_P (NODE))
+  (DECL_NONSTATIC_MEMBER_FUNCTION_P (NODE) || DECL_STATIC_FUNCTION_P (NODE) \
+  || DECL_XOBJ_MEMBER_FUNC_P (NODE))
 
 /* Nonzero for FUNCTION_DECL means that this member function
    has `this' as const X *const.  */
@@ -6264,11 +6276,13 @@  enum cp_storage_class {
 
 /* An individual decl-specifier.  This is used to index the array of
    locations for the declspecs in struct cp_decl_specifier_seq
-   below.  */
+   below.
+   A subset of these enums also corresponds to elements of
+   cp_parser_set_decl_spec_type:decl_spec_names in parser.cc.  */
 
 enum cp_decl_spec {
   ds_first,
-  ds_signed = ds_first,
+  ds_signed = ds_first, /* Index of first element of decl_spec_names.  */
   ds_unsigned,
   ds_short,
   ds_long,
@@ -6285,6 +6299,7 @@  enum cp_decl_spec {
   ds_complex,
   ds_constinit,
   ds_consteval,
+  ds_this, /* Index of last element of decl_spec_names.  */
   ds_thread,
   ds_type_spec,
   ds_redefined_builtin_type_spec,
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 16af59de696..c02d78b8102 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -10318,6 +10318,7 @@  grokfndecl (tree ctype,
 	    int publicp,
 	    int inlinep,
 	    bool deletedp,
+	    bool xobj_func_p,
 	    special_function_kind sfk,
 	    bool funcdef_flag,
 	    bool late_return_type_p,
@@ -10327,7 +10328,6 @@  grokfndecl (tree ctype,
 	    location_t location)
 {
   tree decl;
-  int staticp = ctype && TREE_CODE (type) == FUNCTION_TYPE;
   tree t;
 
   if (location == UNKNOWN_LOCATION)
@@ -10525,12 +10525,9 @@  grokfndecl (tree ctype,
 		  (IDENTIFIER_POINTER (declarator))))))
     SET_DECL_LANGUAGE (decl, lang_c);
 
-  /* Should probably propagate const out from type to decl I bet (mrs).  */
-  if (staticp)
-    {
-      DECL_STATIC_FUNCTION_P (decl) = 1;
-      DECL_CONTEXT (decl) = ctype;
-    }
+  DECL_STATIC_FUNCTION_P (decl)
+    = !xobj_func_p && ctype && TREE_CODE (type) == FUNCTION_TYPE;
+  DECL_FUNCTION_XOBJ_FLAG (decl) = xobj_func_p;
 
   if (deletedp)
     DECL_DELETED_FN (decl) = 1;
@@ -12998,6 +12995,8 @@  grokdeclarator (const cp_declarator *declarator,
   if (attrlist)
     diagnose_misapplied_contracts (*attrlist);
 
+  /* Skip over build_memfn_type when a FUNCTION_DECL is an xobj memfn.  */
+  bool is_xobj_member_function = false;
   /* Determine the type of the entity declared by recurring on the
      declarator.  */
   for (; declarator; declarator = declarator->declarator)
@@ -13113,6 +13112,25 @@  grokdeclarator (const cp_declarator *declarator,
 	    if (raises == error_mark_node)
 	      raises = NULL_TREE;
 
+	    auto find_xobj_parm = [](tree parm_list)
+	      {
+		/* There is no need to iterate over the list,
+		   only the first parm can be a valid xobj parm.  */
+		if (!parm_list || parm_list == void_list_node)
+		  return false;
+		if (TREE_PURPOSE (parm_list) != this_identifier)
+		  return false;
+		/* If we make it here, we are looking at an xobj parm.
+
+		   Non-null 'purpose' usually means the parm has a default
+		   argument, we don't want to violate this assumption.  */
+		TREE_PURPOSE (parm_list) = NULL_TREE;
+		return true;
+	      };
+
+	    is_xobj_member_function
+	      = find_xobj_parm (declarator->u.function.parameters);
+
 	    if (reqs)
 	      error_at (location_of (reqs), "requires-clause on return type");
 	    reqs = declarator->u.function.requires_clause;
@@ -14177,6 +14195,8 @@  grokdeclarator (const cp_declarator *declarator,
     }
 
   if (ctype && TREE_CODE (type) == FUNCTION_TYPE && staticp < 2
+      /* Don't convert xobj member functions to METHOD_TYPE.  */
+      && !is_xobj_member_function
       && !(unqualified_id
 	   && identifier_p (unqualified_id)
 	   && IDENTIFIER_NEWDEL_OP_P (unqualified_id)))
@@ -14398,7 +14418,8 @@  grokdeclarator (const cp_declarator *declarator,
 			       friendp ? -1 : 0, friendp, publicp,
 			       inlinep | (2 * constexpr_p) | (4 * concept_p)
 				       | (8 * consteval_p),
-			       initialized == SD_DELETED, sfk,
+			       initialized == SD_DELETED,
+			       is_xobj_member_function, sfk,
 			       funcdef_flag, late_return_type_p,
 			       template_count, in_namespace,
 			       attrlist, id_loc);
@@ -14733,8 +14754,8 @@  grokdeclarator (const cp_declarator *declarator,
 			   inlinep | (2 * constexpr_p) | (4 * concept_p)
 				   | (8 * consteval_p),
 			   initialized == SD_DELETED,
-                           sfk,
-                           funcdef_flag,
+			   is_xobj_member_function, sfk,
+			   funcdef_flag,
 			   late_return_type_p,
 			   template_count, in_namespace, attrlist,
 			   id_loc);
@@ -15628,7 +15649,8 @@  grok_op_properties (tree decl, bool complain)
   /* An operator function must either be a non-static member function
      or have at least one parameter of a class, a reference to a class,
      an enumeration, or a reference to an enumeration.  13.4.0.6 */
-  if (! methodp || DECL_STATIC_FUNCTION_P (decl))
+  if ((!methodp && !DECL_XOBJ_MEMBER_FUNC_P (decl))
+      || DECL_STATIC_FUNCTION_P (decl))
     {
       if (operator_code == TYPE_EXPR
 	  || operator_code == COMPONENT_REF
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 20e18365906..4fbcc52c222 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -16091,6 +16091,16 @@  cp_parser_decl_specifier_seq (cp_parser* parser,
 	    decl_specs->locations[ds_attribute] = token->location;
 	  continue;
 	}
+      /* Special case for "this" specifier, indicating a parm is an xobj parm.
+	 The "this" specifier must be the first specifier in the declaration,
+	 after any attributes.  */
+      if (token->keyword == RID_THIS)
+	{
+	  cp_lexer_consume_token (parser->lexer);
+	  set_and_check_decl_spec_loc (decl_specs, ds_this, token);
+	  continue;
+	}
+
       /* Assume we will find a decl-specifier keyword.  */
       found_decl_spec = true;
       /* If the next token is an appropriate keyword, we can simply
@@ -25495,6 +25505,13 @@  cp_parser_parameter_declaration (cp_parser *parser,
   if (default_argument)
     STRIP_ANY_LOCATION_WRAPPER (default_argument);
 
+  if (decl_spec_seq_has_spec_p (&decl_specifiers, ds_this))
+    {
+      /* Xobj parameters can not have default arguments, thus
+	 we can reuse the default argument field to flag the param as such.  */
+      default_argument = this_identifier;
+    }
+
   /* Generate a location for the parameter, ranging from the start of the
      initial token to the end of the final token (using input_location for
      the latter, set up by cp_lexer_set_source_position_from_token when
@@ -33873,7 +33890,8 @@  set_and_check_decl_spec_loc (cp_decl_specifier_seq *decl_specs,
 	    "constexpr",
 	    "__complex",
 	    "constinit",
-	    "consteval"
+	    "consteval",
+	    "this"
 	  };
 	  gcc_rich_location richloc (location);
 	  richloc.add_fixit_remove ();
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index 417c92ba76f..ab5fe79146c 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -3660,6 +3660,7 @@  build_min_non_dep_op_overload (enum tree_code op,
 
   expected_nargs = cp_tree_code_length (op);
   if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE
+      || DECL_XOBJ_MEMBER_FUNC_P (overload)
       /* For ARRAY_REF, operator[] is either a non-static member or newly
 	 static member, never out of class and for the static member case
 	 if user uses single index the operator[] needs to have a single
@@ -3677,24 +3678,26 @@  build_min_non_dep_op_overload (enum tree_code op,
   releasing_vec args;
   va_start (p, overload);
 
-  if (TREE_CODE (TREE_TYPE (overload)) == FUNCTION_TYPE)
+  if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE
+	   || DECL_XOBJ_MEMBER_FUNC_P (overload))
     {
-      fn = overload;
-      if (op == ARRAY_REF)
-	obj = va_arg (p, tree);
+      tree object = va_arg (p, tree);
+      tree binfo = TYPE_BINFO (TREE_TYPE (object));
+      tree method = build_baselink (binfo, binfo, overload, NULL_TREE);
+      fn = build_min (COMPONENT_REF, TREE_TYPE (overload),
+		      object, method, NULL_TREE);
       for (int i = 0; i < nargs; i++)
 	{
 	  tree arg = va_arg (p, tree);
 	  vec_safe_push (args, arg);
 	}
     }
-  else if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE)
+  else if (TREE_CODE (TREE_TYPE (overload)) == FUNCTION_TYPE)
     {
-      tree object = va_arg (p, tree);
-      tree binfo = TYPE_BINFO (TREE_TYPE (object));
-      tree method = build_baselink (binfo, binfo, overload, NULL_TREE);
-      fn = build_min (COMPONENT_REF, TREE_TYPE (overload),
-		      object, method, NULL_TREE);
+      gcc_assert (!DECL_XOBJ_MEMBER_FUNC_P (overload));
+      fn = overload;
+      if (op == ARRAY_REF)
+	obj = va_arg (p, tree);
       for (int i = 0; i < nargs; i++)
 	{
 	  tree arg = va_arg (p, tree);
@@ -3729,7 +3732,8 @@  build_min_non_dep_op_overload (tree non_dep, tree overload, tree object,
 
   unsigned int nargs = call_expr_nargs (non_dep);
   tree fn = overload;
-  if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE)
+  if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE
+      || DECL_XOBJ_MEMBER_FUNC_P (overload))
     {
       tree binfo = TYPE_BINFO (TREE_TYPE (object));
       tree method = build_baselink (binfo, binfo, overload, NULL_TREE);
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 49afbd8fb5e..00b999cec19 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7164,6 +7164,12 @@  cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 	    && !mark_used (t, complain) && !(complain & tf_error))
 	  return error_mark_node;
 
+	/* Exception for non-overloaded explicit object member function.
+	   I am pretty sure this is not perfect, I think we aren't
+	   handling some constexpr stuff, but I am leaving it for now. */
+	if (TREE_CODE (TREE_TYPE (t)) == FUNCTION_TYPE)
+	  return build_address (t);
+
 	type = build_ptrmem_type (context_for_name_lookup (t),
 				  TREE_TYPE (t));
 	t = make_ptrmem_cst (type, t);
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic1.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic1.C
new file mode 100644
index 00000000000..1e44c9123b7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic1.C
@@ -0,0 +1,113 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// basic use cases and calling
+
+// non-trailing return
+// definitions
+struct S0 {
+  void f0(this S0) {}
+  void f1(this S0&) {}
+  void f2(this S0&&) {}
+  void f3(this S0 const&) {}
+  void f4(this S0 const&&) {}
+  template<typename Self>
+  void d0(this Self&&) {}
+  void d1(this auto&&) {}
+};
+// declarations
+struct S1 {
+  void f0(this S1);
+  void f1(this S1&);
+  void f2(this S1&&);
+  void f3(this S1 const&);
+  void f4(this S1 const&&);
+  template<typename Self>
+  void d0(this Self&&);
+  void d1(this auto&&);
+};
+// out of line definitions
+void S1::f0(this S1) {}
+void S1::f1(this S1&) {}
+void S1::f2(this S1&&) {}
+void S1::f3(this S1 const&) {}
+void S1::f4(this S1 const&&) {}
+template<typename Self>
+void S1::d0(this Self&&) {}
+void S1::d1(this auto&&) {}
+
+// trailing return
+// definitions
+struct S2 {
+  auto f0(this S2) -> void {}
+  auto f1(this S2&) -> void {}
+  auto f2(this S2&&) -> void {}
+  auto f3(this S2 const&) -> void {}
+  auto f4(this S2 const&&) -> void {}
+  template<typename Self>
+  auto d0(this Self&&) -> void {}
+
+  auto d1(this auto&&) -> void {}
+};
+// declarations
+struct S3 {
+  auto f0(this S3) -> void;
+  auto f1(this S3&) -> void;
+  auto f2(this S3&&) -> void;
+  auto f3(this S3 const&) -> void;
+  auto f4(this S3 const&&) -> void;
+  template<typename Self>
+  auto d0(this Self&&) -> void;
+  auto d1(this auto&&) -> void;
+};
+// out of line definitions
+auto S3::f0(this S3) -> void {}
+auto S3::f1(this S3&) -> void {}
+auto S3::f2(this S3&&) -> void {}
+auto S3::f3(this S3 const&) -> void {}
+auto S3::f4(this S3 const&&) -> void {}
+template<typename Self>
+auto S3::d0(this Self&&) -> void {}
+auto S3::d1(this auto&&) -> void {}
+
+template<typename T>
+void call_with_qualification()
+{
+  T obj{};
+  // by value should take any qualification (f0)
+  T{}.f0();
+  obj.f0();
+  static_cast<T&&>(obj).f0(); 
+  static_cast<T const&>(obj).f0();
+  static_cast<T const&&>(obj).f0();
+  // specific qualification (f1 - f4)
+  T{}.f2();
+  T{}.f3();
+  T{}.f4();
+  obj.f1();
+  obj.f3();
+  static_cast<T&&>(obj).f2();
+  static_cast<T&&>(obj).f3();
+  static_cast<T&&>(obj).f4();
+  static_cast<T const&>(obj).f3();
+  static_cast<T const&&>(obj).f4();
+  // deduced should (obviously) take any qualification (d0, d1)
+  T{}.d0();
+  obj.d0();
+  static_cast<T&&>(obj).d0();
+  static_cast<T const&>(obj).d0();
+  static_cast<T const&&>(obj).d0();
+  T{}.d1();
+  obj.d1();
+  static_cast<T&&>(obj).d1();
+  static_cast<T const&>(obj).d1();
+  static_cast<T const&&>(obj).d1();
+}
+
+void perform_calls()
+{
+  call_with_qualification<S0>();
+  call_with_qualification<S1>();
+  call_with_qualification<S2>();
+  call_with_qualification<S3>();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic2.C
new file mode 100644
index 00000000000..2c2b69ad362
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic2.C
@@ -0,0 +1,27 @@ 
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// explicit object member function pointer type deduction,
+// conversion to function pointer,
+// and calling through pointer to function
+
+struct S {
+  int _n;
+  int f(this S& self) { return self._n; }
+};
+
+using f_type = int(*)(S&);
+
+static_assert (__is_same (f_type, decltype (&S::f)));
+
+int main()
+{
+  auto fp0 = &S::f;
+  f_type fp1 = &S::f;
+  static_assert (__is_same (decltype (fp0), decltype (fp1)));
+  S s{42};
+  if (fp0 (s) != 42)
+    __builtin_abort ();
+  if (fp1 (s) != 42)
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value1.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value1.C
new file mode 100644
index 00000000000..e85c9ab03b0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value1.C
@@ -0,0 +1,49 @@ 
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// conversion of the implicit object argument to an xobj parameter
+// when calling by value xobj member functions
+
+// The initial implementation of xobj member functions incorrectly did not
+// convert the implicit object argument when binding to the xobj
+// parameter. In spite of this, it did correctly check to see if such a
+// conversion would be valid, thus no diagnostic would be emitted when a
+// conversion was valid, but instead of applying the conversion, the
+// argument would silently be reinterpreted as the type of the parameter. 
+
+// This is why we use uintptr_t for the value in S and compare the result
+// of f to &s, we want to test for simple reinterpretation of the
+// argument. To accurately test for this we make sure to use an object
+// that has a different address than the value of our magic number. It's
+// an impossibly improbable edge case but it's trivial to work around. We
+// still compare against both the address of s and the magic number so we
+// can additionally test for bugged conversions, while also
+// differentiating that case from reinterpretation of the argument.
+
+// { dg-xfail-run-if "by value explicit object parameter is not supported yet" { *-*-* } }
+
+using uintptr_t = __UINTPTR_TYPE__;
+inline constexpr uintptr_t magic = 42;
+
+struct S {
+    uintptr_t _v;
+    uintptr_t f(this S self) {
+        return self._v;
+    }
+};
+
+int main() 
+{
+  S s0{magic};
+  S s1{magic};
+  // prevent (absurdly improbable) bogus failures
+  S& s = magic != (uintptr_t)(&s0) ? s0 : s1;
+
+  uintptr_t const ret = s.f();
+  // check for reinterpretation of the object argument
+  if (ret == (uintptr_t)(&s))
+    __builtin_abort ();
+  // check for a bugged conversion
+  if (ret != magic)
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value2.C
new file mode 100644
index 00000000000..051439bb1df
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value2.C
@@ -0,0 +1,59 @@ 
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// conversion of the implicit object argument to an xobj parameter
+// using a user defined conversion or converting constructor
+// when calling by value xobj member functions
+
+// see explicit-obj-by-value1.C for details on this test
+
+// { dg-xfail-run-if "user defined conversions from an implicit object argument to an explicit object parameter are not supported yet" { *-*-* } }
+
+using uintptr_t = __UINTPTR_TYPE__;
+inline constexpr uintptr_t magic = 42;
+
+struct S;
+
+struct FromS {
+  uintptr_t _v;
+  FromS(S);
+};
+
+struct S {
+  operator uintptr_t() const {
+    return magic;
+  }
+  uintptr_t f(this uintptr_t n) {
+    return n;
+  }
+  uintptr_t g(this FromS from_s) {
+    return from_s._v;
+  }
+};
+
+FromS::FromS(S) : _v(magic) {}
+
+
+int main() 
+{
+  S s0{};
+  S s1{};
+  // prevent (absurdly improbable) bogus failures
+  S& s = magic != (uintptr_t)(&s0) ? s0 : s1;
+
+  uintptr_t const ret0 = s.f();
+  // check for reinterpretation of the object argument
+  if (ret0 == (uintptr_t)(&s))
+    __builtin_abort ();
+  // check for a bugged conversion
+  if (ret0 != magic)
+    __builtin_abort ();
+
+  uintptr_t const ret1 = s.g();
+  // check for reinterpretation of the object argument
+  if (ret1 == (uintptr_t)(&s))
+    __builtin_abort ();
+  // check for a bugged conversion
+  if (ret1 != magic)
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value3.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value3.C
new file mode 100644
index 00000000000..30e556bd6cb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value3.C
@@ -0,0 +1,42 @@ 
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// correct constructor selection when initializing a by value xobj parameter
+
+// see explicit-obj-by-value1.C for details on this test
+
+// { dg-xfail-run-if "by value explicit object parameter is not supported yet" { *-*-* } }
+
+using uintptr_t = __UINTPTR_TYPE__;
+inline constexpr uintptr_t magic = 42;
+inline constexpr uintptr_t copy_magic = 5;
+inline constexpr uintptr_t move_magic = 10;
+
+struct S {
+  uintptr_t _v;
+  explicit S(uintptr_t v) : _v(v) {}
+  S(S const& other) : _v(other._v + copy_magic) {}
+  S(S&& other) : _v(other._v + move_magic) {}
+  uintptr_t f(this S self) {
+    return self._v;
+  }
+};
+
+int main() 
+{
+  S s0{magic};
+  S s1{magic};
+  // prevent (absurdly improbable (^2)) bogus results
+  // it's virtually impossible for both to have a bogus result,
+  // but we can guarantee correct results from both easily, so why not?
+  S& s_copy_from = magic + copy_magic != (uintptr_t)(&s0) ? s0 : s1;
+  S& s_move_from = magic + move_magic != (uintptr_t)(&s0) ? s0 : s1;
+  uintptr_t const copy_ret = static_cast<S const&>(s_copy_from).f();
+  uintptr_t const move_ret = static_cast<S&&>(s_move_from).f();
+  // we test specifically for reinterpretation in other
+  // by value tests, it's unnecessary to do it again here
+  if (copy_ret != magic + copy_magic)
+    __builtin_abort ();
+  if (move_ret != magic + move_magic)
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value4.C
new file mode 100644
index 00000000000..d3c5e393e7b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value4.C
@@ -0,0 +1,19 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// diagnosis of ill-formed calls to by-value xobj member functions
+// due to an absence of valid conversion functions
+
+struct NotFromS {};
+
+struct S {
+  void f(this int) {}
+  void g(this NotFromS) {}
+};
+
+void test()
+{
+  S s{};
+  s.f(); // { dg-error {cannot convert 'S' to 'int'} }
+  s.g(); // { dg-error {cannot convert 'S' to 'NotFromS'} }
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda1.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda1.C
new file mode 100644
index 00000000000..913fb3ca5ce
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda1.C
@@ -0,0 +1,11 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// lambda declaration with xobj parameter
+
+// { dg-excess-errors "explicit object parameter with lambdas not implemented yet" { xfail *-*-* } }
+
+void test()
+{
+  (void)[](this auto&& self){};
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-arrow.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-arrow.C
new file mode 100644
index 00000000000..c5b2c805a2f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-arrow.C
@@ -0,0 +1,27 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// uses of member only operators (arrow)
+
+struct S {
+  int _v;
+  S* operator->(this S& self) { return &self; }
+};
+
+void non_dep()
+{
+  S s{};
+  (void)s->_v;
+}
+
+template<typename = void>
+void dependent()
+{
+  S s{};
+  (void)s->_v;
+}
+
+void call()
+{
+  dependent();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-assignment.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-assignment.C
new file mode 100644
index 00000000000..829c7137abc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-assignment.C
@@ -0,0 +1,26 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// uses of member only operators (assignment)
+
+struct S {
+  void operator=(this S&, int) {}
+};
+
+void non_dep()
+{
+  S s{};
+  s = 0;
+}
+
+template<typename = void>
+void dependent()
+{
+  S s{};
+  s = 0;
+}
+
+void call()
+{
+  dependent();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-call.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-call.C
new file mode 100644
index 00000000000..1dfe764de83
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-call.C
@@ -0,0 +1,39 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// uses of member only operators (call op)
+
+// execution paths for subscript with 1 argument and 0 and 2+ arguments are different
+// just to be safe, also test 0 and 2 argument cases here too
+
+struct S {
+  void operator()(this S&) {}
+  void operator()(this S&, int) {}
+  void operator()(this S&, int, int) {}
+  template<typename... Args>
+  void operator()(this S&, Args... args) {}
+};
+
+void non_dep()
+{
+  S s{};
+  s();
+  s(0);
+  s(0, 0);
+  s(0, 0, 0);
+}
+
+template<typename = void>
+void dependent()
+{
+  S s{};
+  s();
+  s(0);
+  s(0, 0);
+  s(0, 0, 0);
+}
+
+void call()
+{
+  dependent();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-subscript.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-subscript.C
new file mode 100644
index 00000000000..cee5f6e135c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-subscript.C
@@ -0,0 +1,39 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// uses of member only operators (subscript)
+
+// execution paths for subscript with 1 argument and 0 and 2+ arguments are different
+// therefore we should additionally test the 0 and 2 argument cases as well
+
+struct S {
+  void operator[](this S&) {}
+  void operator[](this S&, int) {}
+  void operator[](this S&, int, int) {}
+  template<typename... Args>
+  void operator[](this S&, Args... args) {}
+};
+
+void non_dep()
+{
+  S s{};
+  s[];
+  s[0];
+  s[0, 0];
+  s[0, 0, 0];
+}
+
+template<typename = void>
+void dependent()
+{
+  S s{};
+  s[];
+  s[0];
+  s[0, 0];
+  s[0, 0, 0];
+}
+
+void call()
+{
+  dependent();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C
new file mode 100644
index 00000000000..3c9f93fbcc2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C
@@ -0,0 +1,57 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// operators that are not required to be members
+// called in a dependent context (as non dependent exprs)
+// see header
+#include "explicit-obj-ops-non-mem.h"
+
+// noop, indicates which versions are ill-formed
+// I could not find a way to test the invalid cases
+// without requires expressions
+#define TEST_INVALID(X)
+
+template<typename T = void>
+void do_calls()
+{
+  Value value{};
+  TEST_OPS(value)
+  TEST_OPS(static_cast<Value&&>(value))
+  TEST_OPS(static_cast<Value const&>(value))
+  TEST_OPS(static_cast<Value const&&>(value))
+  
+  LRef l_ref{};
+  TEST_OPS(l_ref)
+  TEST_INVALID(static_cast<LRef&&>(l_ref))
+  TEST_INVALID(static_cast<LRef const&>(l_ref))
+  TEST_INVALID(static_cast<LRef const&&>(l_ref))
+
+  RRef r_ref{};
+  TEST_INVALID(r_ref)
+  TEST_OPS(static_cast<RRef&&>(r_ref))
+  TEST_INVALID(static_cast<RRef const&>(r_ref))
+  TEST_INVALID(static_cast<RRef const&&>(r_ref))
+
+  ConstLRef const_l_ref{};
+  TEST_OPS(const_l_ref)
+  TEST_OPS(static_cast<ConstLRef&&>(const_l_ref))
+  TEST_OPS(static_cast<ConstLRef const&>(const_l_ref))
+  TEST_OPS(static_cast<ConstLRef const&&>(const_l_ref))
+
+  ConstRRef const_r_ref{};
+  TEST_INVALID(const_r_ref)
+  TEST_INVALID(static_cast<ConstRRef&&>(const_r_ref))
+  TEST_INVALID(static_cast<ConstRRef const&>(const_r_ref))
+  TEST_OPS(static_cast<ConstRRef const&&>(const_r_ref))
+
+  Deduced deduced{};
+  TEST_OPS(deduced)
+  TEST_OPS(static_cast<Deduced&&>(deduced))
+  TEST_OPS(static_cast<Deduced const&>(deduced))
+  TEST_OPS(static_cast<Deduced const&&>(deduced))
+
+  VALIDATE_RETURN_TYPES(deduced, Deduced&)
+  VALIDATE_RETURN_TYPES(static_cast<Deduced&&>(deduced), Deduced&&)
+  VALIDATE_RETURN_TYPES(static_cast<Deduced const&>(deduced), Deduced const&)
+  VALIDATE_RETURN_TYPES(static_cast<Deduced const&&>(deduced), Deduced const&&)
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C
new file mode 100644
index 00000000000..790a6ec379f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C
@@ -0,0 +1,56 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// operators that are not required to be members
+// called in a non-dependent context
+// see header
+#include "explicit-obj-ops-non-mem.h"
+
+// noop, indicates which versions are ill-formed
+// I could not find a way to test the invalid cases
+// without requires expressions
+#define TEST_INVALID(X)
+
+void do_calls()
+{
+  Value value{};
+  TEST_OPS(value)
+  TEST_OPS(static_cast<Value&&>(value))
+  TEST_OPS(static_cast<Value const&>(value))
+  TEST_OPS(static_cast<Value const&&>(value))
+  
+  LRef l_ref{};
+  TEST_OPS(l_ref)
+  TEST_INVALID(static_cast<LRef&&>(l_ref))
+  TEST_INVALID(static_cast<LRef const&>(l_ref))
+  TEST_INVALID(static_cast<LRef const&&>(l_ref))
+
+  RRef r_ref{};
+  TEST_INVALID(r_ref)
+  TEST_OPS(static_cast<RRef&&>(r_ref))
+  TEST_INVALID(static_cast<RRef const&>(r_ref))
+  TEST_INVALID(static_cast<RRef const&&>(r_ref))
+
+  ConstLRef const_l_ref{};
+  TEST_OPS(const_l_ref)
+  TEST_OPS(static_cast<ConstLRef&&>(const_l_ref))
+  TEST_OPS(static_cast<ConstLRef const&>(const_l_ref))
+  TEST_OPS(static_cast<ConstLRef const&&>(const_l_ref))
+
+  ConstRRef const_r_ref{};
+  TEST_INVALID(const_r_ref)
+  TEST_INVALID(static_cast<ConstRRef&&>(const_r_ref))
+  TEST_INVALID(static_cast<ConstRRef const&>(const_r_ref))
+  TEST_OPS(static_cast<ConstRRef const&&>(const_r_ref))
+
+  Deduced deduced{};
+  TEST_OPS(deduced)
+  TEST_OPS(static_cast<Deduced&&>(deduced))
+  TEST_OPS(static_cast<Deduced const&>(deduced))
+  TEST_OPS(static_cast<Deduced const&&>(deduced))
+
+  VALIDATE_RETURN_TYPES(deduced, Deduced&)
+  VALIDATE_RETURN_TYPES(static_cast<Deduced&&>(deduced), Deduced&&)
+  VALIDATE_RETURN_TYPES(static_cast<Deduced const&>(deduced), Deduced const&)
+  VALIDATE_RETURN_TYPES(static_cast<Deduced const&&>(deduced), Deduced const&&)
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem.h b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem.h
new file mode 100644
index 00000000000..b94e56b1dd6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem.h
@@ -0,0 +1,202 @@ 
+// missing test for three-way-compare (I don't know how to write it)
+// missing test for ->* (I don't know how to write it)
+
+// tests for ops that must be member functions are seperate
+
+#define MAKE_STRUCT_OPS(TYPE) \
+  TYPE operator+=(this TYPE self, int) { return self; }		\
+  TYPE operator-=(this TYPE self, int) { return self; }		\
+  TYPE operator*=(this TYPE self, int) { return self; }		\
+  TYPE operator/=(this TYPE self, int) { return self; }		\
+  TYPE operator%=(this TYPE self, int) { return self; }		\
+  TYPE operator&=(this TYPE self, int) { return self; }		\
+  TYPE operator|=(this TYPE self, int) { return self; }		\
+  TYPE operator^=(this TYPE self, int) { return self; }		\
+  TYPE operator<<=(this TYPE self, int) { return self; }	\
+  TYPE operator>>=(this TYPE self, int) { return self; }	\
+  TYPE operator++(this TYPE self) { return self; }		\
+  TYPE operator--(this TYPE self) { return self; }		\
+  TYPE operator++(this TYPE self, int) { return self; }		\
+  TYPE operator--(this TYPE self, int) { return self; }		\
+  TYPE operator+(this TYPE self) { return self; }		\
+  TYPE operator-(this TYPE self) { return self; }		\
+  TYPE operator+(this TYPE self, int) { return self; }		\
+  TYPE operator-(this TYPE self, int) { return self; }		\
+  TYPE operator*(this TYPE self, int) { return self; }		\
+  TYPE operator/(this TYPE self, int) { return self; }		\
+  TYPE operator%(this TYPE self, int) { return self; }		\
+  TYPE operator&(this TYPE self, int) { return self; }		\
+  TYPE operator|(this TYPE self, int) { return self; }		\
+  TYPE operator^(this TYPE self, int) { return self; }		\
+  TYPE operator<<(this TYPE self, int) { return self; }		\
+  TYPE operator>>(this TYPE self, int) { return self; }		\
+  TYPE operator!(this TYPE self) { return self; }		\
+  TYPE operator&&(this TYPE self, int const&) { return self; }	\
+  TYPE operator||(this TYPE self, int const&) { return self; }	\
+  TYPE operator==(this TYPE self, int) { return self; }		\
+  TYPE operator!=(this TYPE self, int) { return self; }		\
+  TYPE operator<(this TYPE self, int) { return self; }		\
+  TYPE operator>(this TYPE self, int) { return self; }		\
+  TYPE operator<=(this TYPE self, int) { return self; }		\
+  TYPE operator>=(this TYPE self, int) { return self; }		\
+  TYPE operator*(this TYPE self) { return self; }		\
+  TYPE operator&(this TYPE self) { return self; }		\
+  TYPE operator,(this TYPE self, int) { return self; }
+
+struct Value {
+  MAKE_STRUCT_OPS (Value)
+};
+
+struct LRef {
+  MAKE_STRUCT_OPS (LRef&)
+};
+
+struct RRef {
+  MAKE_STRUCT_OPS (RRef&&)
+};
+
+struct ConstLRef {
+  MAKE_STRUCT_OPS (ConstLRef const&)
+};
+
+struct ConstRRef {
+  MAKE_STRUCT_OPS (ConstRRef const&&)
+};
+
+#undef MAKE_STRUCT_OPS
+
+struct Deduced {
+  template<typename Self> Self&& operator+=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator-=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator*=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator/=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator%=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator&=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator|=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator^=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator<<=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator>>=(this Self&& self, int) { return static_cast<Self&&>(self); }
+
+  template<typename Self> Self&& operator++(this Self&& self) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator--(this Self&& self) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator++(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator--(this Self&& self, int) { return static_cast<Self&&>(self); }
+
+  template<typename Self> Self&& operator+(this Self&& self) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator-(this Self&& self) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator+(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator-(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator*(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator/(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator%(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator&(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator|(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator^(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator<<(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator>>(this Self&& self, int) { return static_cast<Self&&>(self); }
+
+  template<typename Self> Self&& operator!(this Self&& self) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator&&(this Self&& self, int const&) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator||(this Self&& self, int const&) { return static_cast<Self&&>(self); }
+
+  template<typename Self> Self&& operator==(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator!=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator<(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator>(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator<=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator>=(this Self&& self, int) { return static_cast<Self&&>(self); }
+
+  template<typename Self> Self&& operator*(this Self&& self) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator&(this Self&& self) { return static_cast<Self&&>(self); }
+  template<typename Self> Self&& operator,(this Self&& self, int) { return static_cast<Self&&>(self); }
+};
+
+#define TEST_OPS(OPERAND) \
+  (OPERAND) += 0;	\
+  (OPERAND) -= 0;	\
+  (OPERAND) *= 0;	\
+  (OPERAND) /= 0;	\
+  (OPERAND) %= 0;	\
+  (OPERAND) &= 0;	\
+  (OPERAND) |= 0;	\
+  (OPERAND) ^= 0;	\
+  (OPERAND) <<= 0;	\
+  (OPERAND) >>= 0;	\
+			\
+  ++(OPERAND);		\
+  --(OPERAND);		\
+  (OPERAND)++;		\
+  (OPERAND)--;		\
+			\
+  +(OPERAND);		\
+  -(OPERAND);		\
+  (OPERAND) + 0;	\
+  (OPERAND) - 0;	\
+  (OPERAND) * 0;	\
+  (OPERAND) / 0;	\
+  (OPERAND) % 0;	\
+  (OPERAND) & 0;	\
+  (OPERAND) | 0;	\
+  (OPERAND) ^ 0;	\
+  (OPERAND) << 0;	\
+  (OPERAND) >> 0;	\
+			\
+  !(OPERAND);		\
+  (OPERAND) && 0;	\
+  (OPERAND) || 0;	\
+			\
+  (OPERAND) == 0;	\
+  (OPERAND) != 0;	\
+  (OPERAND) < 0;	\
+  (OPERAND) > 0;	\
+  (OPERAND) <= 0;	\
+  (OPERAND) >= 0;	\
+			\
+  *(OPERAND);		\
+  &(OPERAND);		\
+  (OPERAND), 0;
+
+#define VALIDATE_RETURN_TYPES(OPERAND, CORRECT_TYPE) \
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) += 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) -= 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) *= 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) /= 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) %= 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) &= 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) |= 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) ^= 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) <<= 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) >>= 0)));		\
+										\
+  static_assert(__is_same(CORRECT_TYPE, decltype(++(OPERAND))));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype(--(OPERAND))));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND)++)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND)--)));		\
+										\
+  static_assert(__is_same(CORRECT_TYPE, decltype(+(OPERAND))));			\
+  static_assert(__is_same(CORRECT_TYPE, decltype(-(OPERAND))));			\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) + 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) - 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) * 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) / 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) % 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) & 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) | 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) ^ 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) << 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) >> 0)));		\
+										\
+  static_assert(__is_same(CORRECT_TYPE, decltype(!(OPERAND))));			\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) && 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) || 0)));		\
+										\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) == 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) != 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) < 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) > 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) <= 0)));		\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND) >= 0)));		\
+										\
+  static_assert(__is_same(CORRECT_TYPE, decltype(*(OPERAND))));			\
+  static_assert(__is_same(CORRECT_TYPE, decltype(&(OPERAND))));			\
+  static_assert(__is_same(CORRECT_TYPE, decltype((OPERAND), 0)));
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-mem.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-mem.C
new file mode 100644
index 00000000000..b5f88c38f32
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-mem.C
@@ -0,0 +1,172 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// well-formed and ill-formed uses of member only operators in a requires expression
+
+// suppress the warning for Value's arrow operator
+// { dg-options "-Wno-return-local-addr" }
+
+// { dg-excess-errors "Known issue with operators" { xfail *-*-* } }
+
+// It's very hard to test for incorrect successes without requires, and by extension a non dependent variable
+// so for the time being, there are no non dependent tests invalid calls.
+
+struct Value {
+  int _v;
+  Value operator=(this Value self, int) { return self; }
+  Value operator()(this Value self) { return self; }
+  Value operator[](this Value self) { return self; }
+  Value* operator->(this Value self) { return &self; }
+};
+
+struct LRef {
+  int _v;
+  LRef& operator=(this LRef& self, int) { return self; }
+  LRef& operator()(this LRef& self) { return self; }
+  LRef& operator[](this LRef& self) { return self; }
+  LRef* operator->(this LRef& self) { return &self; }
+};
+
+struct RRef {
+  int _v;
+  RRef&& operator=(this RRef&& self, int) { return static_cast<RRef&&>(self); }
+  RRef&& operator()(this RRef&& self) { return static_cast<RRef&&>(self) }
+  RRef&& operator[](this RRef&& self) { return static_cast<RRef&&>(self) }
+  RRef* operator->(this RRef&& self) { return &self; }
+};
+
+struct ConstLRef {
+  int _v;
+  ConstLRef const& operator=(this ConstLRef const& self, int) { return self; }
+  ConstLRef const& operator()(this ConstLRef const& self) { return self; }
+  ConstLRef const& operator[](this ConstLRef const& self) { return self; }
+  ConstLRef const* operator->(this ConstLRef const& self) { return &self; }
+};
+
+struct ConstRRef {
+  int _v;
+  ConstRRef const&& operator=(this ConstRRef const&& self, int) { return static_cast<ConstRRef const&&>(self); }
+  ConstRRef const&& operator()(this ConstRRef const&& self) { return static_cast<ConstRRef const&&>(self); }
+  ConstRRef const&& operator[](this ConstRRef const&& self) { return static_cast<ConstRRef const&&>(self); }
+  ConstRRef const* operator->(this ConstRRef const&& self) { return &self; }
+};
+
+// needed to implement deduced operator->
+template<typename T> struct remove_ref { using type = T; };
+template<typename T> struct remove_ref<T&> { using type = T; };
+template<typename T> struct remove_ref<T&&> { using type = T; };
+template<typename T> using remove_ref_t = typename remove_ref<T>::type;
+
+struct Deduced {
+  int _v;
+  template<typename Self>
+  Self&& operator=(this Self&& self, int) { return static_cast<Self&&>(self); }
+  template<typename Self>
+  Self&& operator()(this Self&& self) { return static_cast<Self&&>(self); }
+  template<typename Self>
+  Self&& operator[](this Self&& self) { return static_cast<Self&&>(self); }
+  template<typename Self>
+  remove_ref_t<Self>* operator->(this Self&& self) { return &self; }
+};
+
+#define TEST_INVALID(OPERAND) \
+  static_assert(!requires{ (OPERAND) = 0; }, "Unexpected success calling operator = with " #OPERAND);	\
+  static_assert(!requires{ (OPERAND)(); }, "Unexpected success calling operator () with " #OPERAND);	\
+  static_assert(!requires{ (OPERAND)[]; }, "Unexpected success calling operator [] with " #OPERAND);	\
+  static_assert(!requires{ (OPERAND)->_v; }, "Unexpected success calling operator -> with " #OPERAND);
+
+#define TEST_VALID(OPERAND) \
+  static_assert(requires{ (OPERAND) = 0; }, "Unexpected failure calling operator = with " #OPERAND);	\
+  static_assert(requires{ (OPERAND)(); }, "Unexpected failure calling operator () with " #OPERAND);	\
+  static_assert(requires{ (OPERAND)[]; }, "Unexpected failure calling operator [] with " #OPERAND);	\
+  static_assert(requires{ (OPERAND)->_v; }, "Unexpected failure calling operator -> with " #OPERAND);
+
+template<typename T, typename U>
+concept same_as = __is_same(T, U);
+
+#define TEST_VALID_WITH_RETURN_TYPES(OPERAND, CORRECT_TYPE) \
+  static_assert(requires{ {(OPERAND) = 0} -> same_as<CORRECT_TYPE>; },"Unexpected failure with return type check calling operator = with " #OPERAND " -> expected return type: " #CORRECT_TYPE);	\
+  static_assert(requires{ {(OPERAND)()} -> same_as<CORRECT_TYPE>; },  "Unexpected failure with return type check calling operator () with " #OPERAND " -> expected return type: " #CORRECT_TYPE);	\
+  static_assert(requires{ {(OPERAND)[]} -> same_as<CORRECT_TYPE>; },  "Unexpected failure with return type check calling operator [] with " #OPERAND " -> expected return type: " #CORRECT_TYPE);
+  
+
+template<typename DepValue = Value>
+void test_value()
+{
+  DepValue value{};
+  TEST_VALID(value)
+  TEST_VALID(static_cast<DepValue&&>(value))
+  TEST_VALID(static_cast<DepValue const&>(value))
+  TEST_VALID(static_cast<DepValue const&&>(value))
+}
+
+template<typename DepLRef = LRef>
+void test_l_ref()
+{
+  DepLRef l_ref{};
+  TEST_VALID(l_ref)
+  TEST_INVALID(static_cast<DepLRef&&>(l_ref))
+  TEST_INVALID(static_cast<DepLRef const&>(l_ref))
+  TEST_INVALID(static_cast<DepLRef const&&>(l_ref))
+}
+
+template<typename DepRRef = RRef>
+void test_r_ref()
+{
+  DepRRef r_ref{};
+  TEST_INVALID(r_ref)
+  TEST_VALID(static_cast<DepRRef&&>(r_ref))
+  TEST_INVALID(static_cast<DepRRef const&>(r_ref))
+  TEST_INVALID(static_cast<DepRRef const&&>(r_ref))
+}
+
+template<typename DepConstLRef = ConstLRef>
+void test_const_l_ref()
+{
+  DepConstLRef const_l_ref{};
+  TEST_VALID(const_l_ref)
+  TEST_VALID(static_cast<DepConstLRef&&>(const_l_ref))
+  TEST_VALID(static_cast<DepConstLRef const&>(const_l_ref))
+  TEST_VALID(static_cast<DepConstLRef const&&>(const_l_ref))
+}
+
+template<typename DepConstRRef = ConstRRef>
+void test_const_r_ref()
+{
+  DepConstRRef const_r_ref{};
+  TEST_INVALID(const_r_ref)
+  TEST_INVALID(static_cast<DepConstRRef&&>(const_r_ref))
+  TEST_INVALID(static_cast<DepConstRRef const&>(const_r_ref))
+  TEST_VALID(static_cast<DepConstRRef const&&>(const_r_ref))
+}
+
+template<typename DepDeduced = Deduced>
+void test_deduced()
+{
+  DepDeduced deduced{};
+
+  TEST_VALID(deduced)
+  TEST_VALID(static_cast<DepDeduced&&>(deduced))
+  TEST_VALID(static_cast<DepDeduced const&>(deduced))
+  TEST_VALID(static_cast<DepDeduced const&&>(deduced))
+
+  TEST_VALID_WITH_RETURN_TYPES(deduced, DepDeduced&)
+  TEST_VALID_WITH_RETURN_TYPES(static_cast<DepDeduced&&>(deduced), DepDeduced&&)
+  TEST_VALID_WITH_RETURN_TYPES(static_cast<DepDeduced const&>(deduced), DepDeduced const&)
+  TEST_VALID_WITH_RETURN_TYPES(static_cast<DepDeduced const&&>(deduced), DepDeduced const&&)
+  // arrow operator needs to be seperate to check the type of _v
+  static_assert(requires{ {(deduced->_v)} -> same_as<int&>; }, "Unexpected failure with return type check calling operator -> with deduced->_v");
+  static_assert(requires{ {(static_cast<DepDeduced&&>(deduced)->_v)} -> same_as<int const&>; }, "Unexpected failure with return type check calling operator -> with static_cast<DepDeduced&&>(deduced)->_v");
+  static_assert(requires{ {(static_cast<DepDeduced const&>(deduced)->_v)} -> same_as<int&>; }, "Unexpected failure with return type check calling operator -> with static_cast<DepDeduced const&>(deduced)->_v");
+  static_assert(requires{ {(static_cast<DepDeduced const&&>(deduced)->_v)} -> same_as<int const&>; }, "Unexpected failure with return type check calling operator -> with static_cast<DepDeduced const&&>(deduced)->_v");
+}
+
+void test()
+{
+  test_value();
+  test_l_ref();
+  test_r_ref();
+  test_const_l_ref();
+  test_const_r_ref();
+  test_deduced();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C
new file mode 100644
index 00000000000..981ab39b79d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C
@@ -0,0 +1,229 @@ 
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// well-formed and ill-formed uses of non-member capable operators in a requires expression
+
+// { dg-excess-errors "Known issue with operators" { xfail *-*-* } }
+
+#include "explicit-obj-ops-non-mem.h"
+
+// we only need the structs from the header
+#undef TEST_OPS
+#undef VALIDATE_RETURN_TYPES
+
+// It's very hard to test for incorrect successes without requires, and by extension a non dependent variable
+// so for the time being, there are no non dependent tests invalid calls.
+
+#define TEST_INVALID(OPERAND) \
+  static_assert(!requires{ (OPERAND) += 0; }, "Unexpected success calling operator += with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) -= 0; }, "Unexpected success calling operator -= with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) *= 0; }, "Unexpected success calling operator *= with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) /= 0; }, "Unexpected success calling operator /= with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) %= 0; }, "Unexpected success calling operator %= with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) &= 0; }, "Unexpected success calling operator &= with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) |= 0; }, "Unexpected success calling operator |= with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) ^= 0; }, "Unexpected success calling operator ^= with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) <<= 0; }, "Unexpected success calling operator <<= with " #OPERAND);	\
+  static_assert(!requires{ (OPERAND) >>= 0; }, "Unexpected success calling operator >>= with " #OPERAND);	\
+														\
+  static_assert(!requires{ ++(OPERAND); }, "Unexpected success calling operator pre++ with " #OPERAND);		\
+  static_assert(!requires{ --(OPERAND); }, "Unexpected success calling operator pre-- with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND)++; }, "Unexpected success calling operator post++ with " #OPERAND);	\
+  static_assert(!requires{ (OPERAND)--; }, "Unexpected success calling operator post-- with " #OPERAND);	\
+														\
+  static_assert(!requires{ +(OPERAND); }, "Unexpected success calling operator unary+ with " #OPERAND);		\
+  static_assert(!requires{ -(OPERAND); }, "Unexpected success calling operator unary- with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) + 0; }, "Unexpected success calling operator binary+ with " #OPERAND);	\
+  static_assert(!requires{ (OPERAND) - 0; }, "Unexpected success calling operator binary- with " #OPERAND);	\
+  static_assert(!requires{ (OPERAND) * 0; }, "Unexpected success calling operator binary* with " #OPERAND);	\
+  static_assert(!requires{ (OPERAND) / 0; }, "Unexpected success calling operator / with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) % 0; }, "Unexpected success calling operator % with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) & 0; }, "Unexpected success calling operator binary& with " #OPERAND);	\
+  static_assert(!requires{ (OPERAND) | 0; }, "Unexpected success calling operator | with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) ^ 0; }, "Unexpected success calling operator ^ with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) << 0; }, "Unexpected success calling operator << with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) >> 0; }, "Unexpected success calling operator >> with " #OPERAND);		\
+														\
+  static_assert(!requires{ !(OPERAND); }, "Unexpected success calling operator ! with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) && 0; }, "Unexpected success calling operator && with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) || 0; }, "Unexpected success calling operator || with " #OPERAND);		\
+														\
+  static_assert(!requires{ (OPERAND) == 0; }, "Unexpected success calling operator == with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) != 0; }, "Unexpected success calling operator != with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) < 0; }, "Unexpected success calling operator < with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) > 0; }, "Unexpected success calling operator > with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) <= 0; }, "Unexpected success calling operator <= with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) >= 0; }, "Unexpected success calling operator >= with " #OPERAND);		\
+														\
+  static_assert(!requires{ *(OPERAND); }, "Unexpected success calling operator unary* with " #OPERAND);		\
+  static_assert(!requires{ &(OPERAND); }, "Unexpected success calling operator unary& with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND), 0; }, "Unexpected success calling operator , with " #OPERAND);
+
+#define TEST_VALID(OPERAND) \
+  static_assert(requires{ (OPERAND) += 0; }, "Unexpected failure calling operator += with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) -= 0; }, "Unexpected failure calling operator -= with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) *= 0; }, "Unexpected failure calling operator *= with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) /= 0; }, "Unexpected failure calling operator /= with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) %= 0; }, "Unexpected failure calling operator %= with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) &= 0; }, "Unexpected failure calling operator &= with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) |= 0; }, "Unexpected failure calling operator |= with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) ^= 0; }, "Unexpected failure calling operator ^= with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) <<= 0; }, "Unexpected failure calling operator <<= with " #OPERAND);	\
+  static_assert(requires{ (OPERAND) >>= 0; }, "Unexpected failure calling operator >>= with " #OPERAND);	\
+														\
+  static_assert(requires{ ++(OPERAND); }, "Unexpected failure calling operator pre++ with " #OPERAND);		\
+  static_assert(requires{ --(OPERAND); }, "Unexpected failure calling operator pre-- with " #OPERAND);		\
+  static_assert(requires{ (OPERAND)++; }, "Unexpected failure calling operator post++ with " #OPERAND);		\
+  static_assert(requires{ (OPERAND)--; }, "Unexpected failure calling operator post-- with " #OPERAND);		\
+														\
+  static_assert(requires{ +(OPERAND); }, "Unexpected failure calling operator unary+ with " #OPERAND);		\
+  static_assert(requires{ -(OPERAND); }, "Unexpected failure calling operator unary- with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) + 0; }, "Unexpected failure calling operator binary+ with " #OPERAND);	\
+  static_assert(requires{ (OPERAND) - 0; }, "Unexpected failure calling operator binary- with " #OPERAND);	\
+  static_assert(requires{ (OPERAND) * 0; }, "Unexpected failure calling operator binary* with " #OPERAND);	\
+  static_assert(requires{ (OPERAND) / 0; }, "Unexpected failure calling operator / with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) % 0; }, "Unexpected failure calling operator % with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) & 0; }, "Unexpected failure calling operator binary& with " #OPERAND);	\
+  static_assert(requires{ (OPERAND) | 0; }, "Unexpected failure calling operator | with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) ^ 0; }, "Unexpected failure calling operator ^ with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) << 0; }, "Unexpected failure calling operator << with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) >> 0; }, "Unexpected failure calling operator >> with " #OPERAND);		\
+														\
+  static_assert(requires{ !(OPERAND); }, "Unexpected failure calling operator ! with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) && 0; }, "Unexpected failure calling operator && with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) || 0; }, "Unexpected failure calling operator || with " #OPERAND);		\
+														\
+  static_assert(requires{ (OPERAND) == 0; }, "Unexpected failure calling operator == with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) != 0; }, "Unexpected failure calling operator != with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) < 0; }, "Unexpected failure calling operator < with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) > 0; }, "Unexpected failure calling operator > with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) <= 0; }, "Unexpected failure calling operator <= with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) >= 0; }, "Unexpected failure calling operator >= with " #OPERAND);		\
+														\
+  static_assert(requires{ *(OPERAND); }, "Unexpected failure calling operator unary* with " #OPERAND);		\
+  static_assert(requires{ &(OPERAND); }, "Unexpected failure calling operator unary& with " #OPERAND);		\
+  static_assert(requires{ (OPERAND), 0; }, "Unexpected failure calling operator , with " #OPERAND);
+
+template<typename T, typename U>
+concept same_as = __is_same(T, U);
+
+#define TEST_VALID_WITH_RETURN_TYPES(OPERAND, CORRECT_TYPE) \
+  static_assert(requires{ {(OPERAND) += 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) -= 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) *= 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) /= 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) %= 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) &= 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) |= 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) ^= 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) <<= 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) >>= 0} -> same_as<CORRECT_TYPE>; });	\
+										\
+  static_assert(requires{ {++(OPERAND)} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {--(OPERAND)} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND)++} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND)--} -> same_as<CORRECT_TYPE>; });		\
+										\
+  static_assert(requires{ {+(OPERAND)} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {-(OPERAND)} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) + 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) - 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) * 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) / 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) % 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) & 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) | 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) ^ 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) << 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) >> 0} -> same_as<CORRECT_TYPE>; });	\
+										\
+  static_assert(requires{ {!(OPERAND)} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) && 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) || 0} -> same_as<CORRECT_TYPE>; });	\
+										\
+  static_assert(requires{ {(OPERAND) == 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) != 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) < 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) > 0} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND) <= 0} -> same_as<CORRECT_TYPE>; });	\
+  static_assert(requires{ {(OPERAND) >= 0} -> same_as<CORRECT_TYPE>; });	\
+										\
+  static_assert(requires{ {*(OPERAND)} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {&(OPERAND)} -> same_as<CORRECT_TYPE>; });		\
+  static_assert(requires{ {(OPERAND), 0} -> same_as<CORRECT_TYPE>; });
+
+template<typename DepValue = Value>
+void test_value()
+{
+  DepValue value{};
+  TEST_VALID(value)
+  TEST_VALID(static_cast<DepValue&&>(value))
+  TEST_VALID(static_cast<DepValue const&>(value))
+  TEST_VALID(static_cast<DepValue const&&>(value))
+}
+
+template<typename DepLRef = LRef>
+void test_l_ref()
+{
+  DepLRef l_ref{};
+  TEST_VALID(l_ref)
+  TEST_INVALID(static_cast<DepLRef&&>(l_ref))
+  TEST_INVALID(static_cast<DepLRef const&>(l_ref))
+  TEST_INVALID(static_cast<DepLRef const&&>(l_ref))
+}
+
+template<typename DepRRef = RRef>
+void test_r_ref()
+{
+  DepRRef r_ref{};
+  TEST_INVALID(r_ref)
+  TEST_VALID(static_cast<DepRRef&&>(r_ref))
+  TEST_INVALID(static_cast<DepRRef const&>(r_ref))
+  TEST_INVALID(static_cast<DepRRef const&&>(r_ref))
+}
+
+template<typename DepConstLRef = ConstLRef>
+void test_const_l_ref()
+{
+  DepConstLRef const_l_ref{};
+  TEST_VALID(const_l_ref)
+  TEST_VALID(static_cast<DepConstLRef&&>(const_l_ref))
+  TEST_VALID(static_cast<DepConstLRef const&>(const_l_ref))
+  TEST_VALID(static_cast<DepConstLRef const&&>(const_l_ref))
+}
+
+template<typename DepConstRRef = ConstRRef>
+void test_const_r_ref()
+{
+  DepConstRRef const_r_ref{};
+  TEST_INVALID(const_r_ref)
+  TEST_INVALID(static_cast<DepConstRRef&&>(const_r_ref))
+  TEST_INVALID(static_cast<DepConstRRef const&>(const_r_ref))
+  TEST_VALID(static_cast<DepConstRRef const&&>(const_r_ref))
+}
+
+template<typename DepDeduced = Deduced>
+void test_deduced()
+{
+  DepDeduced deduced{};
+
+  TEST_VALID(deduced)
+  TEST_VALID(static_cast<DepDeduced&&>(deduced))
+  TEST_VALID(static_cast<DepDeduced const&>(deduced))
+  TEST_VALID(static_cast<DepDeduced const&&>(deduced))
+
+  TEST_VALID_WITH_RETURN_TYPES(deduced, DepDeduced&)
+  TEST_VALID_WITH_RETURN_TYPES(static_cast<DepDeduced&&>(deduced), DepDeduced&&)
+  TEST_VALID_WITH_RETURN_TYPES(static_cast<DepDeduced const&>(deduced), DepDeduced const&)
+  TEST_VALID_WITH_RETURN_TYPES(static_cast<DepDeduced const&&>(deduced), DepDeduced const&&)
+}
+
+void test()
+{
+  test_value();
+  test_l_ref();
+  test_r_ref();
+  test_const_l_ref();
+  test_const_r_ref();
+  test_deduced();
+}
-- 
2.42.0