public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
@ 2023-08-31  6:02 waffl3x
  2023-08-31  8:33 ` Jakub Jelinek
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-08-31  6:02 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 3473 bytes --]

Bootstrapped and tested on x86_64-linux with no regressions.

I would like to quickly thank everyone who helped me for their patience as I
learned the ropes of the codebase and toolchain. It is much appretiated and this would
have been much much more difficult without the support.

This patch handles the new explicit object member functions by bypassing
special behavior of implicit object member functions, but opting back into the special
behavior during a function call through member access. This is mainly accomplished by
bypassing becoming a METHOD_TYPE and remaining as a FUNCTION_TYPE. Normally, this would
be treated as a static member function, but a new explicit object member function
flag is added to lang_decl_base and is set for the declaration of explicit object
member functions. This sets it apart from static member functions when it is
relevant, and is the criteria used to opt-in to passing the implicit object argument
during a member function call. The benefit of this design is less code needs to be
modified to support the new feature, as most of the semantics of explicit object member
functions matches those of static member functions. There is very little left to add,
and hopefully there are few bugs in the implementation despite the minimal
changes.

It is possible there are hidden problems with passing the implicit object
argument, but none of the tests I made exhibit such a thing EXCEPT for in the
pathological case as I describe below. Upon reflection, a by value explicit object
parameter might be broken as well, I can't recall if there's a good test for that case.

Lambdas do not work yet, but you can work around it by marking it as mutable
so I suspect it could be supported with minimal changes, I just ran out of time.
The other thing that does not work is the pathological case with an explicit
object parameter of an unrelated type and relying on a user-defined conversion
operator to cast to said type in a call to that function. You can observe the failing
test for that case in explicit-object-param-valid4.C, the result is somewhat
interesting, but is also why I mention that there might be hidden problems here.

I selectively excluded all the diagnostics from this patch, it's possible I
made a mistake and the patch will be non-functional without the addition of the
diagnostics patch. If that ends up being the case, please apply the following patch that
includes the diagnostics and tests and judge the functionality from that. I believe
that even if I mess up this patch, there should still be value in splitting up the
changes into the two patches as it should make the changes to the behavior of the
compiler much more clear.
With that said, I believe I didn't make any mistakes while seperating the two
patches, hopefully that is the case.

I left in a FIXME (in call.cc) as I noticed last minute that I made a mistake,
it should be benign and removing it appears to not break anything, but I don't
have time to do another bootstrap at the moment. My priority is to get eyes on the
changes I've made and recieve feedback.

The patch including the diagnostics will follow shortly, assuming I don't run
out of time and need to rush to catch my flight :).

PS: Are there any circumstances where TREE_CODE is FUNCTION_DECL but the
lang_specific member is null? I have a null check for that case in DECL_IS_XOBJ_MEMBER_FUNC
but I question if it's necessary. 


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-P0847R7-deducing-this-Initial-support.patch --]
[-- Type: text/x-patch; name=0001-P0847R7-deducing-this-Initial-support.patch, Size: 16588 bytes --]

From e485a79ec5656e72ba46053618843c3d69331eab Mon Sep 17 00:00:00 2001
From: Waffl3x <waffl3x@protonmail.com>
Date: Thu, 31 Aug 2023 01:05:25 -0400
Subject: [PATCH] P0847R7 (deducing this) Initial support

Most things should be functional, lambdas need a little more work though.
Limitations: Missing support for lambdas, and user defined conversion functions when passing the implicit object argument does not work properly. See explicit-object-param-valid4.C for an example of the current (errent) behavior.

There is a slight mistake in call.cc, it should be benign.

gcc/cp/ChangeLog:

	* call.cc (add_function_candidate): (Hopefully) benign mistake
	(add_candidates): Treat explicit object member functions as member functions when considering candidates
	(build_over_call): Enable passing an implicit object argument when calling an explicit object member function
	* cp-tree.h (struct lang_decl_base): Added member xobj_flag for differentiating explicit object member functions from static member functions
	(DECL_FUNC_XOBJ_FLAG): New, checked access for xobj_flag
	(DECL_PARM_XOBJ_FLAG): New, access decl_flag_3
	(DECL_IS_XOBJ_MEMBER_FUNC): New, safely check if a node is an explicit object member function
	(enum cp_decl_spec): Support parsing 'this' as a decl spec, change is mirrored in parser.cc:set_and_check_decl_spec_loc
	* decl.cc (grokfndecl): Sets the xobj flag for the FUNCTION_DECL if the first parameter is an explicit object parameter
	(grokdeclarator): Sets the xobj flag for PARM_DECL if 'this' spec is present in declspecs, bypasses conversion from FUNCTION_DECL to METHOD_DECL if an xobj flag is set for the first parameter of the given function declarator
	* parser.cc (cp_parser_decl_specifier_seq): check for 'this' specifier
	(set_and_check_decl_spec_loc): extended decl_spec_names to support 'this', change is mirrored in cp-tree.h:cp_decl_spec

gcc/ChangeLog:

	* tree-core.h (struct tree_decl_common): Added comment describing new use of decl_flag_3

gcc/testsuite/ChangeLog:

	* g++.dg/cpp23/explicit-object-param-valid1.C: New test.
	* g++.dg/cpp23/explicit-object-param-valid2.C: New test.
	* g++.dg/cpp23/explicit-object-param-valid3.C: New test.
	* g++.dg/cpp23/explicit-object-param-valid4.C: New test.

Signed-off-by: Waffl3x <waffl3x@protonmail.com>
---
 gcc/cp/call.cc                                |  13 +-
 gcc/cp/cp-tree.h                              |  20 +++-
 gcc/cp/decl.cc                                |  25 ++++
 gcc/cp/parser.cc                              |  15 ++-
 .../cpp23/explicit-object-param-valid1.C      | 113 ++++++++++++++++++
 .../cpp23/explicit-object-param-valid2.C      |  24 ++++
 .../cpp23/explicit-object-param-valid3.C      |  14 +++
 .../cpp23/explicit-object-param-valid4.C      |  33 +++++
 gcc/tree-core.h                               |   3 +-
 9 files changed, 254 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid4.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 673ec91d60e..ac5e414b084 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -2509,7 +2509,12 @@ add_function_candidate (struct z_candidate **candidates,
 	  tree parmtype = TREE_VALUE (parmnode);
 	  if (i == 0
 	      && DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
-	      && !DECL_CONSTRUCTOR_P (fn))
+	      && !DECL_CONSTRUCTOR_P (fn)
+	      /* FIXME: This doesn't seem to be neccesary, upon review I
+		 realized that it doesn't make sense (an xobj member func
+		 is not a nonstatic_member_function, so this check will
+		 never change anything) */
+	      && !DECL_IS_XOBJ_MEMBER_FUNC (fn))
 	    t = build_this_conversion (fn, ctype, parmtype, argtype, arg,
 				       flags, complain);
 	  else
@@ -6566,7 +6571,8 @@ 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))
+      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+	  || DECL_IS_XOBJ_MEMBER_FUNC (fn))
 	{
 	  /* Figure out where the object arg comes from.  If this
 	     function is a non-static member and we didn't get an
@@ -9995,7 +10001,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_IS_XOBJ_MEMBER_FUNC (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 eb901683b6d..3aca23da105 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2878,7 +2878,9 @@ struct GTY(()) lang_decl_base {
   /* VAR_DECL or FUNCTION_DECL has keyed decls.     */
   unsigned module_keyed_decls_p : 1;
 
-  /* 12 spare bits.  */
+  /* FUNCTION_DECL explicit object member function flag */
+  unsigned xobj_flag : 1;
+  /* 11 spare bits.  */
 };
 
 /* True for DECL codes which have template info and access.  */
@@ -3086,6 +3088,21 @@ struct GTY(()) lang_decl {
 
 #endif /* ENABLE_TREE_CHECKING */
 
+/* these need to moved to somewhere appropriate */
+
+/* the flag is a member of base, but the value is meaningless for other
+   decl types so checking is still justified I imagine */
+#define DECL_FUNC_XOBJ_FLAG(NODE) \
+  (LANG_DECL_FN_CHECK (NODE)->min.base.xobj_flag)
+/* not a lang_decl field, but still specific to c++ */
+#define DECL_PARM_XOBJ_FLAG(NODE) \
+  (PARM_DECL_CHECK (NODE)->decl_common.decl_flag_3)
+/* evaluates false for non func nodes and nodes with a null lang_decl member */
+#define DECL_IS_XOBJ_MEMBER_FUNC(NODE) 					 \
+  (TREE_CODE (STRIP_TEMPLATE (NODE)) == FUNCTION_DECL			 \
+   && DECL_LANG_SPECIFIC (STRIP_TEMPLATE (NODE))			 \
+   && DECL_LANG_SPECIFIC (STRIP_TEMPLATE (NODE))->u.base.xobj_flag == 1) \
+
 /* For a FUNCTION_DECL or a VAR_DECL, the language linkage for the
    declaration.  Some entities (like a member function in a local
    class, or a local variable) do not have linkage at all, and this
@@ -6275,6 +6292,7 @@ enum cp_decl_spec {
   ds_complex,
   ds_constinit,
   ds_consteval,
+  ds_this, /* inserting here to match decl_spec_names in parser.cc*/
   ds_thread,
   ds_type_spec,
   ds_redefined_builtin_type_spec,
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index bea0ee92106..a6d0cfb0ecc 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -10314,6 +10314,9 @@ grokfndecl (tree ctype,
   type = build_cp_fntype_variant (type, rqual, raises, late_return_type_p);
 
   decl = build_lang_decl_loc (location, FUNCTION_DECL, declarator, type);
+  /* all error checking has been done by now, just copy the flag over
+     parms is null (appears to be anyway) for 0 parm functions */
+  DECL_FUNC_XOBJ_FLAG (decl) = parms ? DECL_PARM_XOBJ_FLAG (parms) : false;
 
   /* Set the constraints on the declaration. */
   if (flag_concepts)
@@ -12966,6 +12969,19 @@ grokdeclarator (const cp_declarator *declarator,
   if (attrlist)
     diagnose_misapplied_contracts (*attrlist);
 
+  /* Only used for skipping over build_memfn_type, grokfndecl handles
+     copying the flag to the correct field for a func_decl.
+     I wish there was a better way to do this, but there doesn't seem to be */
+  bool is_xobj_member_function = false;
+  auto get_xobj_parm = [](tree parm_list)
+    {
+      if (!parm_list)
+	return NULL_TREE;
+      tree first_parm = TREE_VALUE (parm_list);
+      if (first_parm == void_type_node)
+	return NULL_TREE;
+      return DECL_PARM_XOBJ_FLAG (first_parm) == 1 ? first_parm : NULL_TREE;
+    };
   /* Determine the type of the entity declared by recurring on the
      declarator.  */
   for (; declarator; declarator = declarator->declarator)
@@ -13061,6 +13077,9 @@ grokdeclarator (const cp_declarator *declarator,
 
 	case cdk_function:
 	  {
+	    tree xobj_parm
+	      = get_xobj_parm (declarator->u.function.parameters);
+	    is_xobj_member_function = xobj_parm;
 	    tree arg_types;
 	    int funcdecl_p;
 
@@ -14145,6 +14164,8 @@ grokdeclarator (const cp_declarator *declarator,
     }
 
   if (ctype && TREE_CODE (type) == FUNCTION_TYPE && staticp < 2
+      /* bypass conversion to METHOD_TYPE if an xobj parm is present */
+      && !is_xobj_member_function
       && !(unqualified_id
 	   && identifier_p (unqualified_id)
 	   && IDENTIFIER_NEWDEL_OP_P (unqualified_id)))
@@ -14163,6 +14184,10 @@ grokdeclarator (const cp_declarator *declarator,
       {
 	decl = cp_build_parm_decl (NULL_TREE, unqualified_id, type);
 	DECL_ARRAY_PARAMETER_P (decl) = array_parameter_p;
+	/* Set the xobj flag for this parm, unfortunately
+	   I don't think there is a better way to do this */
+	DECL_PARM_XOBJ_FLAG (decl)
+	  = decl_spec_seq_has_spec_p (declspecs, ds_this);
 
 	bad_specifiers (decl, BSP_PARM, virtualp,
 			memfn_quals != TYPE_UNQUALIFIED,
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index eeb22e44fb4..ed5dcbde3ed 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -15875,6 +15875,18 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
 	    decl_specs->locations[ds_attribute] = token->location;
 	  continue;
 	}
+      /* Special case for xobj parm, doesn't really belong up here
+	 (it applies to parm decls and those are mostly handled below
+	 the following specifiers) but I intend to refactor this function
+	 so I'm not worrying about it too much.
+	 The error diagnostics might be better elsewhere though. */
+      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
@@ -33624,7 +33636,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/testsuite/g++.dg/cpp23/explicit-object-param-valid1.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid1.C
new file mode 100644
index 00000000000..12230cfc3d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid1.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>();
+}
\ No newline at end of file
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid2.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid2.C
new file mode 100644
index 00000000000..2f9a08207d4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid2.C
@@ -0,0 +1,24 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// explicit object member function pointer type deduction and 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};
+  // { dg-output "42" }
+  __builtin_printf("%d\n%d\n", fp0(s), fp1(s));
+}
\ No newline at end of file
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid3.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid3.C
new file mode 100644
index 00000000000..2b2bc458df8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid3.C
@@ -0,0 +1,14 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// recursive lambdas
+
+// { dg-excess-errors "deducing this with lambdas not implemented yet" { xfail *-*-* } }
+
+int main()
+{
+  auto cl0 = [](this auto&& self, int n){ return n ? self(n - 1) : 42 };
+  auto cl1 = [](this auto self, int n){ return n ? self(n - 1) : 42};
+  int a = cl0(5);
+  int b = cl1(5);
+}
\ No newline at end of file
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid4.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid4.C
new file mode 100644
index 00000000000..1e9ade62a51
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid4.C
@@ -0,0 +1,33 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// test implicit conversion of the object argument
+// to the explicit object parameter
+
+// we compare &s to ret because early on, the
+// object parameter would not convert, it would just get
+// reinterpreted as the type of the explicit object param
+
+// { dg-output "ret != &s : 1" { xfail *-*-* } }
+// { dg-output "ret == 42 : 1" { xfail *-*-* } }
+
+using uintptr_t = __UINTPTR_TYPE__;
+
+struct S {
+    operator uintptr_t() const {
+	return 42;
+    }
+    uintptr_t f(this uintptr_t n) {
+        return n;
+    }
+};
+
+int main() 
+{
+    S s{};
+    uintptr_t ret = s.f();
+    __builtin_printf("ret != &s : %d\n"
+		     "ret == 42 : %d\n",
+		     ret != reinterpret_cast<uintptr_t>(&s) ? 1 : 0,
+		     ret == 42 ? 1 : 0);
+}
\ No newline at end of file
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 91551fde900..e434bd7c9ac 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -1808,7 +1808,8 @@ struct GTY(()) tree_decl_common {
      DECL_HAS_VALUE_EXPR_P.  */
   unsigned decl_flag_2 : 1;
   /* In FIELD_DECL, this is DECL_PADDING_P.
-     In VAR_DECL, this is DECL_MERGEABLE.  */
+     In VAR_DECL, this is DECL_MERGEABLE.
+     In PARM_DECL, this is DECL_XOBJ_PARM. */
   unsigned decl_flag_3 : 1;
   /* Logically, these two would go in a theoretical base shared by var and
      parm decl. */
-- 
2.41.0


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-08-31  6:02 [PATCH 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609] waffl3x
@ 2023-08-31  8:33 ` Jakub Jelinek
  2023-09-02  8:43   ` waffl3x
  2023-09-19 20:24   ` [PATCH 1/2] " Jason Merrill
  0 siblings, 2 replies; 100+ messages in thread
From: Jakub Jelinek @ 2023-08-31  8:33 UTC (permalink / raw)
  To: waffl3x, Jason Merrill; +Cc: gcc-patches

On Thu, Aug 31, 2023 at 06:02:36AM +0000, waffl3x via Gcc-patches wrote:

> From e485a79ec5656e72ba46053618843c3d69331eab Mon Sep 17 00:00:00 2001
> From: Waffl3x <waffl3x@protonmail.com>
> Date: Thu, 31 Aug 2023 01:05:25 -0400
> Subject: [PATCH] P0847R7 (deducing this) Initial support
> 
> Most things should be functional, lambdas need a little more work though.
> Limitations: Missing support for lambdas, and user defined conversion functions when passing the implicit object argument does not work properly. See explicit-object-param-valid4.C for an example of the current (errent) behavior.
> 
> There is a slight mistake in call.cc, it should be benign.

Thanks for working on this.

Just some random mostly formatting comments, defering the actual patch
review to Jason.

The ChangeLog should refer to
	PR c++/102609

and ideally mention somewhere that it implements
C++23 P0847R7 - Deducing this.
so that when one quickly searches when that was implemented it can be found
easily.
ChangeLog entries should start with capital letter after ): and
end with .  And they should fit into 80 columns, one can wrap lines (but use
tab indentation even on the subsequent lines).
More importantly, should describe what changed and not why, if the why needs
explanation, it should go into comments in the code.

> gcc/cp/ChangeLog:
> 
> 	* call.cc (add_function_candidate): (Hopefully) benign mistake

So, this both misses . at the end and doesn't describe what changed.
	* call.cc (add_function_candidate): Don't call build_this_conversion
	for DECL_IS_XOBJ_MEMBER_FUNC.
?

> 	(add_candidates): Treat explicit object member functions as member functions when considering candidates

Too long line and missing . at the end

> 	(build_over_call): Enable passing an implicit object argument when calling an explicit object member function
> 	* cp-tree.h (struct lang_decl_base): Added member xobj_flag for differentiating explicit object member functions from static member functions

Just mention that xobj_flag member has been added, not what it is for.

> 	(DECL_FUNC_XOBJ_FLAG): New, checked access for xobj_flag
> 	(DECL_PARM_XOBJ_FLAG): New, access decl_flag_3
> 	(DECL_IS_XOBJ_MEMBER_FUNC): New, safely check if a node is an explicit object member function

These are macros, just say Define.
etc.

> 	(enum cp_decl_spec): Support parsing 'this' as a decl spec, change is mirrored in parser.cc:set_and_check_decl_spec_loc
> 	* decl.cc (grokfndecl): Sets the xobj flag for the FUNCTION_DECL if the first parameter is an explicit object parameter
> 	(grokdeclarator): Sets the xobj flag for PARM_DECL if 'this' spec is present in declspecs, bypasses conversion from FUNCTION_DECL to METHOD_DECL if an xobj flag is set for the first parameter of the given function declarator
> 	* parser.cc (cp_parser_decl_specifier_seq): check for 'this' specifier
> 	(set_and_check_decl_spec_loc): extended decl_spec_names to support 'this', change is mirrored in cp-tree.h:cp_decl_spec
> 
> gcc/ChangeLog:
> 
> 	* tree-core.h (struct tree_decl_common): Added comment describing new use of decl_flag_3
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp23/explicit-object-param-valid1.C: New test.
> 	* g++.dg/cpp23/explicit-object-param-valid2.C: New test.
> 	* g++.dg/cpp23/explicit-object-param-valid3.C: New test.
> 	* g++.dg/cpp23/explicit-object-param-valid4.C: New test.

I think usually we don't differentiate in testcase names whether
the test is to be accepted or rejected.
So, one would just go with explicit-object-param{1,2,3,4,5,6}.C etc.
for everything related to the feature.
Isn't explicit-object-param too long though?
explicit-this or deducing-this might be shorter...

> +	      /* FIXME: This doesn't seem to be neccesary, upon review I
> +		 realized that it doesn't make sense (an xobj member func
> +		 is not a nonstatic_member_function, so this check will
> +		 never change anything) */

Comments should end with a dot and two spaces before */

> @@ -9995,7 +10001,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_IS_XOBJ_MEMBER_FUNC (fn) )

No space between fn) and )

> +/* these need to moved to somewhere appropriate */

Comments should start with a capital letter and like above, end with
appropriate.  */

> +   && DECL_LANG_SPECIFIC (STRIP_TEMPLATE (NODE))->u.base.xobj_flag == 1) \

No \ after the last line of the macro.

> +}
> \ No newline at end of file

Please avoid these (unless testing preprocessor etc. that
it can handle even sources which don't end with a newline).

> diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid2.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid2.C
> new file mode 100644
> index 00000000000..2f9a08207d4
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid2.C
> @@ -0,0 +1,24 @@
> +// P0847R7
> +// { dg-do run { target c++23 } }

This raises an important question whether we as an extension
should support deducing this even in older standards or not.
I admit I haven't studied the paper enough to figure that out.
The syntax is certainly something that wasn't valid in older standards,
so from that POV it could be accepted say with pedwarn with
OPT_Wc__23_extensions if cxx_dialect < cxx23.  But perhaps some
of the rules in the paper change something unconditionally even when
the new syntax doesn't appear.
And, if it is accepted in older standards, the question is if it
shouldn't be banned say from C++98.

> +// explicit object member function pointer type deduction and 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};
> +  // { dg-output "42" }
> +  __builtin_printf("%d\n%d\n", fp0(s), fp1(s));

Usually runtime tests don't try to print something with dg-output
trying to match it, but instead just compare the values directly and
__builtin_abort () if something has incorrect value.
Also, in the above case, dg-output matches 42 anywhere in the output,
so if fp0(s) is 42 or if fp1(s) is 42.  I'd expect
  if (fp0 (s) != 42 || fp1 (s) != 42)
    __builtin_abort ();

	Jakub


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-08-31  8:33 ` Jakub Jelinek
@ 2023-09-02  8:43   ` waffl3x
  2023-09-11 13:49     ` [PATCH v2 " waffl3x
  2023-09-19 20:24   ` [PATCH 1/2] " Jason Merrill
  1 sibling, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-09-02  8:43 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: Jason Merrill, gcc-patches

Hey Jakub, thanks for the response
and criticism, as soon as I am
back at a computer I will address
the issues you raised, I have a few
questions though.

I apologize in advanced for any
errors in formatting this message,
I'm writing it from a hotel room
on a phone so errors are inevitable,
but I'll try my best.

>More importantly, should describe
>what changed and not why
I was under the impression that
if someone wants to see the what,
they can just check the diff.
I don't understand what should be
written if I'm just to say what,
wouldn't I just be repeating what
is already said in the code itself?

>I think usually we don't
>differentiate
>in testcase names whether
Yeah, for sure, but I felt like
there is value in differentiating
them and that it would be harmless
to do so. If you feel like it's
more important that convention is
followed here I won't object, but I
think this should be considered.

>Isn't explicit-object-param too long
>though?
>explicit-this or deducing-this might
>be shorter...
I agree, but I felt like I should
stick to the wording of the standard
for it, but I don't feel strongly
about that justification, so I
wouldn't object to changing it.
Truthfully I flip flopped many
times around the names, I'll defer
to whatever is decided by the
maintainers on that without complaint.

>> +}
>> \ No newline at end of file
>Please avoid these
Yeah, I noticed it last minute and
wasn't sure how big a deal it was.
I will make sure to fix it along
with everything else you noted.

>Usually runtime tests don't try to
>print something
Yeah, I didn't like printing, but
I'm not sure I like aborting either.
I value granularity in testing, if
one part of the test passes but
another fails, you would want to know
that. If you abort before the second
you lose that granularity.
Once again, I'll defer to the
maintainers for this, but I think
my points are valid here.

>This raises an important question whether we as an extension
>should support deducing this even in older standards or not.
>I admit I haven't studied the paper enough to figure that out.

I'm glad you think so, I fully agree.
I had planned to raise that once
the initial patch made it in. I don't
believe there is anything in the paper
in particular that breaks previous
standards.
I can imagine there being problems
if older projects tried to convert
everything to use it as there are
alignment differences (at least in
the current patch version) between
explicit object member functions and
implicit object member functions.
This is mostly a side effect of
treating them as static functions
most of the time, but I wasn't sure
what would be ideal, nor how to change
it. I decided that the difference
was likely mainly due to virtual
functions (if you know more about
this, please be sure to correct me),
and as virtual is not
(currently) allowed, I just left it
as it is.
In short, I agree, and furthermore
I think the syntax should be allowed
with virtual just so style can
be maintained. That would have greater
implications than what you mentioned
though and would need some extra
hacks to make work, instead of just
allowing what should -just- work.

Thanks again for the input, I will
get on it asap. Unfortunately that
will be in a while, but I am
determined to get this feature into
GCC14 so one way or another I will
make it happen.

-Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* [PATCH v2 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-02  8:43   ` waffl3x
@ 2023-09-11 13:49     ` waffl3x
  2023-09-19 20:14       ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-09-11 13:49 UTC (permalink / raw)
  To: waffl3x; +Cc: Jakub Jelinek, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 248 bytes --]

Bootstrapped and tested on x86_64-linux with no regressions.

Hopefully I fixed all the issues. I also took the opportunity to remove the
small mistake present in v1, so that is no longer a concern.

Thanks again for all the patience.
  -Alex

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-c-Initial-support-for-C-23-P0847R7-Deducing-This-PR1.patch --]
[-- Type: text/x-patch; name=0001-c-Initial-support-for-C-23-P0847R7-Deducing-This-PR1.patch, Size: 16890 bytes --]

From 0db52146880faf20e7a7b786dad47c686a5f26d6 Mon Sep 17 00:00:00 2001
From: Waffl3x <waffl3x@protonmail.com>
Date: Mon, 11 Sep 2023 04:21:10 -0600
Subject: [PATCH] c++: Initial support for C++23 P0847R7 (Deducing This)
 [PR102609]

This patch implements initial support for P0847R7, without additions to
diagnostics.  Almost everything should work correctly, barring a few
limitations which are listed below.  I attempted to minimize changes to the
existing code, treating explicit object member functions as static functions,
while flagging them to give them extra powers seemed to be the best way of
achieving this.  For this patch, the flag is only utilized in call.cc for
resolving overloads and making the actual function call.  To achieve this,
conversion of a FUNC_TYPE to a METHOD_TYPE is suppressed when the first
parameter of the FUNC_TYPE is an explicit object parameter, this appears to be
sufficient.  I opted to create a new variable to achieve this instead of
using "staticp" to avoid any possible confusion.

As for the previously mentioned limitations, lambdas do not work correctly yet,
but I suspect that a few tweaks are all it will take to have them fully
functional.  User defined conversion functions are not called when an explicit
object member function with an explicit object parameter of an unrelated type
is called.  The following case does not behave correctly because of this.

struct S {
  operator size_t() {
    return 42;
  }
  size_t f(this size_t n) {
    return n;
  }
};

int main()
{
  S s{};
  size_t a = s.f();
}

Currently, it appears that the object argument is simply reinterpreted as
a size_t instead of properly calling the user defined conversion function.
The validity of such a conversion is still considered however, if there is no
way to convert S to a size_t an appropriate compile error will be emitted.
I have an idea of what changes need to be made to fix this, but I did not
persue this for the initial implementation patch.
This bug can be observed in the explicit-object-param4.C test case, while
explicit-object-param3.C demonstrates the non functioning lambdas.

	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_base::xobj_flag): New data member.
	(DECL_FUNC_XOBJ_FLAG): Define.
	(DECL_PARM_XOBJ_FLAG): Define.
	(DECL_IS_XOBJ_MEMBER_FUNC): Define.
	(enum cp_decl_spec): Add ds_this.
	* decl.cc (grokfndecl): Set xobj_flag if first param is an xobj param.
	(grokdeclarator): For xobj member functions, Don't change type to
	METHOD_TYPE, leave it as FUNC_TYPE. For PARM decl_context, set
	decl_flag_3 if param is an xobj param.
	* parser.cc (cp_parser_decl_specifier_seq): Handle this specifier.
	(set_and_check_decl_spec_loc): Add "this".

gcc/ChangeLog:

	PR c++/102609
	Initial support for C++23 P0847R7 - Deducing this.
	* tree-core.h (struct tree_decl_common): Comment use of decl_flag_3.

gcc/testsuite/ChangeLog:

	PR c++/102609
	Initial support for C++23 P0847R7 - Deducing this.
	* g++.dg/cpp23/explicit-object-param1.C: New test.
	* g++.dg/cpp23/explicit-object-param2.C: New test.
	* g++.dg/cpp23/explicit-object-param3.C: New test.
	* g++.dg/cpp23/explicit-object-param4.C: New test.

Signed-off-by: Waffl3x <waffl3x@protonmail.com>
---
 gcc/cp/call.cc                                |   6 +-
 gcc/cp/cp-tree.h                              |  21 +++-
 gcc/cp/decl.cc                                |  24 ++++
 gcc/cp/parser.cc                              |  15 ++-
 .../g++.dg/cpp23/explicit-object-param1.C     | 114 ++++++++++++++++++
 .../g++.dg/cpp23/explicit-object-param2.C     |  28 +++++
 .../g++.dg/cpp23/explicit-object-param3.C     |  15 +++
 .../g++.dg/cpp23/explicit-object-param4.C     |  33 +++++
 gcc/tree-core.h                               |   3 +-
 9 files changed, 254 insertions(+), 5 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param4.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 399345307ea..37690fdae25 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -6547,7 +6547,8 @@ 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))
+      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+	  || DECL_IS_XOBJ_MEMBER_FUNC (fn))
 	{
 	  /* Figure out where the object arg comes from.  If this
 	     function is a non-static member and we didn't get an
@@ -9969,7 +9970,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_IS_XOBJ_MEMBER_FUNC (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 3ca011c61c8..522ac2d067e 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2878,7 +2878,9 @@ struct GTY(()) lang_decl_base {
   /* VAR_DECL or FUNCTION_DECL has keyed decls.     */
   unsigned module_keyed_decls_p : 1;
 
-  /* 12 spare bits.  */
+  /* FUNCTION_DECL explicit object member function flag.  */
+  unsigned xobj_flag : 1;
+  /* 11 spare bits.  */
 };
 
 /* True for DECL codes which have template info and access.  */
@@ -3086,6 +3088,22 @@ struct GTY(()) lang_decl {
 
 #endif /* ENABLE_TREE_CHECKING */
 
+/* These need to moved to somewhere appropriate.  */
+
+/* The flag is a member of base, but the value is meaningless for other
+   decl types so checking is still justified I imagine.  */
+#define DECL_FUNC_XOBJ_FLAG(NODE) \
+  (LANG_DECL_FN_CHECK (NODE)->min.base.xobj_flag)
+/* Not a lang_decl field, but still specific to c++.  */
+#define DECL_PARM_XOBJ_FLAG(NODE) \
+  (PARM_DECL_CHECK (NODE)->decl_common.decl_flag_3)
+/* First checks if the node has a meaningful value for xobj_flag,
+   evaluates false if not, otherwise evaluates to the value of the flag.  */
+#define DECL_IS_XOBJ_MEMBER_FUNC(NODE) 			\
+  (TREE_CODE (STRIP_TEMPLATE (NODE)) == FUNCTION_DECL	\
+   && DECL_LANG_SPECIFIC (STRIP_TEMPLATE (NODE))	\
+   && DECL_LANG_SPECIFIC (STRIP_TEMPLATE (NODE))->u.base.xobj_flag == 1)
+
 /* For a FUNCTION_DECL or a VAR_DECL, the language linkage for the
    declaration.  Some entities (like a member function in a local
    class, or a local variable) do not have linkage at all, and this
@@ -6275,6 +6293,7 @@ enum cp_decl_spec {
   ds_complex,
   ds_constinit,
   ds_consteval,
+  ds_this, /* inserting here to match decl_spec_names in parser.cc.  */
   ds_thread,
   ds_type_spec,
   ds_redefined_builtin_type_spec,
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 255c4026bdb..d0026059f0f 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -10345,6 +10345,8 @@ grokfndecl (tree ctype,
   type = build_cp_fntype_variant (type, rqual, raises, late_return_type_p);
 
   decl = build_lang_decl_loc (location, FUNCTION_DECL, declarator, type);
+  /* All error checking has been done by now, just copy the flag over.  */
+  DECL_FUNC_XOBJ_FLAG (decl) = parms ? DECL_PARM_XOBJ_FLAG (parms) : false;
 
   /* Set the constraints on the declaration. */
   if (flag_concepts)
@@ -12997,6 +12999,19 @@ grokdeclarator (const cp_declarator *declarator,
   if (attrlist)
     diagnose_misapplied_contracts (*attrlist);
 
+  /* Only used for skipping over build_memfn_type, grokfndecl handles
+     copying the flag to the correct field for a func_decl.
+     There must be a better way to do this, but it isn't obvious how.  */
+  bool is_xobj_member_function = false;
+  auto get_xobj_parm = [](tree parm_list)
+    {
+      if (!parm_list)
+	return NULL_TREE;
+      tree first_parm = TREE_VALUE (parm_list);
+      if (first_parm == void_type_node)
+	return NULL_TREE;
+      return DECL_PARM_XOBJ_FLAG (first_parm) == 1 ? first_parm : NULL_TREE;
+    };
   /* Determine the type of the entity declared by recurring on the
      declarator.  */
   for (; declarator; declarator = declarator->declarator)
@@ -13092,6 +13107,9 @@ grokdeclarator (const cp_declarator *declarator,
 
 	case cdk_function:
 	  {
+	    tree xobj_parm
+	      = get_xobj_parm (declarator->u.function.parameters);
+	    is_xobj_member_function = xobj_parm;
 	    tree arg_types;
 	    int funcdecl_p;
 
@@ -14176,6 +14194,8 @@ grokdeclarator (const cp_declarator *declarator,
     }
 
   if (ctype && TREE_CODE (type) == FUNCTION_TYPE && staticp < 2
+      /* bypass conversion to METHOD_TYPE if an xobj parm is present */
+      && !is_xobj_member_function
       && !(unqualified_id
 	   && identifier_p (unqualified_id)
 	   && IDENTIFIER_NEWDEL_OP_P (unqualified_id)))
@@ -14194,6 +14214,10 @@ grokdeclarator (const cp_declarator *declarator,
       {
 	decl = cp_build_parm_decl (NULL_TREE, unqualified_id, type);
 	DECL_ARRAY_PARAMETER_P (decl) = array_parameter_p;
+	/* Set the xobj flag for this parm, unfortunately
+	   I don't think there is a better way to do this.  */
+	DECL_PARM_XOBJ_FLAG (decl)
+	  = decl_spec_seq_has_spec_p (declspecs, ds_this);
 
 	bad_specifiers (decl, BSP_PARM, virtualp,
 			memfn_quals != TYPE_UNQUALIFIED,
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index ed0675c9599..354297bfff8 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -16049,6 +16049,18 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
 	    decl_specs->locations[ds_attribute] = token->location;
 	  continue;
 	}
+      /* Special case for xobj parm, doesn't really belong up here
+	 (it applies to parm decls and those are mostly handled below
+	 the following specifiers) but I intend to refactor this function
+	 so I'm not worrying about it too much.
+	 The error diagnostics might be better elsewhere though.  */
+      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
@@ -33809,7 +33821,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/testsuite/g++.dg/cpp23/explicit-object-param1.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param1.C
new file mode 100644
index 00000000000..134182c7741
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param1.C
@@ -0,0 +1,114 @@
+// 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-object-param2.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param2.C
new file mode 100644
index 00000000000..a3164c2537c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param2.C
@@ -0,0 +1,28 @@
+// 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-object-param3.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param3.C
new file mode 100644
index 00000000000..fa92e4cd440
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param3.C
@@ -0,0 +1,15 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// recursive lambdas
+
+// { dg-excess-errors "deducing this with lambdas not implemented yet" { xfail *-*-* } }
+
+int main()
+{
+  auto cl0 = [](this auto&& self, int n){ return n ? self(n - 1) : 42 };
+  auto cl1 = [](this auto self, int n){ return n ? self(n - 1) : 42};
+  int a = cl0(5);
+  int b = cl1(5);
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-object-param4.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param4.C
new file mode 100644
index 00000000000..746f6d99b94
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param4.C
@@ -0,0 +1,33 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// test implicit conversion of the object argument
+// to the explicit object parameter
+
+// we compare &s to ret because early on, the
+// object parameter would not convert, it would just get
+// reinterpreted as the type of the explicit object param
+
+// { 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__;
+
+struct S {
+    operator uintptr_t() const {
+	return 42;
+    }
+    uintptr_t f(this uintptr_t n) {
+        return n;
+    }
+};
+
+int main() 
+{
+  S s{};
+  uintptr_t ret = s.f();
+  if (ret == reinterpret_cast<uintptr_t>(&s))
+    __builtin_abort ();
+  if (ret != 42)
+    __builtin_abort ();
+}
+
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 91551fde900..336064fb270 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -1808,7 +1808,8 @@ struct GTY(()) tree_decl_common {
      DECL_HAS_VALUE_EXPR_P.  */
   unsigned decl_flag_2 : 1;
   /* In FIELD_DECL, this is DECL_PADDING_P.
-     In VAR_DECL, this is DECL_MERGEABLE.  */
+     In VAR_DECL, this is DECL_MERGEABLE.
+     In PARM_DECL, this is DECL_XOBJ_PARM.  */
   unsigned decl_flag_3 : 1;
   /* Logically, these two would go in a theoretical base shared by var and
      parm decl. */
-- 
2.42.0


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-11 13:49     ` [PATCH v2 " waffl3x
@ 2023-09-19 20:14       ` Jason Merrill
  2023-09-20  0:30         ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-09-19 20:14 UTC (permalink / raw)
  To: waffl3x; +Cc: Jakub Jelinek, gcc-patches

On 9/11/23 09:49, waffl3x via Gcc-patches wrote:
> Bootstrapped and tested on x86_64-linux with no regressions.
> 
> Hopefully I fixed all the issues. I also took the opportunity to remove the
> small mistake present in v1, so that is no longer a concern.
> 
> Thanks again for all the patience.
>    -Alex

Thank you, this is great!

One legal hurdle to start with: our DCO policy 
(https://gcc.gnu.org/dco.html) requires real names in the sign-off, not 
pseudonyms.  If you would prefer to contribute under this pseudonym, I 
encourage you to file a copyright assignment with the FSF, who are set 
up to handle that.

> +/* These need to moved to somewhere appropriate.  */

This isn't a bad spot for these macros, but you could also move them 
down lower, maybe near DECL_THIS_STATIC and DECL_ARRAY_PARAMETER_P for 
some thematic connection.

> +/* The flag is a member of base, but the value is meaningless for other
> +   decl types so checking is still justified I imagine.  */

Absolutely, we often reuse bits for other purposes if they're disjoint 
from the use they were added for.

> +/* Not a lang_decl field, but still specific to c++.  */
> +#define DECL_PARM_XOBJ_FLAG(NODE) \
> +  (PARM_DECL_CHECK (NODE)->decl_common.decl_flag_3)

Better to use a DECL_LANG_FLAG than claim one of the 
language-independent flags for C++.

There's a list at the top of cp-tree.h of the uses of *_LANG_FLAG_* on 
various kinds of tree node.  DECL_LANG_FLAG_4 seems free on PARM_DECL.

> +  /* Only used for skipping over build_memfn_type, grokfndecl handles
> +     copying the flag to the correct field for a func_decl.
> +     There must be a better way to do this, but it isn't obvious how.  */
> +  bool is_xobj_member_function = false;
> +  auto get_xobj_parm = [](tree parm_list)

I guess you could add a flag to the declarator, but this is fine too. 
Though I'd move this lambda down into the cdk_function case or out to a 
separate function.

>  	case cdk_function:
>  	  {
> +	    tree xobj_parm
> +	      = get_xobj_parm (declarator->u.function.parameters);
> +	    is_xobj_member_function = xobj_parm;

I'd also move these down a few lines after the setting of 'raises'.

> +	/* Set the xobj flag for this parm, unfortunately
> +	   I don't think there is a better way to do this.  */
> +	DECL_PARM_XOBJ_FLAG (decl)
> +	  = decl_spec_seq_has_spec_p (declspecs, ds_this);

This seems like a fine way to handle this.

> +      /* Special case for xobj parm, doesn't really belong up here
> +	 (it applies to parm decls and those are mostly handled below
> +	 the following specifiers) but I intend to refactor this function
> +	 so I'm not worrying about it too much.
> +	 The error diagnostics might be better elsewhere though.  */

This seems like a reasonable place for it since 'this' is supposed to 
precede the decl-specifiers, and since we are parsing initial attributes 
here rather than in the caller.  You will want to give an error if 
found_decl_spec is set.  And elsewhere complain about 'this' on 
parameters after the first (in cp_parser_parameter_declaration_list?), 
or in a non-member/lambda (in grokdeclarator?).

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-08-31  8:33 ` Jakub Jelinek
  2023-09-02  8:43   ` waffl3x
@ 2023-09-19 20:24   ` Jason Merrill
  1 sibling, 0 replies; 100+ messages in thread
From: Jason Merrill @ 2023-09-19 20:24 UTC (permalink / raw)
  To: Jakub Jelinek, waffl3x; +Cc: gcc-patches

On 8/31/23 04:33, Jakub Jelinek wrote:
> On Thu, Aug 31, 2023 at 06:02:36AM +0000, waffl3x via Gcc-patches wrote:
> 
>> +++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param-valid2.C
>> @@ -0,0 +1,24 @@
>> +// P0847R7
>> +// { dg-do run { target c++23 } }
> 
> This raises an important question whether we as an extension
> should support deducing this even in older standards or not.
> I admit I haven't studied the paper enough to figure that out.
> The syntax is certainly something that wasn't valid in older standards,
> so from that POV it could be accepted say with pedwarn with
> OPT_Wc__23_extensions if cxx_dialect < cxx23.  But perhaps some
> of the rules in the paper change something unconditionally even when
> the new syntax doesn't appear.
> And, if it is accepted in older standards, the question is if it
> shouldn't be banned say from C++98.

I don't think there's any obstacle to allowing it as an extension in 
older standards (with a pedwarn, of course).

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* [PATCH v2 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-19 20:14       ` Jason Merrill
@ 2023-09-20  0:30         ` waffl3x
  2023-09-20 21:19           ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-09-20  0:30 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Jakub Jelinek, gcc-patches

> Thank you, this is great!

Thanks!

> One legal hurdle to start with: our DCO policy
> (https://gcc.gnu.org/dco.html) requires real names in the sign-off, not
> pseudonyms. If you would prefer to contribute under this pseudonym, I
> encourage you to file a copyright assignment with the FSF, who are set
> up to handle that.

I will get on that right away.

> > +/* These need to moved to somewhere appropriate. */
> 
> 
> This isn't a bad spot for these macros, but you could also move them
> down lower, maybe near DECL_THIS_STATIC and DECL_ARRAY_PARAMETER_P for
> some thematic connection.

Sounds good, I will move them down.

> > +/* The flag is a member of base, but the value is meaningless for other
> > + decl types so checking is still justified I imagine. */
> 
> 
> Absolutely, we often reuse bits for other purposes if they're disjoint
> from the use they were added for.

Would it be more appropriate to give it a general name in base instead
then? If so, I can also change that.

> > +/* Not a lang_decl field, but still specific to c++. */
> > +#define DECL_PARM_XOBJ_FLAG(NODE) \
> > + (PARM_DECL_CHECK (NODE)->decl_common.decl_flag_3)
> 
> 
> Better to use a DECL_LANG_FLAG than claim one of the
> language-independent flags for C++.
> 
> There's a list at the top of cp-tree.h of the uses of LANG_FLAG on
> various kinds of tree node. DECL_LANG_FLAG_4 seems free on PARM_DECL.

Okay, I will switch to that instead, I didn't like using such a general
purpose flag for what is only relevant until the FUNC_DECL is created
and then never again.

If you don't mind answering right now, what are the consequences of
claiming language-independent flags for C++? Or to phrase it
differently, why would this be claiming it for C++? My guess was that
those flags could be used by any front ends and there wouldn't be any
conflicts, as you can't really have crossover between two front ends at
the same time. Or is that the thing, that kind of cross-over is
actually viable and claiming a language independent flag inhibits that
possibility? Like I eluded to, this is kinda off topic from the patch
so feel free to defer the answer to someone else but I just want to
clear up my understanding for the future.

> > + /* Only used for skipping over build_memfn_type, grokfndecl handles
> > + copying the flag to the correct field for a func_decl.
> > + There must be a better way to do this, but it isn't obvious how. */
> > + bool is_xobj_member_function = false;
> > + auto get_xobj_parm = [](tree parm_list)
> 
> 
> I guess you could add a flag to the declarator, but this is fine too.
> Though I'd move this lambda down into the cdk_function case or out to a
> separate function.

Okay, I will move the lambda.

> > case cdk_function:
> > {
> > + tree xobj_parm
> > + = get_xobj_parm (declarator->u.function.parameters);
> > + is_xobj_member_function = xobj_parm;
> 
> 
> I'd also move these down a few lines after the setting of 'raises'.

Will do.
Also, I forgot to mention it anywhere, the diagnostic patch utilizes
xobj_parm which is why it's a separate variable.

> > + /* Set the xobj flag for this parm, unfortunately
> > + I don't think there is a better way to do this. */
> > + DECL_PARM_XOBJ_FLAG (decl)
> > + = decl_spec_seq_has_spec_p (declspecs, ds_this);
> 
> 
> This seems like a fine way to handle this.

Okay good, I had my doubt's there.
> > + /* Special case for xobj parm, doesn't really belong up here
> > + (it applies to parm decls and those are mostly handled below
> > + the following specifiers) but I intend to refactor this function
> > + so I'm not worrying about it too much.
> > + The error diagnostics might be better elsewhere though. */
> 
> 
> This seems like a reasonable place for it since 'this' is supposed to
> precede the decl-specifiers, and since we are parsing initial attributes
> here rather than in the caller. You will want to give an error if
> found_decl_spec is set. And elsewhere complain about 'this' on
> parameters after the first (in cp_parser_parameter_declaration_list?),
> or in a non-member/lambda (in grokdeclarator?).
> 
> Jason

Yeah, I separated all the diagnostics out into the second patch. This
patch was meant to include the bare minimum of what was necessary to
get the feature functional. As for the diagnostics patch, I'm not happy
with how scattered about the code base it is, but you'll be able to
judge for yourself when I resubmit that patch, hopefully later today.
So not to worry, I didn't neglect diagnostics, it's just in a follow
up. The v1 of it was submitted on August 31st if you want to find it,
but I wouldn't recommend it. I misunderstood how some things were to be
formatted so it's probably best you just wait for me to finish a v2 of
it.

One last thing, I assume I should clean up the comments and replace
them with more typical ones right? I'm going to go forward with that
assumption in v3, I just want to mention it in advanced just in case I
have the wrong idea.

I will get started on v3 of this patch and v2 of the diagnostic patch
as soon as I have the ball rolling on legal stuff. I should have it all
finished tonight. Thanks for the detailed response, it cleared up a lot
of my doubts.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-20  0:30         ` waffl3x
@ 2023-09-20 21:19           ` Jason Merrill
  2023-09-21 11:28             ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-09-20 21:19 UTC (permalink / raw)
  To: waffl3x; +Cc: Jakub Jelinek, gcc-patches

On 9/19/23 20:30, waffl3x wrote:
>> Thank you, this is great!
> 
> Thanks!
> 
>> One legal hurdle to start with: our DCO policy
>> (https://gcc.gnu.org/dco.html) requires real names in the sign-off, not
>> pseudonyms. If you would prefer to contribute under this pseudonym, I
>> encourage you to file a copyright assignment with the FSF, who are set
>> up to handle that.
> 
> I will get on that right away.
> 
>>> +/* These need to moved to somewhere appropriate. */
>>
>> This isn't a bad spot for these macros, but you could also move them
>> down lower, maybe near DECL_THIS_STATIC and DECL_ARRAY_PARAMETER_P for
>> some thematic connection.
> 
> Sounds good, I will move them down.
> 
>>> +/* The flag is a member of base, but the value is meaningless for other
>>> + decl types so checking is still justified I imagine. */
>>
>> Absolutely, we often reuse bits for other purposes if they're disjoint
>> from the use they were added for.
> 
> Would it be more appropriate to give it a general name in base instead
> then? If so, I can also change that.

That would make sense.

>>> +/* Not a lang_decl field, but still specific to c++. */
>>> +#define DECL_PARM_XOBJ_FLAG(NODE) \
>>> + (PARM_DECL_CHECK (NODE)->decl_common.decl_flag_3)
>>
>> Better to use a DECL_LANG_FLAG than claim one of the
>> language-independent flags for C++.
>>
>> There's a list at the top of cp-tree.h of the uses of LANG_FLAG on
>> various kinds of tree node. DECL_LANG_FLAG_4 seems free on PARM_DECL.
> 
> Okay, I will switch to that instead, I didn't like using such a general
> purpose flag for what is only relevant until the FUNC_DECL is created
> and then never again.

That's a good point, but the flag you chose seems even more general purpose.

A better option might be, instead of putting this flag on the PARM_DECL, 
to put it on the short-lived TREE_LIST which is only used for 
communication between cp_parser_parameter_declaration_list and 
grokparms, and have grokdeclarator grab it from 
declarator->u.function.parameters?

> If you don't mind answering right now, what are the consequences of
> claiming language-independent flags for C++? Or to phrase it
> differently, why would this be claiming it for C++? My guess was that
> those flags could be used by any front ends and there wouldn't be any
> conflicts, as you can't really have crossover between two front ends at
> the same time. Or is that the thing, that kind of cross-over is
> actually viable and claiming a language independent flag inhibits that
> possibility? Like I eluded to, this is kinda off topic from the patch
> so feel free to defer the answer to someone else but I just want to
> clear up my understanding for the future.

Generally the flags that aren't specifically specified to be 
language-specific are reserved for language-independent uses; even if 
only one front-end actually uses the feature, it should be for 
communication to language-independent code rather than communication 
within the particular front-end.  The patch modified tree-core.h to 
refer to a macro in cp-tree.h.

> Yeah, I separated all the diagnostics out into the second patch. This
> patch was meant to include the bare minimum of what was necessary to
> get the feature functional. As for the diagnostics patch, I'm not happy
> with how scattered about the code base it is, but you'll be able to
> judge for yourself when I resubmit that patch, hopefully later today.
> So not to worry, I didn't neglect diagnostics, it's just in a follow
> up. The v1 of it was submitted on August 31st if you want to find it,
> but I wouldn't recommend it. I misunderstood how some things were to be
> formatted so it's probably best you just wait for me to finish a v2 of
> it.

Ah, oops, I assumed that v2 completely replaced v1.

> One last thing, I assume I should clean up the comments and replace
> them with more typical ones right? I'm going to go forward with that
> assumption in v3, I just want to mention it in advanced just in case I
> have the wrong idea.

Yes, please.

> I will get started on v3 of this patch and v2 of the diagnostic patch
> as soon as I have the ball rolling on legal stuff. I should have it all
> finished tonight. Thanks for the detailed response, it cleared up a lot
> of my doubts.

Sounds good!

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-20 21:19           ` Jason Merrill
@ 2023-09-21 11:28             ` waffl3x
  2023-09-22 11:30               ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-09-21 11:28 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Jakub Jelinek, gcc-patches

> This seems like a reasonable place for it since 'this' is supposed to
> precede the decl-specifiers, and since we are parsing initial attributes
> here rather than in the caller. You will want to give an error if
> found_decl_spec is set. And elsewhere complain about 'this' on
> parameters after the first (in cp_parser_parameter_declaration_list?),
> or in a non-member/lambda (in grokdeclarator?).

Bringing this back up, I recalled another detail regarding this.

I'm pretty sure that found_decl_spec can be false when parsing the
second or latter decl-specifier. I tested it quickly and I believe I am
correct. I raise this as my diagnostics patch introduces another
variable to track whether we are on the first decl-specifier, given the
results of my quick test, I believe that was the correct choice.

This kinda unclear machinery is what makes me really want to refactor
this code, but I've resisted as it would be inappropriate to try to do
so while implementing a feature. Once I am finished implementing
`deducing this` would you be open to me refactoring grokdeclarator and
it's various auxiliary functions?

As for where the complaining happens, I believe I implemented this
particular error in cp_parser_decl_specifier_seq, I don't plan to be
stubborn on any of the diagnostic code though as I'm pretty unhappy
with how it got scattered about. I intend to get more input on that
after I finish v2 of the diagnostic patch though.


> That's a good point, but the flag you chose seems even more general purpose.

Yeah, I had to just settle on it because I was bikeshedding it for a
couple hours despite being very unhappy with it.

> A better option might be, instead of putting this flag on the PARM_DECL,
> to put it on the short-lived TREE_LIST which is only used for
> communication between cp_parser_parameter_declaration_list and
> grokparms, and have grokdeclarator grab it from
> declarator->u.function.parameters?

That does sound ideal! I will look into doing it this way.

> Generally the flags that aren't specifically specified to be
> language-specific are reserved for language-independent uses; even if
> only one front-end actually uses the feature, it should be for
> communication to language-independent code rather than communication
> within the particular front-end.

Ah okay, that makes perfect sense to me, understood.

> The patch modified tree-core.h to
> refer to a macro in cp-tree.h.

Yeah, I wasn't sure about doing that, I will refrain from that in the
future, (along with removing it from v3, but the other change you
suggested should eliminate the referred to macro anyway.)

> > Yeah, I separated all the diagnostics out into the second patch. This
> > patch was meant to include the bare minimum of what was necessary to
> > get the feature functional. As for the diagnostics patch, I'm not happy
> > with how scattered about the code base it is, but you'll be able to
> > judge for yourself when I resubmit that patch, hopefully later today.
> > So not to worry, I didn't neglect diagnostics, it's just in a follow
> > up. The v1 of it was submitted on August 31st if you want to find it,
> > but I wouldn't recommend it. I misunderstood how some things were to be
> > formatted so it's probably best you just wait for me to finish a v2 of
> > it.
> 
> 
> Ah, oops, I assumed that v2 completely replaced v1.

I had intended to complete v2 of it quite some time ago, I've just been
busy. Today as well I got sidetracked with some job hunting, but I plan
on finishing v3 of the initial support patch (the one related to this
thread) tonight at the very least. I can't commit to diagnostics v2
tonight, but if it happens it happens. :)

I might even have to leave out communicating that a PARM_DECL is an
xobj parm cp_parser_parameter_declaration_list if I have too hard a
time figuring out how to work it in, if that is the case then I will
make that change in a v4.

Alex





^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v2 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-21 11:28             ` waffl3x
@ 2023-09-22 11:30               ` Jason Merrill
  2023-09-26  1:56                 ` [PATCH v3 " waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-09-22 11:30 UTC (permalink / raw)
  To: waffl3x; +Cc: Jakub Jelinek, gcc-patches

On 9/21/23 07:28, waffl3x wrote:
>> This seems like a reasonable place for it since 'this' is supposed to
>> precede the decl-specifiers, and since we are parsing initial attributes
>> here rather than in the caller. You will want to give an error if
>> found_decl_spec is set. And elsewhere complain about 'this' on
>> parameters after the first (in cp_parser_parameter_declaration_list?),
>> or in a non-member/lambda (in grokdeclarator?).
> 
> Bringing this back up, I recalled another detail regarding this.
> 
> I'm pretty sure that found_decl_spec can be false when parsing the
> second or latter decl-specifier. I tested it quickly and I believe I am
> correct. I raise this as my diagnostics patch introduces another
> variable to track whether we are on the first decl-specifier, given the
> results of my quick test, I believe that was the correct choice.

Makes sense.

> This kinda unclear machinery is what makes me really want to refactor
> this code, but I've resisted as it would be inappropriate to try to do
> so while implementing a feature. Once I am finished implementing
> `deducing this` would you be open to me refactoring grokdeclarator and
> it's various auxiliary functions?

Yes, but I'll warn you that grokdeclarator has resisted refactoring for 
a long time...

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-22 11:30               ` Jason Merrill
@ 2023-09-26  1:56                 ` waffl3x
  2023-09-27 22:43                   ` Hans-Peter Nilsson
                                     ` (2 more replies)
  0 siblings, 3 replies; 100+ messages in thread
From: waffl3x @ 2023-09-26  1:56 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Jakub Jelinek, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 1624 bytes --]

> Yes, but I'll warn you that grokdeclarator has resisted refactoring for
> a long time...

That will certainly be what I work on after this is squared off then,
I've been up and down grokdeclarator so I'm confident I'll be able to
do it.

As for the patch, I sure took my sweet time with it, but here it is. I
hope to work on the diagnostics patch tomorrow, but as you've probably
figured out it's best not to take my word on timeframes :^).

On the plus side, I took my time to figure out how to best to pass down
information about whether a param is an xobj param. My initial
impression on what you were suggesting was to push another node on the
front of the list, but I stared at it for a few hours and didn't think
it would work out. However, eventually I realized that the purpose
member if free for xobj params as it is illegal for them to have
default arguments. So I ended up passing it over the TREE_LIST after
all, maybe this is what you meant in the first place anyway too.

I am pretty confident that this version is all good, with only a few
possible issues.

An update on my copyright assignment, I sent an e-mail and haven't
gotten a response yet. From what I saw, I am confident that it's my
preferred option going forward though. Hopefully they get back to me
soon.

Also, just a quick update on my copyright assignment, I have sent an
e-mail to the FSF and haven't gotten a response yet. From what I was
reading, I am confident that it's my preferred option going forward
though. Hopefully they get back to me soon.

Bootstrapped and regtested on x86_64-pc-linux-gnu.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-c-Initial-support-for-C-23-P0847R7-Deducing-This-PR1.patch --]
[-- Type: text/x-patch; name=0001-c-Initial-support-for-C-23-P0847R7-Deducing-This-PR1.patch, Size: 17815 bytes --]

From bbfbcc72e8c0868559284352c71731394c98441e Mon Sep 17 00:00:00 2001
From: waffl3x <waffl3x@protonmail.com>
Date: Mon, 25 Sep 2023 16:59:10 -0600
Subject: [PATCH] c++: Initial support for C++23 P0847R7 (Deducing This)
 [PR102609]

This patch implements initial support for P0847R7, without additions to
diagnostics.  Almost everything should work correctly, barring a few
limitations which are listed below.  I attempted to minimize changes to the
existing code, treating explicit object member functions as static functions,
while flagging them to give them extra powers seemed to be the best way of
achieving this.  For this patch, the flag is only utilized in call.cc for
resolving overloads and making the actual function call.

Internally, the difference between a static member function and an implicit
object member function appears to be whether the type node of the decl is a
FUNCTION_TYPE or a METHOD_TYPE.  So to get the desired behavior, it seems to be
sufficient to simply prevent conversion from FUNC_TYPE to METHOD_TYPE in
grokdeclarator when the first parameter is an explicit object parameter.  To
achieve this, explicit object parameters are flagged as such through each the
TREE_LIST's purpose member in declarator->u.function.parameters.  Typically the
purpose member is used for default arguments,  as those are not allowed for
explicit object parameters, we are able to repurpose purpose for our purposes.
The value used as a flag is the "this_identifier" global tree, as it seemed to
be the most fitting of the current global trees.  Even though it is obviously
illegal for any parameter except the first to be an explicit object parameter,
each parameter parsed as an explicit object parameter will be flagged in this
manner.  This will be used for diagnostics in the following patch.  When an
explicit object parameter is encountered in grokdeclarator, the purpose member
is nulled before the list is passed elsewhere to maintain compatibility with
any code that assumes that a non-null purpose member indicates a default
argument.  This patch only checks for and nulls the first parameter however.

As for the previously mentioned limitations, lambdas do not work correctly yet,
but I suspect that a few tweaks are all it will take to have them fully
functional.  User defined conversion functions are not called when an explicit
object member function with an explicit object parameter of an unrelated type
is called.  The following case does not behave correctly because of this.

struct S {
  operator size_t() {
    return 42;
  }
  size_t f(this size_t n) {
    return n;
  }
};

int main()
{
  S s{};
  size_t a = s.f();
}

Currently, it appears that the object argument is simply reinterpreted as
a size_t instead of properly calling the user defined conversion function.
The validity of such a conversion is still considered however, if there is no
way to convert S to a size_t an appropriate compile error will be emitted.
I have an idea of what changes need to be made to fix this, but I did not
persue this for the initial implementation patch.
This bug can be observed in the explicit-object-param4.C test case, while
explicit-object-param3.C demonstrates the non functioning lambdas.

	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_FUNC_XOBJ_FLAG): Define.
	(DECL_IS_XOBJ_MEMBER_FUNC): Define.
	(enum cp_decl_spec): Add ds_this.
	* decl.cc (grokdeclarator): Clear "this_identifier" from declarator's
	first parameter's purpose member.  Set xobj_func flag on xobj member
	function's decl, set it's type node to FUNCTION_TYPE, not METHOD_TYPE.
	* parser.cc (cp_parser_decl_specifier_seq): Handle this specifier.
	(cp_parser_parameter_declaration): Set default argument to
	"this_identifier" for xobj parameters.
	(set_and_check_decl_spec_loc): Add "this".

gcc/testsuite/ChangeLog:
	PR c++/102609
	Initial support for C++23 P0847R7 - Deducing this.
	* g++.dg/cpp23/explicit-object-param1.C: New test.
	* g++.dg/cpp23/explicit-object-param2.C: New test.
	* g++.dg/cpp23/explicit-object-param3.C: New test.
	* g++.dg/cpp23/explicit-object-param4.C: New test.

Signed-off-by: waffl3x <waffl3x@protonmail.com>
---
 gcc/cp/call.cc                                |   6 +-
 gcc/cp/cp-tree.h                              |  19 ++-
 gcc/cp/decl.cc                                |  23 ++++
 gcc/cp/parser.cc                              |  17 ++-
 .../g++.dg/cpp23/explicit-object-param1.C     | 114 ++++++++++++++++++
 .../g++.dg/cpp23/explicit-object-param2.C     |  28 +++++
 .../g++.dg/cpp23/explicit-object-param3.C     |  15 +++
 .../g++.dg/cpp23/explicit-object-param4.C     |  33 +++++
 8 files changed, 249 insertions(+), 6 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-object-param4.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 85274b81d7e..d861f5e7d54 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -6555,7 +6555,8 @@ 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))
+      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+	  || DECL_IS_XOBJ_MEMBER_FUNC (fn))
 	{
 	  /* Figure out where the object arg comes from.  If this
 	     function is a non-static member and we didn't get an
@@ -9975,7 +9976,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_IS_XOBJ_MEMBER_FUNC (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 8fee4754604..2bef5a4909c 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2932,8 +2932,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.  */
 
@@ -4394,6 +4395,15 @@ get_vec_init_expr (tree t)
 #define DECL_DEFAULTED_FN(DECL) \
   (LANG_DECL_FN_CHECK (DECL)->defaulted_p)
 
+/* Simple member access, only valid for FUNCTION_DECL nodes.  */
+#define DECL_FUNC_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 decls.  */
+#define DECL_IS_XOBJ_MEMBER_FUNC(NODE)			\
+  (TREE_CODE (STRIP_TEMPLATE (NODE)) == FUNCTION_DECL	\
+   && DECL_FUNC_XOBJ_FLAG (NODE) == 1)
+
 /* Nonzero if DECL is explicitly defaulted in the class body.  */
 #define DECL_DEFAULTED_IN_CLASS_P(DECL)					\
   (DECL_DEFAULTED_FN (DECL) && DECL_INITIALIZED_IN_CLASS_P (DECL))
@@ -6223,11 +6233,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,
@@ -6244,6 +6256,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 30d89bba9e8..427fd12d3d8 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -12926,6 +12926,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)
@@ -13021,6 +13023,19 @@ grokdeclarator (const cp_declarator *declarator,
 
 	case cdk_function:
 	  {
+	    auto get_xobj_parm = [](tree parm_list)
+	      {
+		if (!parm_list)
+		  return NULL_TREE;
+		if (TREE_VALUE (parm_list) == void_type_node)
+		  return NULL_TREE;
+		if (TREE_PURPOSE (parm_list) != this_identifier)
+		  return NULL_TREE;
+		/* Non-null 'purpose' usually means the param has a default
+		   argument, we don't want to violate this assumption.  */
+		TREE_PURPOSE (parm_list) = NULL_TREE;
+		return TREE_VALUE (parm_list);
+	      };
 	    tree arg_types;
 	    int funcdecl_p;
 
@@ -13041,6 +13056,10 @@ grokdeclarator (const cp_declarator *declarator,
 	    if (raises == error_mark_node)
 	      raises = NULL_TREE;
 
+	    tree xobj_parm
+	      = get_xobj_parm (declarator->u.function.parameters);
+	    is_xobj_member_function = xobj_parm;
+
 	    if (reqs)
 	      error_at (location_of (reqs), "requires-clause on return type");
 	    reqs = declarator->u.function.requires_clause;
@@ -14105,6 +14124,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)))
@@ -14333,6 +14354,7 @@ grokdeclarator (const cp_declarator *declarator,
             decl = set_virt_specifiers (decl, virt_specifiers);
 	    if (decl == NULL_TREE)
 	      return error_mark_node;
+	    DECL_FUNC_XOBJ_FLAG (decl) = is_xobj_member_function;
 #if 0
 	    /* This clobbers the attrs stored in `decl' from `attrlist'.  */
 	    /* The decl and setting of decl_attr is also turned off.  */
@@ -14665,6 +14687,7 @@ grokdeclarator (const cp_declarator *declarator,
 			   id_loc);
 	if (decl == NULL_TREE)
 	  return error_mark_node;
+	DECL_FUNC_XOBJ_FLAG (decl) = is_xobj_member_function;
 
 	if (explicitp == 2)
 	  DECL_NONCONVERTING_P (decl) = 1;
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index b0cd1b463c8..0f3ff5c796d 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -15843,6 +15843,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
@@ -25239,6 +25249,10 @@ cp_parser_parameter_declaration (cp_parser *parser,
 
   if (default_argument)
     STRIP_ANY_LOCATION_WRAPPER (default_argument);
+  /* Xobj parameters can not have default arguments, thus
+     we can reuse the default argument field to flag the param as such.  */
+  if (decl_spec_seq_has_spec_p (&decl_specifiers, ds_this))
+    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
@@ -33587,7 +33601,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/testsuite/g++.dg/cpp23/explicit-object-param1.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param1.C
new file mode 100644
index 00000000000..134182c7741
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param1.C
@@ -0,0 +1,114 @@
+// 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-object-param2.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param2.C
new file mode 100644
index 00000000000..a3164c2537c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param2.C
@@ -0,0 +1,28 @@
+// 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-object-param3.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param3.C
new file mode 100644
index 00000000000..fa92e4cd440
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param3.C
@@ -0,0 +1,15 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// recursive lambdas
+
+// { dg-excess-errors "deducing this with lambdas not implemented yet" { xfail *-*-* } }
+
+int main()
+{
+  auto cl0 = [](this auto&& self, int n){ return n ? self(n - 1) : 42 };
+  auto cl1 = [](this auto self, int n){ return n ? self(n - 1) : 42};
+  int a = cl0(5);
+  int b = cl1(5);
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-object-param4.C b/gcc/testsuite/g++.dg/cpp23/explicit-object-param4.C
new file mode 100644
index 00000000000..746f6d99b94
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-object-param4.C
@@ -0,0 +1,33 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// test implicit conversion of the object argument
+// to the explicit object parameter
+
+// we compare &s to ret because early on, the
+// object parameter would not convert, it would just get
+// reinterpreted as the type of the explicit object param
+
+// { 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__;
+
+struct S {
+    operator uintptr_t() const {
+	return 42;
+    }
+    uintptr_t f(this uintptr_t n) {
+        return n;
+    }
+};
+
+int main() 
+{
+  S s{};
+  uintptr_t ret = s.f();
+  if (ret == reinterpret_cast<uintptr_t>(&s))
+    __builtin_abort ();
+  if (ret != 42)
+    __builtin_abort ();
+}
+
-- 
2.42.0


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-26  1:56                 ` [PATCH v3 " waffl3x
@ 2023-09-27 22:43                   ` Hans-Peter Nilsson
  2023-09-27 23:35                     ` Waffl3x
  2023-10-17 20:53                   ` Jason Merrill
  2023-10-17 21:11                   ` Jason Merrill
  2 siblings, 1 reply; 100+ messages in thread
From: Hans-Peter Nilsson @ 2023-09-27 22:43 UTC (permalink / raw)
  To: waffl3x; +Cc: jason, jakub, gcc-patches

> Date: Tue, 26 Sep 2023 01:56:55 +0000
> From: waffl3x <waffl3x@protonmail.com>

> Signed-off-by: waffl3x <waffl3x@protonmail.com>

I think I've read that you have to put your actual name in
the DCO; using an alias (presumably) as above would be
wrong.

Ah, it's on https://gcc.gnu.org/dco.html - the *second* DCO
link; under "Signed-off-by", on
https://gcc.gnu.org/contribute.html! "sorry, no pseudonyms
or anonymous contributions".

(Also, from Some Source I Don't Remember: using an alias if
you have FSF papers in place is ok; you can use a pseudonym
if FSF can match it to papers on file that have your actual
name or something to that effect.)

brgds, H-P

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-27 22:43                   ` Hans-Peter Nilsson
@ 2023-09-27 23:35                     ` Waffl3x
  0 siblings, 0 replies; 100+ messages in thread
From: Waffl3x @ 2023-09-27 23:35 UTC (permalink / raw)
  To: Hans-Peter Nilsson; +Cc: jason, jakub, gcc-patches

Not to worry, I'm currently going through that process with the FSF, it
was confirmed that a pseudonym should be just fine. I don't know how
long the process takes but my goal is to get this in for GCC14, and
surely this won't take more than a month. One can only hope anyway.

On 2023-09-27 04:43 p.m., Hans-Peter Nilsson wrote:
>> Date: Tue, 26 Sep 2023 01:56:55 +0000
>> From: waffl3x <waffl3x@protonmail.com>
> 
>> Signed-off-by: waffl3x <waffl3x@protonmail.com>
> 
> I think I've read that you have to put your actual name in
> the DCO; using an alias (presumably) as above would be
> wrong.
> 
> Ah, it's on https://gcc.gnu.org/dco.html - the *second* DCO
> link; under "Signed-off-by", on
> https://gcc.gnu.org/contribute.html! "sorry, no pseudonyms
> or anonymous contributions".
> 
> (Also, from Some Source I Don't Remember: using an alias if
> you have FSF papers in place is ok; you can use a pseudonym
> if FSF can match it to papers on file that have your actual
> name or something to that effect.)
> 
> brgds, H-P


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-26  1:56                 ` [PATCH v3 " waffl3x
  2023-09-27 22:43                   ` Hans-Peter Nilsson
@ 2023-10-17 20:53                   ` Jason Merrill
  2023-10-17 21:11                   ` Jason Merrill
  2 siblings, 0 replies; 100+ messages in thread
From: Jason Merrill @ 2023-10-17 20:53 UTC (permalink / raw)
  To: waffl3x; +Cc: Jakub Jelinek, gcc-patches

On 9/25/23 21:56, waffl3x wrote:
> 
> Also, just a quick update on my copyright assignment, I have sent an
> e-mail to the FSF and haven't gotten a response yet. From what I was
> reading, I am confident that it's my preferred option going forward
> though. Hopefully they get back to me soon.

Any progress on this, or do I need to coax the process along?  :)

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-09-26  1:56                 ` [PATCH v3 " waffl3x
  2023-09-27 22:43                   ` Hans-Peter Nilsson
  2023-10-17 20:53                   ` Jason Merrill
@ 2023-10-17 21:11                   ` Jason Merrill
  2023-10-18 11:46                     ` waffl3x
  2 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-10-17 21:11 UTC (permalink / raw)
  To: waffl3x; +Cc: Jakub Jelinek, gcc-patches

On 9/25/23 21:56, waffl3x wrote:
> 
> On the plus side, I took my time to figure out how to best to pass down
> information about whether a param is an xobj param. My initial
> impression on what you were suggesting was to push another node on the
> front of the list, but I stared at it for a few hours and didn't think
> it would work out.

I was thinking to set a TREE_LANG_FLAG on the TREE_LIST node.

> However, eventually I realized that the purpose
> member if free for xobj params as it is illegal for them to have
> default arguments.

Hmm, is it?  I see that clang thinks so, but I don't know where they get 
that idea from.  The grammar certainly allows it:

> attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause

and I don't see anything else that prohibits it.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-17 21:11                   ` Jason Merrill
@ 2023-10-18 11:46                     ` waffl3x
  2023-10-18 14:17                       ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-10-18 11:46 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Jakub Jelinek, gcc-patches

> Any progress on this, or do I need to coax the process along?  :)

Yeah, I've been working on it since the copyright assignment process
has finished, originally I was going to note that on my next update
which I had hoped to finish today or tomorrow. Well, in truth I was
hoping to send one the same day that copyright assignment finished, but
I found a nasty bug so I spent all day adding test cases for all the
relevant overloadable operators. Currently, it crashes when calling a
subscript operator declared with an explicit object parameter in a
dependent context. I haven't looked into the fix yet, but I plan to.

Also, before I forget, what is the process for confirming my copyright
assignment on your end? Do you just need to check in with the FSF to
see if it went through? Let me know if there's anything you need from
me regarding that.

Aside from the bug that's currently present in the first patch, I only
have like 1 or 2 little things I want to change about it. I will make
those few changes to patch 1, finish patch 2 (diagnostics) which will
also include test cases for the new bug I found. After I am done that I
plan on adding the things that are missing, because I suspect that
looking into that will get me close to finding the fix for the crash.

> Hmm, is it? I see that clang thinks so, but I don't know where they get
> that idea from. The grammar certainly allows it:
> 
> > attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
> 
> 
> and I don't see anything else that prohibits it.

You would be right for P0847R7, but CWG DR 2653 changed that. You can
find the updated grammar in dcl.fct section 3 (subsection? I'm not
really sure to be honest.)

I've also included a copy of the grammar here for your convenience.

https://eel.is/c++draft/dcl.fct#nt:parameter-declaration
parameter-declaration:
  attribute-specifier-seqopt thisopt decl-specifier-seq declarator
  attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
  attribute-specifier-seqopt thisopt decl-specifier-seq abstract-declaratoropt
  attribute-specifier-seqopt decl-specifier-seq abstract-declaratoropt = initializer-clause 


> I was thinking to set a TREE_LANG_FLAG on the TREE_LIST node.

I did figure this is originally what you meant, and I can still change
it to go this route since I'm sure it's likely just as good. But I do
recall something I didn't like in the implementation that nudged me
towards using the purpose member instead. Either way, not a big deal. I
think I just liked not having to mess with a linked list as I am not
used to them as a data structure, it might have been that simple. :^)

I will try to get something done today, but I was struggling with
writing some of the tests, there's also a lot more of them now. I also
wrote a bunch of musings in comments that I would like feedback on.

My most concrete question is, how exactly should I be testing a
pedwarn, I want to test that I get the correct warning and error with
the separate flags, do I have to create two separate tests for each one?

I'm just going to include the little wall I wrote in decl.cc regarding
pedwarn, just in case I can't get this done tonight so I can get some
feedback regarding it. On the other hand, it might just not be very
relevant to this patch in particular as I kind of noted, but maybe
there's some way to do what I was musing about that I've overlooked. It
does end up a bit ranty I guess, hopefully that doesn't make it
confusing.

```
/* I believe we should make a switch for this feature specifically,
   I recall seeing discussion regarding enabling newer language
   features when set to older standards. I would advocate for a
   specific flag for each specific feature. Maybe they should all
   be under an override flag? -foverride-dialect=xobj,ifconstexpr (?)
   I dont think it makes sense to give each feature override it's own
   flag. I don't recall what the consensus was around this discussion
   either though.
   For the time being it's controlled by pedantic. I am concerned that
   tying this to pedantic going forward that one might want to enable
   -pedantic-errors while also enabling select features from newer
   dialects. It didn't look like this use case is supported to me.

   I suppose this will require redesign work to support, so for
   the purposes of this patch, emitting a pedwarn seems correct.
   I just don't like that it can't be suppressed on an individual
   basis.  */
if (xobj_parm && cxx_dialect < cxx23)
  pedwarn(DECL_SOURCE_LOCATION (xobj_parm), OPT_Wpedantic, "");
```

That's all for now, I will try, (but I am very much not promising,) to
have an update by the end of today (6-8 hours for me.) If I manage to
get that out, I will (finally) start moving forward on implementing the
missing and broken features, and the aforementioned bug.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-18 11:46                     ` waffl3x
@ 2023-10-18 14:17                       ` Jason Merrill
  2023-10-18 17:28                         ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-10-18 14:17 UTC (permalink / raw)
  To: waffl3x; +Cc: Jakub Jelinek, gcc-patches

On 10/18/23 07:46, waffl3x wrote:
>> Any progress on this, or do I need to coax the process along?  :)
> 
> Yeah, I've been working on it since the copyright assignment process
> has finished, originally I was going to note that on my next update
> which I had hoped to finish today or tomorrow. Well, in truth I was
> hoping to send one the same day that copyright assignment finished, but
> I found a nasty bug so I spent all day adding test cases for all the
> relevant overloadable operators. Currently, it crashes when calling a
> subscript operator declared with an explicit object parameter in a
> dependent context. I haven't looked into the fix yet, but I plan to.
> 
> Also, before I forget, what is the process for confirming my copyright
> assignment on your end? Do you just need to check in with the FSF to
> see if it went through? Let me know if there's anything you need from
> me regarding that.
> 
> Aside from the bug that's currently present in the first patch, I only
> have like 1 or 2 little things I want to change about it. I will make
> those few changes to patch 1, finish patch 2 (diagnostics) which will
> also include test cases for the new bug I found. After I am done that I
> plan on adding the things that are missing, because I suspect that
> looking into that will get me close to finding the fix for the crash.
> 
>> Hmm, is it? I see that clang thinks so, but I don't know where they get
>> that idea from. The grammar certainly allows it:
>>
>>> attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
>>
>> and I don't see anything else that prohibits it.
> 
> You would be right for P0847R7, but CWG DR 2653 changed that. You can
> find the updated grammar in dcl.fct section 3 (subsection? I'm not
> really sure to be honest.)
> 
> I've also included a copy of the grammar here for your convenience.
> 
> https://eel.is/c++draft/dcl.fct#nt:parameter-declaration
> parameter-declaration:
>    attribute-specifier-seqopt thisopt decl-specifier-seq declarator
>    attribute-specifier-seqopt decl-specifier-seq declarator = initializer-clause
>    attribute-specifier-seqopt thisopt decl-specifier-seq abstract-declaratoropt
>    attribute-specifier-seqopt decl-specifier-seq abstract-declaratoropt = initializer-clause

Ah, yes, thanks.

>> I was thinking to set a TREE_LANG_FLAG on the TREE_LIST node.
> 
> I did figure this is originally what you meant, and I can still change
> it to go this route since I'm sure it's likely just as good. But I do
> recall something I didn't like in the implementation that nudged me
> towards using the purpose member instead. Either way, not a big deal. I
> think I just liked not having to mess with a linked list as I am not
> used to them as a data structure, it might have been that simple. :^)

I wouldn't expect to need any actual dealing with the linked list, just 
setting a flag in cp_parameter_declaration_list at the same point as the 
existing PARENTHESIZED_LIST_P flag.

But given CWG2653 as you pointed out, your current approach is fine.

> I will try to get something done today, but I was struggling with
> writing some of the tests, there's also a lot more of them now. I also
> wrote a bunch of musings in comments that I would like feedback on.
> 
> My most concrete question is, how exactly should I be testing a
> pedwarn, I want to test that I get the correct warning and error with
> the separate flags, do I have to create two separate tests for each one?

Yes.  I tend to use letter suffixes for tests that vary only in flags 
(and expected results), e.g. feature1a.C, feature1b.C.

> I'm just going to include the little wall I wrote in decl.cc regarding
> pedwarn, just in case I can't get this done tonight so I can get some
> feedback regarding it. On the other hand, it might just not be very
> relevant to this patch in particular as I kind of noted, but maybe
> there's some way to do what I was musing about that I've overlooked. It
> does end up a bit ranty I guess, hopefully that doesn't make it
> confusing.
> 
> ```
> /* I believe we should make a switch for this feature specifically,
>     I recall seeing discussion regarding enabling newer language
>     features when set to older standards. I would advocate for a
>     specific flag for each specific feature. Maybe they should all
>     be under an override flag? -foverride-dialect=xobj,ifconstexpr (?)
>     I dont think it makes sense to give each feature override it's own
>     flag. I don't recall what the consensus was around this discussion
>     either though.
>
>     For the time being it's controlled by pedantic. I am concerned that
>     tying this to pedantic going forward that one might want to enable
>     -pedantic-errors while also enabling select features from newer
>     dialects. It didn't look like this use case is supported to me.
> 
>     I suppose this will require redesign work to support, so for
>     the purposes of this patch, emitting a pedwarn seems correct.
>     I just don't like that it can't be suppressed on an individual
>     basis.  */
> if (xobj_parm && cxx_dialect < cxx23)
>    pedwarn(DECL_SOURCE_LOCATION (xobj_parm), OPT_Wpedantic, "");

Instead of OPT_Wpedantic, this should be controlled by 
-Wc++23-extensions (OPT_Wc__23_extensions)

If you wanted, you could add a more specific warning option for this 
(e.g. -Wc++23-explicit-this) which is also affected by 
-Wc++23-extensions, but I would lean toward just using the existing 
flag.  Up to you.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-18 14:17                       ` Jason Merrill
@ 2023-10-18 17:28                         ` waffl3x
  2023-10-18 17:45                           ` Jakub Jelinek
  2023-10-18 18:12                           ` Jason Merrill
  0 siblings, 2 replies; 100+ messages in thread
From: waffl3x @ 2023-10-18 17:28 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Jakub Jelinek, gcc-patches

> > I will try to get something done today, but I was struggling with
> > writing some of the tests, there's also a lot more of them now. I also
> > wrote a bunch of musings in comments that I would like feedback on.
> > 
> > My most concrete question is, how exactly should I be testing a
> > pedwarn, I want to test that I get the correct warning and error with
> > the separate flags, do I have to create two separate tests for each one?
> 
> 
> Yes. I tend to use letter suffixes for tests that vary only in flags
> (and expected results), e.g. feature1a.C, feature1b.C.

Will do.

> Instead of OPT_Wpedantic, this should be controlled by
> -Wc++23-extensions (OPT_Wc__23_extensions)

Yeah, I'll do this.

> If you wanted, you could add a more specific warning option for this
> (e.g. -Wc++23-explicit-this) which is also affected by
> -Wc++23-extensions, but I would lean toward just using the existing
> flag. Up to you.

I brought it up in irc and there was some pushback to my point of view
on it, so I'll just stick with OPT_Wc__23_extensions for now. I do
think a more sophisticated interface would be beneficial but I will
bring discussion around that up again in the future.

I've seen plenty of these G_ or _ macros on strings around like in
grokfndecl for these errors.

G_("static member function %qD cannot have cv-qualifier")
G_("non-member function %qD cannot have cv-qualifier")

G_("static member function %qD cannot have ref-qualifier")
G_("non-member function %qD cannot have ref-qualifier")

I have been able to figure out it relates to translation, but not
exactly what the protocol around them is. I think in my original patch
I had refactored this code a bunch, I figured adding a 3rd case to it
justifies a refactor. I think I forgot to add those changes to the
original patch, either that or I undid it or moved it somewhere else.
Anyway, the point is, coming back to it now to re-add those diagnostics
I realized I probably shouldn't have changed those strings.

I also have been wondering whether I should be putting macros on any
strings I add, it seemed like there might have been a macro for text
that needs translation. Is this something I should be doing?

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-18 17:28                         ` waffl3x
@ 2023-10-18 17:45                           ` Jakub Jelinek
  2023-10-18 18:12                           ` Jason Merrill
  1 sibling, 0 replies; 100+ messages in thread
From: Jakub Jelinek @ 2023-10-18 17:45 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

On Wed, Oct 18, 2023 at 05:28:10PM +0000, waffl3x wrote:
> I've seen plenty of these G_ or _ macros on strings around like in
> grokfndecl for these errors.
> 
> G_("static member function %qD cannot have cv-qualifier")
> G_("non-member function %qD cannot have cv-qualifier")
> 
> G_("static member function %qD cannot have ref-qualifier")
> G_("non-member function %qD cannot have ref-qualifier")
> 
> I have been able to figure out it relates to translation, but not
> exactly what the protocol around them is. I think in my original patch
> I had refactored this code a bunch, I figured adding a 3rd case to it
> justifies a refactor. I think I forgot to add those changes to the
> original patch, either that or I undid it or moved it somewhere else.
> Anyway, the point is, coming back to it now to re-add those diagnostics
> I realized I probably shouldn't have changed those strings.
> 
> I also have been wondering whether I should be putting macros on any
> strings I add, it seemed like there might have been a macro for text
> that needs translation. Is this something I should be doing?

There are different kinds of format strings in GCC, the most common
are the gcc-internal-format strings.  If you call a function which
is expected to take such translatable format string (in particular
a function which takes a gmsgid named argument like error, error_at,
pedwarn, warning_at, ...) and pass a string literal to that function,
nothing needs to be marked in a special way, both gcc/po/exgettext
is able to collect such literals into gcc/po/gcc.pot for translations
and the function is supposed to use gettext etc. to translate it
- e.g. see diagnostic_set_info using _(gmsgid) for that.
But, if there is e.g. a temporary pointer var which points to format
strings and only that is eventually passed to the diagnostic functions,
gcc/po/exgettext won't be able to collect such literals, which is where
the G_() macro comes into play and one marks the string as
gcc-internal-format with it; the translation is still handled by the
diagnostic function at runtime.  The N_() macro is similar but for c-format
strings instead.  The _() macro both collects for translations if it is
used with string literal, and expands to gettext call to translate it at
runtime, which is something that should be avoided if something translates
it again.

And another i18n rule is that one shouldn't try to construct diagnostic
messages from parts of english sentences, it is fine to fill in with %s/%qs
etc. language keywords etc. but otherwise the format string should contain
the whole diagnostic line, so that translators can reorder the words etc.

	Jakub


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-18 17:28                         ` waffl3x
  2023-10-18 17:45                           ` Jakub Jelinek
@ 2023-10-18 18:12                           ` Jason Merrill
  2023-10-19 21:05                             ` waffl3x
  1 sibling, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-10-18 18:12 UTC (permalink / raw)
  To: waffl3x; +Cc: Jakub Jelinek, gcc-patches

On 10/18/23 13:28, waffl3x wrote:
>>> I will try to get something done today, but I was struggling with
>>> writing some of the tests, there's also a lot more of them now. I also
>>> wrote a bunch of musings in comments that I would like feedback on.
>>>
>>> My most concrete question is, how exactly should I be testing a
>>> pedwarn, I want to test that I get the correct warning and error with
>>> the separate flags, do I have to create two separate tests for each one?
>>
>>
>> Yes. I tend to use letter suffixes for tests that vary only in flags
>> (and expected results), e.g. feature1a.C, feature1b.C.
> 
> Will do.
> 
>> Instead of OPT_Wpedantic, this should be controlled by
>> -Wc++23-extensions (OPT_Wc__23_extensions)
> 
> Yeah, I'll do this.
> 
>> If you wanted, you could add a more specific warning option for this
>> (e.g. -Wc++23-explicit-this) which is also affected by
>> -Wc++23-extensions, but I would lean toward just using the existing
>> flag. Up to you.
> 
> I brought it up in irc and there was some pushback to my point of view
> on it, so I'll just stick with OPT_Wc__23_extensions for now. I do
> think a more sophisticated interface would be beneficial but I will
> bring discussion around that up again in the future.
> 
> I've seen plenty of these G_ or _ macros on strings around like in
> grokfndecl for these errors.
> 
> G_("static member function %qD cannot have cv-qualifier")
> G_("non-member function %qD cannot have cv-qualifier")
> 
> G_("static member function %qD cannot have ref-qualifier")
> G_("non-member function %qD cannot have ref-qualifier")
> 
> I have been able to figure out it relates to translation, but not
> exactly what the protocol around them is.

The protocol is described in gcc/ABOUT-GCC-NLS.  In general, "strings" 
passed directly to a diagnostic function don't need any decoration, but 
if they're assigned to a variable first, they need G_() so they're 
recognized as diagnostic strings to be added to the translation table.

The _() macro is used for strings that are going to be passed to a %s, 
but better to avoid doing that for strings that need translation.  N_() 
is (rarely) used for strings that aren't diagnostic format strings, but 
get passed to another function that passes them to _().

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-18 18:12                           ` Jason Merrill
@ 2023-10-19 21:05                             ` waffl3x
  2023-10-19 21:11                               ` Jakub Jelinek
  2023-10-19 22:18                               ` Jason Merrill
  0 siblings, 2 replies; 100+ messages in thread
From: waffl3x @ 2023-10-19 21:05 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Jakub Jelinek, gcc-patches

> (Jakub)
> There are different kinds of format strings in GCC, the most common
> are the gcc-internal-format strings. If you call a function which
> is expected to take such translatable format string (in particular
> a function which takes a gmsgid named argument like error, error_at,
> pedwarn, warning_at, ...) and pass a string literal to that function,
> nothing needs to be marked in a special way, both gcc/po/exgettext
> is able to collect such literals into gcc/po/gcc.pot for translations
> and the function is supposed to use gettext etc. to translate it
> - e.g. see diagnostic_set_info using (gmsgid) for that.
> But, if there is e.g. a temporary pointer var which points to format
> strings and only that is eventually passed to the diagnostic functions,
> gcc/po/exgettext won't be able to collect such literals, which is where
> the G() macro comes into play and one marks the string as
> gcc-internal-format with it; the translation is still handled by the
> diagnostic function at runtime. The N_() macro is similar but for c-format
> strings instead. The _() macro both collects for translations if it is
> used with string literal, and expands to gettext call to translate it at
> runtime, which is something that should be avoided if something translates
> it again.

> (Jason)
> The protocol is described in gcc/ABOUT-GCC-NLS. In general, "strings"
> passed directly to a diagnostic function don't need any decoration, but
> if they're assigned to a variable first, they need G_() so they're
> recognized as diagnostic strings to be added to the translation table.

I read this last night and decided to sleep on it hoping it would make
more sense now. I was going to write about how I'm still confused but I
think it just clicked for me. I somehow didn't make the connection that
any kind of of expression (like a conditional) is going to require it's
use. I guess I kept thinking "but they aren't being assigned to a
variable" but I get it now, it's when the strings aren't passed
DIRECTLY into the diagnostic function.

> (Jakub)
> And another i18n rule is that one shouldn't try to construct diagnostic
> messages from parts of english sentences, it is fine to fill in with %s/%qs
> etc. language keywords etc. but otherwise the format string should contain
> the whole diagnostic line, so that translators can reorder the words etc.

> (Jason)
> The () macro is used for strings that are going to be passed to a %s,
> but better to avoid doing that for strings that need translation. N()
> is (rarely) used for strings that aren't diagnostic format strings, but
> get passed to another function that passes them to _().

Okay so taking what you guys are saying here it sounds like it would be
appropriate to refactor the code I was reluctant to refactor. The code
(in grokfndecl) conditionally selects one of the two (now three with my
changes) following strings despite them being mostly identical.

"non-member function %qD cannot have cv-qualifier"
"static member function %qD cannot have cv-qualifier"
"explicit object member function %qD cannot have cv-qualifier"

From what I'm getting from what you two have said is that it would be
reasonable to instead construct a string from it's two parts, just
selecting the differing part dynamically.

"%s function %qD cannot have cv-qualifier"

Just to clarify, should I be marking the "non-member", "static member",
"explicit object member" strings with the _ macro then? I'm just not
sure since it seems like Jason is saying they shouldn't be since the
string they are being formatted into is being translated afterwards.

Also, I'm not sure what %qs is, should I be using that instead of %s
for strings?

On another topic, I have been trying to fix taking pointers to explicit
object member functions today, as I ended up breaking that when I
started setting static_function to false for them. Originally it just
worked so I haven't touched any code related to this, but now that they
aren't being treating like static member functions as much so a few
things just broke. What I'm asking myself now is whether it would be
appropriate to just opt-in to static member function behavior for this
one, and I'm not sure that would be correct.

So I started by checking what it did before I turned off the
static_function flag. It's was being passed into cp_build_addr_expr_1
as a baselink node, while regular member functions are passed in as an
offset_ref node. I then checked what the case is for static member
function, and unsurprisingly those are also handled wrapped in a
baselink node, but this raised some questions for me.

I am now trying to figure out what exactly a baselink is, and why it's
used for static member functions. My current best guess is that
method_type nodes already hold the information that a baselink does,
and that information is needed in general. If that is the case, it
might just be correct to just do the same thing for explicit object
member functions, but I wonder if there is more to it, but maybe there
isn't. It worked just fine before when the static_function was still
being set after all.

Any insight on this is appreciated.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-19 21:05                             ` waffl3x
@ 2023-10-19 21:11                               ` Jakub Jelinek
  2023-10-19 21:31                                 ` waffl3x
  2023-10-19 22:18                               ` Jason Merrill
  1 sibling, 1 reply; 100+ messages in thread
From: Jakub Jelinek @ 2023-10-19 21:11 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

On Thu, Oct 19, 2023 at 09:05:38PM +0000, waffl3x wrote:
> Okay so taking what you guys are saying here it sounds like it would be
> appropriate to refactor the code I was reluctant to refactor. The code
> (in grokfndecl) conditionally selects one of the two (now three with my
> changes) following strings despite them being mostly identical.
> 
> "non-member function %qD cannot have cv-qualifier"
> "static member function %qD cannot have cv-qualifier"
> "explicit object member function %qD cannot have cv-qualifier"
> 
> >From what I'm getting from what you two have said is that it would be
> reasonable to instead construct a string from it's two parts, just
> selecting the differing part dynamically.
> 
> "%s function %qD cannot have cv-qualifier"

No, that wouldn't be appropriate for translation.
None of non-member, static member and explicit object member are
something that should be printed verbatim untranslated.
"%s function %qD cannot have cv-qualifier", _("non-member")
etc. is still inappropriate, some language might need to reorder
the words in one case and not another one etc.

What is ok if that %s (but in that case better %qs) is say some
language keyword which shouldn't be translated.

"%qs keyword not expected"
with
the arg being "inline", "constexpr", "consteval" for example would
be fine.

	Jakub


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-19 21:11                               ` Jakub Jelinek
@ 2023-10-19 21:31                                 ` waffl3x
  2023-10-19 21:53                                   ` Jakub Jelinek
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-10-19 21:31 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: Jason Merrill, gcc-patches

> No, that wouldn't be appropriate for translation.
> None of non-member, static member and explicit object member are
> something that should be printed verbatim untranslated.
> "%s function %qD cannot have cv-qualifier", _("non-member")
> etc. is still inappropriate, some language might need to reorder
> the words in one case and not another one etc.
>
> What is ok if that %s (but in that case better %qs) is say some
> language keyword which shouldn't be translated.
>
> "%qs keyword not expected"
> with
> the arg being "inline", "constexpr", "consteval" for example would
> be fine.
>
> Jakub

Ah alright, I see what you're saying, I see what the difference is now.
It's a shame we can't have the translated string insert a %s and format
into that :^). Ah well, I guess this code is just doomed to look poor
then, what can you do.

This is pretty much why I've been afraid to touch anything with these
macros, if I don't understand exactly how it works anything slightly
weird solution I use might just not work.

Anyway, I think that clears up everything regarding that now, thanks.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-19 21:31                                 ` waffl3x
@ 2023-10-19 21:53                                   ` Jakub Jelinek
  0 siblings, 0 replies; 100+ messages in thread
From: Jakub Jelinek @ 2023-10-19 21:53 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

On Thu, Oct 19, 2023 at 09:31:06PM +0000, waffl3x wrote:
> Ah alright, I see what you're saying, I see what the difference is now.
> It's a shame we can't have the translated string insert a %s and format
> into that :^). Ah well, I guess this code is just doomed to look poor
> then, what can you do.

Consider e.g. de.po:
msgid "Generate code to check exception specifications."
msgstr "Code zur Überprüfung von Exception-Spezifikationen erzeugen."
If you try to construct the above from 2 parts as
"%s to check exception specifications.", _("Generate code")
then the german translator will need to give up, because the verb
needs to go last and noun first, so translating "Generate code"
to "Code erzeugen" and the rest can't be done, you want to put one word
in one place and another at another.  A lot of languages
have quite strict rules on order of words in sentence, unlike say
Latin with its comparatively free word order.
You could be lucky, but without knowing at least all the currently
supported languages it can be hard to prove it is ok.
Furthermore, it could prevent some other language translations from being
added.

	Jakub


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-19 21:05                             ` waffl3x
  2023-10-19 21:11                               ` Jakub Jelinek
@ 2023-10-19 22:18                               ` Jason Merrill
       [not found]                                 ` <Q2xXS2RkwtzDslDUAsi4YWupkb9s3QKvecqsxLNUr=5FL-MdSmzIJcZ96S3B9Avk30GneMm8R67JsQ4D-Uj1JB6N8dhTw6LAlNsrpLKHuLP2o=3D@protonmail.com>
  2023-10-19 22:51                                 ` waffl3x
  1 sibling, 2 replies; 100+ messages in thread
From: Jason Merrill @ 2023-10-19 22:18 UTC (permalink / raw)
  To: waffl3x; +Cc: Jakub Jelinek, gcc-patches

On 10/19/23 17:05, waffl3x wrote:
> Also, I'm not sure what %qs is, should I be using that instead of %s
> for strings?

The q prefix means quoted, with ' or other quotation marks, depending on 
the locale.

> On another topic, I have been trying to fix taking pointers to explicit
> object member functions today, as I ended up breaking that when I
> started setting static_function to false for them. Originally it just
> worked so I haven't touched any code related to this, but now that they
> aren't being treating like static member functions as much so a few
> things just broke. What I'm asking myself now is whether it would be
> appropriate to just opt-in to static member function behavior for this
> one, and I'm not sure that would be correct.
> 
> So I started by checking what it did before I turned off the
> static_function flag. It's was being passed into cp_build_addr_expr_1
> as a baselink node, while regular member functions are passed in as an
> offset_ref node. I then checked what the case is for static member
> function, and unsurprisingly those are also handled wrapped in a
> baselink node, but this raised some questions for me.
> 
> I am now trying to figure out what exactly a baselink is, and why it's
> used for static member functions. My current best guess is that
> method_type nodes already hold the information that a baselink does,
> and that information is needed in general. If that is the case, it
> might just be correct to just do the same thing for explicit object
> member functions, but I wonder if there is more to it, but maybe there
> isn't. It worked just fine before when the static_function was still
> being set after all.
> 
> Any insight on this is appreciated.

A BASELINK expresses the result of name lookup for a member function, 
since we need to pass information about the name lookup context along to 
after overload resolution.

An OFFSET_REF (with PTRMEM_OK_P) is used to express that we saw the 
&A::f syntax, so we could build a pointer to member if it resolves to an 
implicit-object member function.

For an overload set containing only a single static member function, 
build_offset_ref doesn't bother to build an OFFSET_REF, but returns the 
BASELINK itself.

I think we need the OFFSET_REF for an explicit-object member function 
because it expresses that the code satisfies the requirement "If the 
operand names an explicit object member function, the operand shall be a 
qualified-id."

It might simplify things to remove the optimization in build_offset_ref 
so we get an OFFSET_REF even for a single static member function, and 
add support for that to cp_build_addr_expr_1.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-19 22:18                               ` Jason Merrill
       [not found]                                 ` <Q2xXS2RkwtzDslDUAsi4YWupkb9s3QKvecqsxLNUr=5FL-MdSmzIJcZ96S3B9Avk30GneMm8R67JsQ4D-Uj1JB6N8dhTw6LAlNsrpLKHuLP2o=3D@protonmail.com>
@ 2023-10-19 22:51                                 ` waffl3x
  2023-10-19 23:35                                   ` waffl3x
  1 sibling, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-10-19 22:51 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Jakub Jelinek, gcc-patches


> A BASELINK expresses the result of name lookup for a member function,
> since we need to pass information about the name lookup context along to
> after overload resolution.
> 
> An OFFSET_REF (with PTRMEM_OK_P) is used to express that we saw the
> &A::f syntax, so we could build a pointer to member if it resolves to an
> implicit-object member function.
> 
> For an overload set containing only a single static member function,
> build_offset_ref doesn't bother to build an OFFSET_REF, but returns the
> BASELINK itself.
> 
> I think we need the OFFSET_REF for an explicit-object member function
> because it expresses that the code satisfies the requirement "If the
> operand names an explicit object member function, the operand shall be a
> qualified-id."
> 
> It might simplify things to remove the optimization in build_offset_ref
> so we get an OFFSET_REF even for a single static member function, and
> add support for that to cp_build_addr_expr_1.
> 
> Jason

Ah okay I think that sheds a little bit of light on things, and here I
was trying not to involve overloads to make it easier for me to
understand things, it seems it ended up making me miss some things.

At a glance it seems like all I need to do then is disable the
PTRMEM_OK_P flag then. I will try that and see how it goes, provided I
can find where it's all setup. (I think I'm almost to the bottom of it,
but it's tough to unravel so I'm not sure.)

I'm also now realizing it's probably about time I add more tests
involving overloads, because I avoided that early on and I don't think
I ever got around to adding any for that.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-19 22:51                                 ` waffl3x
@ 2023-10-19 23:35                                   ` waffl3x
  2023-10-20  2:39                                     ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-10-19 23:35 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Jakub Jelinek, gcc-patches

> (waffl3x (me))
> At a glance it seems like all I need to do then is disable the
> PTRMEM_OK_P flag then.

I'm just now realizing that I'm almost certainly wrong about this. It
still needs PTRMEM_OK_P set if there are any implicit-object member
functions in the overload set. That is, if OFFSET_REF includes that
information... but it doesn't seem like it does? Reading the
information on OFFSET_REF, particularly build_offset_ref, seems to
indicate that OFFSET_REF (at least historically) was only for things
with a pointer to member type.

> > An OFFSET_REF (with PTRMEM_OK_P) is used to express that we saw the
> > &A::f syntax, so we could build a pointer to member if it resolves to an
> > implicit-object member function.
> > 
> > For an overload set containing only a single static member function,
> > build_offset_ref doesn't bother to build an OFFSET_REF, but returns the
> > BASELINK itself.

Based on what you've said, I assume that OFFSET_REF handles static
member functions that are overloaded. But as I've said this seems to
contradict the comments I'm reading, so I'm not sure that I'm
understanding you correctly.

I suppose that will be pretty easy to test, so I'll just do that as
well.

> > I think we need the OFFSET_REF for an explicit-object member function
> > because it expresses that the code satisfies the requirement "If the
> > operand names an explicit object member function, the operand shall be a
> > qualified-id."

I do agree here, but it does reinforce that OFFSET_REF is no longer
just for members represented by pointer to member type. So that might
be something to take into consideration.

> > It might simplify things to remove the optimization in build_offset_ref
> > so we get an OFFSET_REF even for a single static member function, and
> > add support for that to cp_build_addr_expr_1.

I don't think this should be necessary, the "right thing" should just
be done for explicit-object member functions. With all the stuff going
on here that I missed I'm starting to wonder how function overloads
ever worked at all in my patch. On the other hand though, this
optimization probably could be documented better, but I very well might
have missed it even if it were.

Hell, maybe it needs a greater redesign altogether, it seems strange to
me to bundle overload information in with a construct for a specific
expression. (Assuming that's whats happening of course, I still don't
fully understand it.) It's not like this has rules unique to it for how
overload resolution is decided, right? Initializing a param/variable of
pointer to function type with an overloaded function resolves that with
similar rules, I think? Maybe it is a little different now that I write
it out loud.

I wasn't going to finish my musings about that, but it made me realize
that it might not actually be correct for address of explicit-object
member functions to be wrapped by OFFSET_REF. I mean surely it's fine
because based on what you've said static member functions are also
wrapped by OFFSET_REF, so it's likely fully implemented, especially
considering things worked before. But now that there are 2 different
varieties of class members that the address of them can be taken, it
might make sense to split things up a bit? Then again, why were static
member functions ever handled the same way? Taking the address of other
static members isn't handled in the same way here is it?

I'm probably spending too much time thinking about it when I don't
fully understand how it's all being done, I'll just go back to poking
around trying to figure it all out. Then I'll worry about whether
thing's should be done differently or not.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-19 23:35                                   ` waffl3x
@ 2023-10-20  2:39                                     ` Jason Merrill
  2023-10-20  4:34                                       ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-10-20  2:39 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 10/19/23 19:35, waffl3x wrote:
>> (waffl3x (me))
>> At a glance it seems like all I need to do then is disable the
>> PTRMEM_OK_P flag then.
> 
> I'm just now realizing that I'm almost certainly wrong about this. It
> still needs PTRMEM_OK_P set if there are any implicit-object member
> functions in the overload set. That is, if OFFSET_REF includes that
> information... but it doesn't seem like it does? Reading the
> information on OFFSET_REF, particularly build_offset_ref, seems to
> indicate that OFFSET_REF (at least historically) was only for things
> with a pointer to member type.

Or things that might end up with pointer-to-member type after overload 
resolution.

>>> An OFFSET_REF (with PTRMEM_OK_P) is used to express that we saw the
>>> &A::f syntax, so we could build a pointer to member if it resolves to an
>>> implicit-object member function.
>>>
>>> For an overload set containing only a single static member function,
>>> build_offset_ref doesn't bother to build an OFFSET_REF, but returns the
>>> BASELINK itself.
> 
> Based on what you've said, I assume that OFFSET_REF handles static
> member functions that are overloaded. But as I've said this seems to
> contradict the comments I'm reading, so I'm not sure that I'm
> understanding you correctly.

That's right.  For instance,

struct A {
   static void g();
   static void g(int);
};

void (*p)(int) = &A::g; // cp_build_addr_expr_1 gets an OFFSET_REF

>>> I think we need the OFFSET_REF for an explicit-object member function
>>> because it expresses that the code satisfies the requirement "If the
>>> operand names an explicit object member function, the operand shall be a
>>> qualified-id."
> 
> I do agree here, but it does reinforce that OFFSET_REF is no longer
> just for members represented by pointer to member type. So that might
> be something to take into consideration.

An OFFSET_REF that isn't type_unknown_p, agreed.

>>> It might simplify things to remove the optimization in build_offset_ref
>>> so we get an OFFSET_REF even for a single static member function, and
>>> add support for that to cp_build_addr_expr_1.
> 
> I don't think this should be necessary, the "right thing" should just
> be done for explicit-object member functions. With all the stuff going
> on here that I missed I'm starting to wonder how function overloads
> ever worked at all in my patch. On the other hand though, this
> optimization probably could be documented better, but I very well might
> have missed it even if it were.
> 
> Hell, maybe it needs a greater redesign altogether, it seems strange to
> me to bundle overload information in with a construct for a specific
> expression. (Assuming that's whats happening of course, I still don't
> fully understand it.) It's not like this has rules unique to it for how
> overload resolution is decided, right? Initializing a param/variable of
> pointer to function type with an overloaded function resolves that with
> similar rules, I think? Maybe it is a little different now that I write
> it out loud.
> 
> I wasn't going to finish my musings about that, but it made me realize
> that it might not actually be correct for address of explicit-object
> member functions to be wrapped by OFFSET_REF. I mean surely it's fine
> because based on what you've said static member functions are also
> wrapped by OFFSET_REF, so it's likely fully implemented, especially
> considering things worked before. But now that there are 2 different
> varieties of class members that the address of them can be taken, it
> might make sense to split things up a bit? Then again, why were static
> member functions ever handled the same way? Taking the address of other
> static members isn't handled in the same way here is it?

Functions are different because of overloading; in general we can't 
decide what an expression that names a function actually means until we 
have enough context to decide which function, exactly.  So we represent 
the id-expression largely as lookup+syntax until overload resolution 
turns it into a specific function.  The type_unknown_p check earlier in 
cp_build_addr_expr_1 is for that case.

An id-expression that names a single non-template function 
(!really_overloaded_fn) is handled somewhat differently, as we don't 
need to defer everything.  But that means various special-case code.

Currently build_offset_ref special-cases &A::f for a single static 
member function, but we can't use the same special case for single 
explicit object member functions because we need to distinguish between 
&A::f and &f somehow to check the requirement I quoted above.  So it 
seems to me we'll need to add support for single explicit object member 
functions in the OFFSET_REF handling in cp_build_addr_expr_1.  And I 
thought if we're doing that, perhaps we want to move the single static 
handling over there as well, but that's not necessary.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-20  2:39                                     ` Jason Merrill
@ 2023-10-20  4:34                                       ` waffl3x
  2023-10-20 16:01                                         ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-10-20  4:34 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

> > 
> > Based on what you've said, I assume that OFFSET_REF handles static
> > member functions that are overloaded. But as I've said this seems to
> > contradict the comments I'm reading, so I'm not sure that I'm
> > understanding you correctly.
> 
> 
> That's right. For instance,
> 
> struct A {
> static void g();
> static void g(int);
> };
> 
> void (*p)(int) = &A::g; // cp_build_addr_expr_1 gets an OFFSET_REF
> 
> > > > I think we need the OFFSET_REF for an explicit-object member function
> > > > because it expresses that the code satisfies the requirement "If the
> > > > operand names an explicit object member function, the operand shall be a
> > > > qualified-id."
> > 
> > I do agree here, but it does reinforce that OFFSET_REF is no longer
> > just for members represented by pointer to member type. So that might
> > be something to take into consideration.
> 
> 
> An OFFSET_REF that isn't type_unknown_p, agreed.
> 
> > > > It might simplify things to remove the optimization in build_offset_ref
> > > > so we get an OFFSET_REF even for a single static member function, and
> > > > add support for that to cp_build_addr_expr_1.
> > 
> > I don't think this should be necessary, the "right thing" should just
> > be done for explicit-object member functions. With all the stuff going
> > on here that I missed I'm starting to wonder how function overloads
> > ever worked at all in my patch. On the other hand though, this
> > optimization probably could be documented better, but I very well might
> > have missed it even if it were.
> > 
> > Hell, maybe it needs a greater redesign altogether, it seems strange to
> > me to bundle overload information in with a construct for a specific
> > expression. (Assuming that's whats happening of course, I still don't
> > fully understand it.) It's not like this has rules unique to it for how
> > overload resolution is decided, right? Initializing a param/variable of
> > pointer to function type with an overloaded function resolves that with
> > similar rules, I think? Maybe it is a little different now that I write
> > it out loud.
> > 
> > I wasn't going to finish my musings about that, but it made me realize
> > that it might not actually be correct for address of explicit-object
> > member functions to be wrapped by OFFSET_REF. I mean surely it's fine
> > because based on what you've said static member functions are also
> > wrapped by OFFSET_REF, so it's likely fully implemented, especially
> > considering things worked before. But now that there are 2 different
> > varieties of class members that the address of them can be taken, it
> > might make sense to split things up a bit? Then again, why were static
> > member functions ever handled the same way? Taking the address of other
> > static members isn't handled in the same way here is it?
> 
> 
> Functions are different because of overloading; in general we can't
> decide what an expression that names a function actually means until we
> have enough context to decide which function, exactly. So we represent
> the id-expression largely as lookup+syntax until overload resolution
> turns it into a specific function. The type_unknown_p check earlier in
> cp_build_addr_expr_1 is for that case.

Yeah this all makes sense, but that's why I was confused by the
following documentation from cp-tree.def.

```
/* An OFFSET_REF is used in two situations:

   1. An expression of the form `A::m' where `A' is a class and `m' is
      a non-static member.  In this case, operand 0 will be a TYPE
      (corresponding to `A') and operand 1 will be a FIELD_DECL,
      BASELINK, or TEMPLATE_ID_EXPR (corresponding to `m').

      The expression is a pointer-to-member if its address is taken,
      but simply denotes a member of the object if its address is not
      taken.
```

> An OFFSET_REF that isn't type_unknown_p, agreed.
But I suppose that's what this might have been referring to.

So is that the case then? OFFSET_REF might be for a regular address of
member expression unless it's type_unknown_p?

> 
> An id-expression that names a single non-template function
> (!really_overloaded_fn) is handled somewhat differently, as we don't
> need to defer everything. But that means various special-case code.
> 
> Currently build_offset_ref special-cases &A::f for a single static
> member function, but we can't use the same special case for single
> explicit object member functions because we need to distinguish between
> &A::f and &f somehow to check the requirement I quoted above. So it
> seems to me we'll need to add support for single explicit object member
> functions in the OFFSET_REF handling in cp_build_addr_expr_1. And I
> thought if we're doing that, perhaps we want to move the single static
> handling over there as well, but that's not necessary.
> 
> Jason

I'm done for today but it does seem like that special case is what is
causing my crash. I found that it only jumps into the section that it
crashes in when there are no overloads. I'm kinda close to fixing it
probably but I've spent too long on it for today, it's melting together.

> Currently build_offset_ref special-cases &A::f for a single static
> member function, but we can't use the same special case for single
> explicit object member functions because we need to distinguish between
> &A::f and &f somehow to check the requirement I quoted above.

I don't understand what this means exactly, under what circumstances
would &f find the member function. Oh, I guess while in the body of
it's class, I hadn't considered that. Is that what you're referring to?

Well either way, I'm going to pick back up here tomorrow and see if I
can finish figuring out exactly whats causing the problems. I'm pretty
certain it's the special case that causes it, I'm just not sure what
part exactly is causing the difference, and whether there aren't more
hiding issues. But for now I need to get some rest. Thanks for the
detailed responses, they helped a lot, this part of the code has been
particularly difficult to figure out.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-20  4:34                                       ` waffl3x
@ 2023-10-20 16:01                                         ` Jason Merrill
       [not found]                                           ` <zXWkSXEO=5FH62WXyNUeV1zNAx9wSVGQ5ooxKAfpN2InCP4X25uOC0yTZlnMqbDMoIa4lGwj0hP-KEP5UIcMs1S1zkhz=5FZx4oM3oz09DY2BRg=3D@protonmail.com>
  2023-10-28  4:07                                           ` waffl3x
  0 siblings, 2 replies; 100+ messages in thread
From: Jason Merrill @ 2023-10-20 16:01 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 10/20/23 00:34, waffl3x wrote:
>>>
>>> Based on what you've said, I assume that OFFSET_REF handles static
>>> member functions that are overloaded. But as I've said this seems to
>>> contradict the comments I'm reading, so I'm not sure that I'm
>>> understanding you correctly.
>>
>>
>> That's right. For instance,
>>
>> struct A {
>> static void g();
>> static void g(int);
>> };
>>
>> void (*p)(int) = &A::g; // cp_build_addr_expr_1 gets an OFFSET_REF
>>
>>>>> I think we need the OFFSET_REF for an explicit-object member function
>>>>> because it expresses that the code satisfies the requirement "If the
>>>>> operand names an explicit object member function, the operand shall be a
>>>>> qualified-id."
>>>
>>> I do agree here, but it does reinforce that OFFSET_REF is no longer
>>> just for members represented by pointer to member type. So that might
>>> be something to take into consideration.
>>
>>
>> An OFFSET_REF that isn't type_unknown_p, agreed.
>>
>>>>> It might simplify things to remove the optimization in build_offset_ref
>>>>> so we get an OFFSET_REF even for a single static member function, and
>>>>> add support for that to cp_build_addr_expr_1.
>>>
>>> I don't think this should be necessary, the "right thing" should just
>>> be done for explicit-object member functions. With all the stuff going
>>> on here that I missed I'm starting to wonder how function overloads
>>> ever worked at all in my patch. On the other hand though, this
>>> optimization probably could be documented better, but I very well might
>>> have missed it even if it were.
>>>
>>> Hell, maybe it needs a greater redesign altogether, it seems strange to
>>> me to bundle overload information in with a construct for a specific
>>> expression. (Assuming that's whats happening of course, I still don't
>>> fully understand it.) It's not like this has rules unique to it for how
>>> overload resolution is decided, right? Initializing a param/variable of
>>> pointer to function type with an overloaded function resolves that with
>>> similar rules, I think? Maybe it is a little different now that I write
>>> it out loud.
>>>
>>> I wasn't going to finish my musings about that, but it made me realize
>>> that it might not actually be correct for address of explicit-object
>>> member functions to be wrapped by OFFSET_REF. I mean surely it's fine
>>> because based on what you've said static member functions are also
>>> wrapped by OFFSET_REF, so it's likely fully implemented, especially
>>> considering things worked before. But now that there are 2 different
>>> varieties of class members that the address of them can be taken, it
>>> might make sense to split things up a bit? Then again, why were static
>>> member functions ever handled the same way? Taking the address of other
>>> static members isn't handled in the same way here is it?
>>
>>
>> Functions are different because of overloading; in general we can't
>> decide what an expression that names a function actually means until we
>> have enough context to decide which function, exactly. So we represent
>> the id-expression largely as lookup+syntax until overload resolution
>> turns it into a specific function. The type_unknown_p check earlier in
>> cp_build_addr_expr_1 is for that case.
> 
> Yeah this all makes sense, but that's why I was confused by the
> following documentation from cp-tree.def.
> 
> ```
> /* An OFFSET_REF is used in two situations:
> 
>     1. An expression of the form `A::m' where `A' is a class and `m' is
>        a non-static member.  In this case, operand 0 will be a TYPE
>        (corresponding to `A') and operand 1 will be a FIELD_DECL,
>        BASELINK, or TEMPLATE_ID_EXPR (corresponding to `m').
> 
>        The expression is a pointer-to-member if its address is taken,
>        but simply denotes a member of the object if its address is not
>        taken.
> ```

Yeah, I'll adjust that statement to be more conditional.  Is this clearer?

     1. An expression of the form `A::m' where `A' is a class and `m' is 

        a non-static member or an overload set.  In this case, operand 0 

        will be a TYPE (corresponding to `A') and operand 1 will be a 

        FIELD_DECL, BASELINK, or TEMPLATE_ID_EXPR (corresponding to 
`m').
 

        The expression is a pointer-to-member if its address is taken 
(and
        if, after any overload resolution, 'm' does not designate a 

        static or explicit object member function).  It simply denotes a 

        member of the object if its address is not taken. 


>> An OFFSET_REF that isn't type_unknown_p, agreed.
> But I suppose that's what this might have been referring to.
> 
> So is that the case then? OFFSET_REF might be for a regular address of
> member expression unless it's type_unknown_p?

If it's type_unknown_p we don't know which member it is, so we don't 
know whether taking its address gives a PMF or a regular pointer.

If it's not type_unknown_p, we know which member it is, and we currently 
skip building it at all for static members, so taking its address always 
gives a pointer to member.  explicit object member functions will make 
that assumption no longer valid.

>> An id-expression that names a single non-template function
>> (!really_overloaded_fn) is handled somewhat differently, as we don't
>> need to defer everything. But that means various special-case code.
>>
>> Currently build_offset_ref special-cases &A::f for a single static
>> member function, but we can't use the same special case for single
>> explicit object member functions because we need to distinguish between
>> &A::f and &f somehow to check the requirement I quoted above. So it
>> seems to me we'll need to add support for single explicit object member
>> functions in the OFFSET_REF handling in cp_build_addr_expr_1. And I
>> thought if we're doing that, perhaps we want to move the single static
>> handling over there as well, but that's not necessary.
> 
> I'm done for today but it does seem like that special case is what is
> causing my crash. I found that it only jumps into the section that it
> crashes in when there are no overloads. I'm kinda close to fixing it
> probably but I've spent too long on it for today, it's melting together.
> 
>> Currently build_offset_ref special-cases &A::f for a single static
>> member function, but we can't use the same special case for single
>> explicit object member functions because we need to distinguish between
>> &A::f and &f somehow to check the requirement I quoted above.
> 
> I don't understand what this means exactly, under what circumstances
> would &f find the member function. Oh, I guess while in the body of
> it's class, I hadn't considered that. Is that what you're referring to?

Right:

struct A {
   void g(this A&);
   A() {
     &A::g; // ok
     &g; // same error as for an implicit object member function
   }
};

> Well either way, I'm going to pick back up here tomorrow and see if I
> can finish figuring out exactly whats causing the problems. I'm pretty
> certain it's the special case that causes it, I'm just not sure what
> part exactly is causing the difference, and whether there aren't more
> hiding issues. But for now I need to get some rest. Thanks for the
> detailed responses, they helped a lot, this part of the code has been
> particularly difficult to figure out.

You're welcome, thanks for your interest.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-20 16:01                                         ` Jason Merrill
       [not found]                                           ` <zXWkSXEO=5FH62WXyNUeV1zNAx9wSVGQ5ooxKAfpN2InCP4X25uOC0yTZlnMqbDMoIa4lGwj0hP-KEP5UIcMs1S1zkhz=5FZx4oM3oz09DY2BRg=3D@protonmail.com>
@ 2023-10-28  4:07                                           ` waffl3x
  2023-10-28 23:21                                             ` waffl3x
  2023-11-03  3:04                                             ` Jason Merrill
  1 sibling, 2 replies; 100+ messages in thread
From: waffl3x @ 2023-10-28  4:07 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

I've been under the weather so I took a few days break, I honestly was
also very reluctant to pick it back up. The current problem is what I
like to call "not friendly", but I am back at it now.

> > I don't understand what this means exactly, under what circumstances
> > would &f find the member function. Oh, I guess while in the body of
> > it's class, I hadn't considered that. Is that what you're referring to?
>
>
> Right:
>
> struct A {
> void g(this A&);
> A() {
> &A::g; // ok
> &g; // same error as for an implicit object member function
> }
> };

I fully get this now, I threw together a test for it so this case
doesn't get forgotten about. Unfortunately though, I am concerned that
the approach I was going to take to fix the crash would have the
incorrect behavior for this.

Here is what I added to cp_build_addr_expr_1 with context included.
```
    case OFFSET_REF:
    offset_ref:
      /* Turn a reference to a non-static data member into a
	 pointer-to-member.  */
      {
	tree type;
	tree t;

	gcc_assert (PTRMEM_OK_P (arg));

	t = TREE_OPERAND (arg, 1);
	if (TYPE_REF_P (TREE_TYPE (t)))
	  {
	    if (complain & tf_error)
	      error_at (loc,
			"cannot create pointer to reference member %qD", t);
	    return error_mark_node;
	  }
        /* -- Waffl3x additions start -- */
	/* Exception for non-overloaded explicit object member function.  */
	if (TREE_CODE (TREE_TYPE (t)) == FUNCTION_TYPE)
	  return build1 (ADDR_EXPR, unknown_type_node, arg);
        /* -- Waffl3x additions end -- */

	/* Forming a pointer-to-member is a use of non-pure-virtual fns.  */
	if (TREE_CODE (t) == FUNCTION_DECL
	    && !DECL_PURE_VIRTUAL_P (t)
	    && !mark_used (t, complain) && !(complain & tf_error))
	  return error_mark_node;
```

I had hoped this naive solution would work just fine, but unfortunately
the following code fails to compile with an error.

```
struct S {
    void f(this S&) {}
};
int main() {
    void (*a)(S&) = &S::f;
}
```
normal_static.C: In function ‘int main()’:
normal_static.C:13:25: error: cannot convert ‘S::f’ from type ‘void(S&)’ to type ‘void (*)(S&)’
   13 |     void (*a)(S&) = &S::f;
      |                         ^

So clearly it isn't going to be that easy. I went up and down looking
at how the single static case is handled, and tried to read the code in
build_ptrmem_type and build_ptrmemfunc_type but I had a hard time
figuring it out.

The good news is, this problem was difficult enough that it made me
pick a proper diff tool with regex support instead of using a silly web
browser tool and pasting things into it. Or worse, pasting them into a
tool and doing replacement and then pasting them into the silly web
browser tool. I have been forced to improve my workflow thanks to this
head scratcher. So it's not all for naught.

Back on topic, it's not really the optimization returning a baselink
that is causing the original crash. It's just the assert in
build_ptrmem_type failing when a FUNCTION_TYPE is reaching it. The
optimization did throw me for a loop when I was comparing how my
previous version (that incorrectly set the lang_decl_fn ::
static_function flag) was handling things. Looking back, I think I
explained myself and the methodology I was using to investigate really
poorly, I apologize for the confusion I might have caused :).

To state it plainly, it seems to me that the arg parameter being passed
into cp_build_addr_expr_1 for explicit object member functions is
(mostly) pretty much correct and what we would want.

So the whole thing with the baselink optimization was really just a red
herring that I was following. Now that I have a better understanding of
what's happening leading up to and in cp_build_addr_expr_1 I don't
think it's relevant at all for this problem. With that said, I am
questioning again if the optimization that returns a baselink node is
the right way to do things. So this is something I'm going to put down
into my little notes text file to investigate at a later time, and
forget about it for the moment as it shouldn't be causing any friction
for us here.

Anyway, as I eluded to above, if I can't figure out the right way to
solve this problem in a decent amount of time I think I'm going to
leave it for now. I'll come back to it once other higher priority
things are fixed or finished. And hopefully someone more familiar with
this area of the code will have a better idea of what we need to do to
handle this case in a non-intrusive manner.

That wraps up my current status on this specifically. But while
investigating it I uncovered a few things that I feel are important to
discuss/note.

I wanted to change DECL_NONSTATIC_MEMBER_FUNCTION_P to include explicit
object member functions, but it had some problems when I made the
modification. I also noticed that it's used in cp-objcp-common.cc so
would making changes to it be a bad idea?

-- cp-tree.h
```
/* Nonzero for FUNCTION_DECL means that this decl is a non-static
   member function.  */
#define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
  (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
```
I didn't want to investigate the problems as I was knee deep in
investigating the addressof bug. So I instead modified
DECL_FUNCTION_MEMBER_P to include explicit object member functions and
moved on.

-- cp-tree.h
```
/* 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_IS_XOBJ_MEMBER_FUNC (NODE))
```
I am mostly just mentioning this here in case it becomes more relevant
later. Looking at how much DECL_NONSTATIC_MEMBER_FUNCTION_P is used
throughout the code I now suspect that adding explicit object member
functions to it might cause xobj member functions to be treated as
regular member functions when they should not be.

If this change were to stick it would cause a discrepancy in the
behavior of DECL_NONSTATIC_MEMBER_FUNCTION_P and it's name. If we were
to do this, I think it's important we document the discrepancy and why
it exists, and in the future, it should possibly be refactored. One
option would be to simply rename it to DECL_IOBJ_MEMBER_FUNCTION_P.
After all, I suspect that it's unlikely that the current macro
(DECL_NONSTATIC_MEMBER_FUNCTION_P) is being used in places that concern
explicit object member functions. So just adding explicit object member
functions to it will most likely just result in headaches.

It seems to me that would be the best solution, so when and if it comes
up again, I think that route should be considered.


Secondly, there are some differences in the nodes describing an
explicit object member function from those describing static member
functions and implicit object member functions that I am not sure
should be present.

I did my best to summarize the differences, if you want the logs of
tree_debug that I derived them from I can provide them. Most of my
understanding of the layout of the nodes is from reading print-tree.cc
and looking at debug_tree outputs, so it's possible I made a mistake.

I am opting to use the names of members as they are output by
debug_tree, I recognize this is not always the actual name of the
member in the actual tree_node structures.

Additionally, some of the differences listed are to be expected and are
most likely the correct values for each node. However, I wanted to be
exhaustive when listing them just in case I am mistaken in my opinion
on whether the differences should or should not occur.

The following declarations were used as input to the compiler.
iobj decl:
struct S { void f() {} };
xobj decl:
struct S { void f(this S&) {} };
static decl:
struct S { static void f(S&) {} };

These differences can be observed in the return values of
grokdeclarator for each declaration.

1. function_decl::type::tree_code
iobj: method_type
xobj: function_type
stat: function_type
2. function_decl::type::method basetype
iobj: <record_type 0x7ffff7194c78 S>
xobj: NULL/no output
stat: NULL/no output
3. function_decl::type::arg-types::tree_list[0]::value
iobj: <pointer_type>
xobj: <reference_type>
stat: <reference_type>
4. function_decl::decl_6
iobj: false/no output
xobj: false/no output
stat: true
5. function_decl::align
iobj: 16
xobj: 8
stat: 8
6. function_decl::result::uid
iobj: D.2513
xobj: D.2513
stat: D.2512
7. function_decl::full-name
iobj: "void S::f()"
xobj: "void S::f(this S&)"

Differences 1, 3, and 7 seem obviously correct to me for all 3
declarations, 6 is a little bizarre to me, but since it's just a UID
it's merely an oddity, I doubt it is concerning.

That leaves 2, 4, and 5.

2. I am pretty sure xobj functions should have the struct they are a
part of recorded as the method basetype member. I have already checked
that function_type and method_type are the same node type under the
hood and it does appear to be, so it should be trivial to set it.
However I do have to wonder why static member functions don't set it,
it seems to be that it would be convenient to use the same field. Can
you provide any insight into that?

4. I have no comment here, but it does concern me since I don't
understand it at all.

5. I am pretty sure this is fine for now, but if xobj member functions
ever were to support virtual/override capabilities, then it would be a
problem. Is my understanding correct, or is there some other reason
that iobj member functions have a different value here?

There are also some differences in the arg param in
cp_build_addr_expr_1 that concerns me, but most of them are reflected
in the differences I have already noted. I had wanted to include these
differences as well but I have been spending too much time staring at
it, it's no longer productive. In short, the indirect_ref node for xobj
member functions has reference_to_this set, while iobj member functions
do not. The baselink binfo field has the private flag set for xobj
member functions, iobj member functions does not.

I've spent too much time on this write up, so I am calling it here, it
wasn't all a waste of time because half of what I was doing here are
things I was going to need to do anyway at least. I still feel like I
spent too much time on it. Hopefully it's of some value for me/others
later.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-28  4:07                                           ` waffl3x
@ 2023-10-28 23:21                                             ` waffl3x
  2023-11-01 23:15                                               ` waffl3x
  2023-11-03  3:04                                             ` Jason Merrill
  1 sibling, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-10-28 23:21 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

I woke up today and figured it out in about 30 minutes, I don't know if
this solution would be okay but I am running away from
cp_build_addr_expr_1 for now. I think this is another place I will have
the urge to refactor in the future, but if I keep looking at it right
now I am just going to waste all my energy before I finish everything
else.

This line
```
  else if (BASELINK_P (TREE_OPERAND (arg, 1)))
```
near the bottom of the function is almost certainly dead. The switch
case (near the top) for baselink reassigns arg.
```
case BASELINK:
  arg = BASELINK_FUNCTIONS (arg);
```
And I'm pretty sure a baselink's functions can't be a baselink, so that
case near the bottom is almost certainly dead. I've put it in my todo
so I will look at it in the future.

Anyway, here is my new solution, it seems to work, but as indicated in
the comment (for myself) I'm not sure this handles constexpr things
right as near the bottom of this function there is some constexpr
handling. I know I said it took me 30 minutes but I realized I had some
holes I needed to check as I was writing this email, so it was more
like 2 hours.
```
        /* Forming a pointer-to-member is a use of non-pure-virtual fns.  */
        if (TREE_CODE (t) == FUNCTION_DECL
            && !DECL_PURE_VIRTUAL_P (t)
            && !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 still 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);
        return t;
      }
```
I'm sharing it because I am still uncertain on whether this is the
ideal solution, this function is kind of a mess in general (sorry) so
it's hard to tell.

There's still a few things in my previous email that I would like
looked at, primarily the differences I observed in returns from
grokdeclarator, but I am glad to finally be moving forward on to other
things. Hopefully I can put together the patches with this one fixed.
(Of course I still have to fix the other bug I found as well.)

I had thought I might try to get lambda support and by value xobj
params working in this patch, but I think that will still be something
I work on once I am finished the initial patch. I still want to get
both of those into GCC14 though so I probably need to speed things up.

Alex

> 
> 
> I've been under the weather so I took a few days break, I honestly was
> also very reluctant to pick it back up. The current problem is what I
> like to call "not friendly", but I am back at it now.
> 
> > > I don't understand what this means exactly, under what circumstances
> > > would &f find the member function. Oh, I guess while in the body of
> > > it's class, I hadn't considered that. Is that what you're referring to?
> > 
> > Right:
> > 
> > struct A {
> > void g(this A&);
> > A() {
> > &A::g; // ok
> > &g; // same error as for an implicit object member function
> > }
> > };
> 
> 
> I fully get this now, I threw together a test for it so this case
> doesn't get forgotten about. Unfortunately though, I am concerned that
> the approach I was going to take to fix the crash would have the
> incorrect behavior for this.
> 
> Here is what I added to cp_build_addr_expr_1 with context included.
> `case OFFSET_REF: offset_ref: /* Turn a reference to a non-static data member into a pointer-to-member. */ { tree type; tree t; gcc_assert (PTRMEM_OK_P (arg)); t = TREE_OPERAND (arg, 1); if (TYPE_REF_P (TREE_TYPE (t))) { if (complain & tf_error) error_at (loc, "cannot create pointer to reference member %qD", t); return error_mark_node; } /* -- Waffl3x additions start -- */ /* Exception for non-overloaded explicit object member function. */ if (TREE_CODE (TREE_TYPE (t)) == FUNCTION_TYPE) return build1 (ADDR_EXPR, unknown_type_node, arg); /* -- Waffl3x additions end -- */ /* Forming a pointer-to-member is a use of non-pure-virtual fns. */ if (TREE_CODE (t) == FUNCTION_DECL && !DECL_PURE_VIRTUAL_P (t) && !mark_used (t, complain) && !(complain & tf_error)) return error_mark_node;`
> 
> I had hoped this naive solution would work just fine, but unfortunately
> the following code fails to compile with an error.
> 
> `struct S { void f(this S&) {} }; int main() { void (*a)(S&) = &S::f; }`
> normal_static.C: In function ‘int main()’:
> normal_static.C:13:25: error: cannot convert ‘S::f’ from type ‘void(S&)’ to type ‘void (*)(S&)’
> 13 | void (*a)(S&) = &S::f;
> | ^
> 
> So clearly it isn't going to be that easy. I went up and down looking
> at how the single static case is handled, and tried to read the code in
> build_ptrmem_type and build_ptrmemfunc_type but I had a hard time
> figuring it out.
> 
> The good news is, this problem was difficult enough that it made me
> pick a proper diff tool with regex support instead of using a silly web
> browser tool and pasting things into it. Or worse, pasting them into a
> tool and doing replacement and then pasting them into the silly web
> browser tool. I have been forced to improve my workflow thanks to this
> head scratcher. So it's not all for naught.
> 
> Back on topic, it's not really the optimization returning a baselink
> that is causing the original crash. It's just the assert in
> build_ptrmem_type failing when a FUNCTION_TYPE is reaching it. The
> optimization did throw me for a loop when I was comparing how my
> previous version (that incorrectly set the lang_decl_fn ::
> static_function flag) was handling things. Looking back, I think I
> explained myself and the methodology I was using to investigate really
> poorly, I apologize for the confusion I might have caused :).
> 
> To state it plainly, it seems to me that the arg parameter being passed
> into cp_build_addr_expr_1 for explicit object member functions is
> (mostly) pretty much correct and what we would want.
> 
> So the whole thing with the baselink optimization was really just a red
> herring that I was following. Now that I have a better understanding of
> what's happening leading up to and in cp_build_addr_expr_1 I don't
> think it's relevant at all for this problem. With that said, I am
> questioning again if the optimization that returns a baselink node is
> the right way to do things. So this is something I'm going to put down
> into my little notes text file to investigate at a later time, and
> forget about it for the moment as it shouldn't be causing any friction
> for us here.
> 
> Anyway, as I eluded to above, if I can't figure out the right way to
> solve this problem in a decent amount of time I think I'm going to
> leave it for now. I'll come back to it once other higher priority
> things are fixed or finished. And hopefully someone more familiar with
> this area of the code will have a better idea of what we need to do to
> handle this case in a non-intrusive manner.
> 
> That wraps up my current status on this specifically. But while
> investigating it I uncovered a few things that I feel are important to
> discuss/note.
> 
> I wanted to change DECL_NONSTATIC_MEMBER_FUNCTION_P to include explicit
> object member functions, but it had some problems when I made the
> modification. I also noticed that it's used in cp-objcp-common.cc so
> would making changes to it be a bad idea?
> 
> -- cp-tree.h
> `/* Nonzero for FUNCTION_DECL means that this decl is a non-static member function. */ #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \\ (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)`
> I didn't want to investigate the problems as I was knee deep in
> investigating the addressof bug. So I instead modified
> DECL_FUNCTION_MEMBER_P to include explicit object member functions and
> moved on.
> 
> -- cp-tree.h
> `/* 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_IS_XOBJ_MEMBER_FUNC (NODE))`
> I am mostly just mentioning this here in case it becomes more relevant
> later. Looking at how much DECL_NONSTATIC_MEMBER_FUNCTION_P is used
> throughout the code I now suspect that adding explicit object member
> functions to it might cause xobj member functions to be treated as
> regular member functions when they should not be.
> 
> If this change were to stick it would cause a discrepancy in the
> behavior of DECL_NONSTATIC_MEMBER_FUNCTION_P and it's name. If we were
> to do this, I think it's important we document the discrepancy and why
> it exists, and in the future, it should possibly be refactored. One
> option would be to simply rename it to DECL_IOBJ_MEMBER_FUNCTION_P.
> After all, I suspect that it's unlikely that the current macro
> (DECL_NONSTATIC_MEMBER_FUNCTION_P) is being used in places that concern
> explicit object member functions. So just adding explicit object member
> functions to it will most likely just result in headaches.
> 
> It seems to me that would be the best solution, so when and if it comes
> up again, I think that route should be considered.
> 
> 
> Secondly, there are some differences in the nodes describing an
> explicit object member function from those describing static member
> functions and implicit object member functions that I am not sure
> should be present.
> 
> I did my best to summarize the differences, if you want the logs of
> tree_debug that I derived them from I can provide them. Most of my
> understanding of the layout of the nodes is from reading print-tree.cc
> and looking at debug_tree outputs, so it's possible I made a mistake.
> 
> I am opting to use the names of members as they are output by
> debug_tree, I recognize this is not always the actual name of the
> member in the actual tree_node structures.
> 
> Additionally, some of the differences listed are to be expected and are
> most likely the correct values for each node. However, I wanted to be
> exhaustive when listing them just in case I am mistaken in my opinion
> on whether the differences should or should not occur.
> 
> The following declarations were used as input to the compiler.
> iobj decl:
> struct S { void f() {} };
> xobj decl:
> struct S { void f(this S&) {} };
> static decl:
> struct S { static void f(S&) {} };
> 
> These differences can be observed in the return values of
> grokdeclarator for each declaration.
> 
> 1. function_decl::type::tree_code
> iobj: method_type
> xobj: function_type
> stat: function_type
> 2. function_decl::type::method basetype
> iobj: <record_type 0x7ffff7194c78 S>
> 
> xobj: NULL/no output
> stat: NULL/no output
> 3. function_decl::type::arg-types::tree_list[0]::value
> iobj: <pointer_type>
> 
> xobj: <reference_type>
> 
> stat: <reference_type>
> 
> 4. function_decl::decl_6
> iobj: false/no output
> xobj: false/no output
> stat: true
> 5. function_decl::align
> iobj: 16
> xobj: 8
> stat: 8
> 6. function_decl::result::uid
> iobj: D.2513
> xobj: D.2513
> stat: D.2512
> 7. function_decl::full-name
> iobj: "void S::f()"
> xobj: "void S::f(this S&)"
> 
> Differences 1, 3, and 7 seem obviously correct to me for all 3
> declarations, 6 is a little bizarre to me, but since it's just a UID
> it's merely an oddity, I doubt it is concerning.
> 
> That leaves 2, 4, and 5.
> 
> 2. I am pretty sure xobj functions should have the struct they are a
> part of recorded as the method basetype member. I have already checked
> that function_type and method_type are the same node type under the
> hood and it does appear to be, so it should be trivial to set it.
> However I do have to wonder why static member functions don't set it,
> it seems to be that it would be convenient to use the same field. Can
> you provide any insight into that?
> 
> 4. I have no comment here, but it does concern me since I don't
> understand it at all.
> 
> 5. I am pretty sure this is fine for now, but if xobj member functions
> ever were to support virtual/override capabilities, then it would be a
> problem. Is my understanding correct, or is there some other reason
> that iobj member functions have a different value here?
> 
> There are also some differences in the arg param in
> cp_build_addr_expr_1 that concerns me, but most of them are reflected
> in the differences I have already noted. I had wanted to include these
> differences as well but I have been spending too much time staring at
> it, it's no longer productive. In short, the indirect_ref node for xobj
> member functions has reference_to_this set, while iobj member functions
> do not. The baselink binfo field has the private flag set for xobj
> member functions, iobj member functions does not.
> 
> I've spent too much time on this write up, so I am calling it here, it
> wasn't all a waste of time because half of what I was doing here are
> things I was going to need to do anyway at least. I still feel like I
> spent too much time on it. Hopefully it's of some value for me/others
> later.
> 
> Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-28 23:21                                             ` waffl3x
@ 2023-11-01 23:15                                               ` waffl3x
  2023-11-02  7:01                                                 ` waffl3x
  2023-11-02  7:01                                                 ` Jakub Jelinek
  0 siblings, 2 replies; 100+ messages in thread
From: waffl3x @ 2023-11-01 23:15 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

Just want to quickly check, when is the cutoff for GCC14 exactly? I
want to know how much time I have left to try to tackle this bug with
subscript. I'm going to be crunching out final stuff this week and I'll
try to get a (probably non-final) patch for you to review today.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-01 23:15                                               ` waffl3x
@ 2023-11-02  7:01                                                 ` waffl3x
  2023-11-02  7:01                                                 ` Jakub Jelinek
  1 sibling, 0 replies; 100+ messages in thread
From: waffl3x @ 2023-11-02  7:01 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

The problem with operators is fixed, now starts a long period of
testing. It's been so long since I've gotten to this part that I almost
forgot that I have to do it :'). When/if regtests and bootstrap all
pass I will format the patch and submit it.



------- Original Message -------
On Wednesday, November 1st, 2023 at 5:15 PM, waffl3x <waffl3x@protonmail.com> wrote:


> 
> 
> Just want to quickly check, when is the cutoff for GCC14 exactly? I
> want to know how much time I have left to try to tackle this bug with
> subscript. I'm going to be crunching out final stuff this week and I'll
> try to get a (probably non-final) patch for you to review today.
> 
> Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-01 23:15                                               ` waffl3x
  2023-11-02  7:01                                                 ` waffl3x
@ 2023-11-02  7:01                                                 ` Jakub Jelinek
  1 sibling, 0 replies; 100+ messages in thread
From: Jakub Jelinek @ 2023-11-02  7:01 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

On Wed, Nov 01, 2023 at 11:15:56PM +0000, waffl3x wrote:
> Just want to quickly check, when is the cutoff for GCC14 exactly? I

Nov 18th EOD.  But generally it is cutoff for posting new feature patches
that could be accepted, patches posted before the deadline if reviewed
soon after the deadline still can make it in.

	Jakub


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-10-28  4:07                                           ` waffl3x
  2023-10-28 23:21                                             ` waffl3x
@ 2023-11-03  3:04                                             ` Jason Merrill
  2023-11-03  4:44                                               ` waffl3x
  1 sibling, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-03  3:04 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 10/28/23 00:07, waffl3x wrote:
> I wanted to change DECL_NONSTATIC_MEMBER_FUNCTION_P to include explicit
> object member functions, but it had some problems when I made the
> modification. I also noticed that it's used in cp-objcp-common.cc so
> would making changes to it be a bad idea?
> 
> -- cp-tree.h
> ```
> /* Nonzero for FUNCTION_DECL means that this decl is a non-static
>     member function.  */
> #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
>    (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
> ```
> I didn't want to investigate the problems as I was knee deep in
> investigating the addressof bug. So I instead modified
> DECL_FUNCTION_MEMBER_P to include explicit object member functions and
> moved on.
> 
> -- cp-tree.h
> ```
> /* 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_IS_XOBJ_MEMBER_FUNC (NODE))
> ```
> I am mostly just mentioning this here in case it becomes more relevant
> later. Looking at how much DECL_NONSTATIC_MEMBER_FUNCTION_P is used
> throughout the code I now suspect that adding explicit object member
> functions to it might cause xobj member functions to be treated as
> regular member functions when they should not be.
> 
> If this change were to stick it would cause a discrepancy in the
> behavior of DECL_NONSTATIC_MEMBER_FUNCTION_P and it's name. If we were
> to do this, I think it's important we document the discrepancy and why
> it exists, and in the future, it should possibly be refactored. One
> option would be to simply rename it to DECL_IOBJ_MEMBER_FUNCTION_P.
> After all, I suspect that it's unlikely that the current macro
> (DECL_NONSTATIC_MEMBER_FUNCTION_P) is being used in places that concern
> explicit object member functions. So just adding explicit object member
> functions to it will most likely just result in headaches.
> 
> It seems to me that would be the best solution, so when and if it comes
> up again, I think that route should be considered.

Agreed, it sounds good to rename the current macro and then add a new 
macro that includes both implicit and explicit, assuming that's a useful 
category.

> Secondly, there are some differences in the nodes describing an
> explicit object member function from those describing static member
> functions and implicit object member functions that I am not sure
> should be present.
> 
> I did my best to summarize the differences, if you want the logs of
> tree_debug that I derived them from I can provide them. Most of my
> understanding of the layout of the nodes is from reading print-tree.cc
> and looking at debug_tree outputs, so it's possible I made a mistake.
> 
> I am opting to use the names of members as they are output by
> debug_tree, I recognize this is not always the actual name of the
> member in the actual tree_node structures.
> 
> Additionally, some of the differences listed are to be expected and are
> most likely the correct values for each node. However, I wanted to be
> exhaustive when listing them just in case I am mistaken in my opinion
> on whether the differences should or should not occur.
> 
> The following declarations were used as input to the compiler.
> iobj decl:
> struct S { void f() {} };
> xobj decl:
> struct S { void f(this S&) {} };
> static decl:
> struct S { static void f(S&) {} };
> 
> These differences can be observed in the return values of
> grokdeclarator for each declaration.
> 
> 1. function_decl::type::tree_code
> iobj: method_type
> xobj: function_type
> stat: function_type
> 2. function_decl::type::method basetype
> iobj: <record_type 0x7ffff7194c78 S>
> xobj: NULL/no output
> stat: NULL/no output
> 3. function_decl::type::arg-types::tree_list[0]::value
> iobj: <pointer_type>
> xobj: <reference_type>
> stat: <reference_type>
> 4. function_decl::decl_6
> iobj: false/no output
> xobj: false/no output
> stat: true
> 5. function_decl::align
> iobj: 16
> xobj: 8
> stat: 8
> 6. function_decl::result::uid
> iobj: D.2513
> xobj: D.2513
> stat: D.2512
> 7. function_decl::full-name
> iobj: "void S::f()"
> xobj: "void S::f(this S&)"
> 
> Differences 1, 3, and 7 seem obviously correct to me for all 3
> declarations, 6 is a little bizarre to me, but since it's just a UID
> it's merely an oddity, I doubt it is concerning.

Agreed.

> That leaves 2, 4, and 5.
> 
> 2. I am pretty sure xobj functions should have the struct they are a
> part of recorded as the method basetype member. I have already checked
> that function_type and method_type are the same node type under the
> hood and it does appear to be, so it should be trivial to set it.
> However I do have to wonder why static member functions don't set it,
> it seems to be that it would be convenient to use the same field. Can
> you provide any insight into that?

method basetype is only for METHOD_TYPE; if you want the containing type 
of the function, that's DECL_CONTEXT.

> 4. I have no comment here, but it does concern me since I don't
> understand it at all.

In the list near the top of cp-tree.h, DECL_LANG_FLAG_6 for a 
FUNCTION_DECL is documented to be DECL_THIS_STATIC, which should only be 
set on the static member.

> 5. I am pretty sure this is fine for now, but if xobj member functions
> ever were to support virtual/override capabilities, then it would be a
> problem. Is my understanding correct, or is there some other reason
> that iobj member functions have a different value here?

It is surprising that an iobj memfn would have a different DECL_ALIGN, 
but it shouldn't be a problem; the vtables only rely on alignment being 
at least 2.

> There are also some differences in the arg param in
> cp_build_addr_expr_1 that concerns me, but most of them are reflected
> in the differences I have already noted. I had wanted to include these
> differences as well but I have been spending too much time staring at
> it, it's no longer productive. In short, the indirect_ref node for xobj
> member functions has reference_to_this set, while iobj member functions
> do not.

That's a result of difference 3.

> The baselink binfo field has the private flag set for xobj
> member functions, iobj member functions does not.

TREE_PRIVATE on a binfo is part of BINFO_ACCESS, which isn't a fixed 
value, but gets updated during member search.  Perhaps the differences 
in consideration of conversion to a base led to it being set or cleared 
differently?  I wouldn't worry too much about it unless you see 
differences in access control.

> I've spent too much time on this write up, so I am calling it here, it
> wasn't all a waste of time because half of what I was doing here are
> things I was going to need to do anyway at least. I still feel like I
> spent too much time on it. Hopefully it's of some value for me/others
> later.

I hope my responses are helpful as well.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-03  3:04                                             ` Jason Merrill
@ 2023-11-03  4:44                                               ` waffl3x
  2023-11-03 18:05                                                 ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-03  4:44 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

> > That leaves 2, 4, and 5.
> > 
> > 2. I am pretty sure xobj functions should have the struct they are a
> > part of recorded as the method basetype member. I have already checked
> > that function_type and method_type are the same node type under the
> > hood and it does appear to be, so it should be trivial to set it.
> > However I do have to wonder why static member functions don't set it,
> > it seems to be that it would be convenient to use the same field. Can
> > you provide any insight into that?
> 
> 
> method basetype is only for METHOD_TYPE; if you want the containing type
> of the function, that's DECL_CONTEXT.

-- gcc/tree.h:530
#define FUNC_OR_METHOD_CHECK(T)	TREE_CHECK2 (T, FUNCTION_TYPE, METHOD_TYPE)
-- gcc/tree.h:2518
#define TYPE_METHOD_BASETYPE(NODE)			\
  (FUNC_OR_METHOD_CHECK (NODE)->type_non_common.maxval)

The code doesn't seem to reflect that, perhaps since the underlying
node type is the same (as far as I can tell, they both inherit from
tree_type_non_common) it wasn't believed to be necessary.

Upon looking at DECL_CONTEXT though I see it seems you were thinking of
FUNCTION_DECL. I haven't observed DECL_CONTEXT being set for
FUNCTION_DECL nodes though, perhaps it should be? Although it's more
likely that it is being set and I just haven't noticed, but if that's
the case I'll have to make a note to make sure it is being set for xobj
member functions.

I was going to say that this seems like a redundancy, but I realized
that the type of a member function pointer is tied to the struct, so it
actually ends up relevant for METHOD_TYPE nodes. I would hazard a guess
that when forming member function pointers the FUNCTION_DECL node was
not as easily accessed. If I remember correctly that is not the case
right now so it might be worthwhile to refactor away from
TYPE_METHOD_BASETYPE and replace uses of it with DECL_CONTEXT.

I'm getting ahead of myself though, I'll stop here and avoid going on
too much of a refactoring tangent. I do want this patch to make it into
GCC14 after all.

> > 4. I have no comment here, but it does concern me since I don't
> > understand it at all.
> 
> 
> In the list near the top of cp-tree.h, DECL_LANG_FLAG_6 for a
> FUNCTION_DECL is documented to be DECL_THIS_STATIC, which should only be
> set on the static member.

Right, I'll try to remember to check this area in the future, but yeah
that tracks because I did remove that flag. Removing that flag just so
happened to be the start of this saga of bug fixes but alas, it had to
be done.

> > 5. I am pretty sure this is fine for now, but if xobj member functions
> > ever were to support virtual/override capabilities, then it would be a
> > problem. Is my understanding correct, or is there some other reason
> > that iobj member functions have a different value here?
> 
> 
> It is surprising that an iobj memfn would have a different DECL_ALIGN,
> but it shouldn't be a problem; the vtables only rely on alignment being
> at least 2.

I'll put a note for myself to look into it in the future, it's an
oddity at minimum and oddities interest me :^).

> > There are also some differences in the arg param in
> > cp_build_addr_expr_1 that concerns me, but most of them are reflected
> > in the differences I have already noted. I had wanted to include these
> > differences as well but I have been spending too much time staring at
> > it, it's no longer productive. In short, the indirect_ref node for xobj
> > member functions has reference_to_this set, while iobj member functions
> > do not.
> 
> 
> That's a result of difference 3.

Okay, makes sense, I'm mildly concerned about any possible side effects
this might have since we have a function_type node suddenly going
through execution paths that only method_type went through before. The
whole "reference_to_this" "pointer_to_this" thing is a little confusing
because I'm pretty sure that doesn't refer to the actual `this` object
argument or parameter since I've seen it all over the place. Hopefully
it's benign.

> > The baselink binfo field has the private flag set for xobj
> > member functions, iobj member functions does not.
> 
> 
> TREE_PRIVATE on a binfo is part of BINFO_ACCESS, which isn't a fixed
> value, but gets updated during member search. Perhaps the differences
> in consideration of conversion to a base led to it being set or cleared
> differently? I wouldn't worry too much about it unless you see
> differences in access control.

Unfortunately I don't have any tests for private/public access yet,
it's one of the area's I've neglected. Unfortunately I probably won't
put too much effort into writing TOO many more right now as it takes up
a lot of my time. I still have to clean up the ones I currently have
and I have a few I wanted to write that are not yet written.

> > I've spent too much time on this write up, so I am calling it here, it
> > wasn't all a waste of time because half of what I was doing here are
> > things I was going to need to do anyway at least. I still feel like I
> > spent too much time on it. Hopefully it's of some value for me/others
> > later.
> 
> 
> I hope my responses are helpful as well.

Very much so, thank you!

An update on the regression testing, I had one test fail comparing
against commit a4d2b108cf234e7893322a32a7956ca24e283b05 (GCC13) and I'm
not sure if I need to be concerned about it.
libgomp.c++/../libgomp.c-c++-common/for-16.c execution test

I'm going to be starting tests for my patch against trunk now, once
that is finished I should be ready to format a patch for review.

That's all for now, thanks again.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-03  4:44                                               ` waffl3x
@ 2023-11-03 18:05                                                 ` Jason Merrill
  2023-11-04  6:40                                                   ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-03 18:05 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/3/23 00:44, waffl3x wrote:
>>> That leaves 2, 4, and 5.
>>>
>>> 2. I am pretty sure xobj functions should have the struct they are a
>>> part of recorded as the method basetype member. I have already checked
>>> that function_type and method_type are the same node type under the
>>> hood and it does appear to be, so it should be trivial to set it.
>>> However I do have to wonder why static member functions don't set it,
>>> it seems to be that it would be convenient to use the same field. Can
>>> you provide any insight into that?
>>
>>
>> method basetype is only for METHOD_TYPE; if you want the containing type
>> of the function, that's DECL_CONTEXT.
> 
> -- gcc/tree.h:530
> #define FUNC_OR_METHOD_CHECK(T)	TREE_CHECK2 (T, FUNCTION_TYPE, METHOD_TYPE)
> -- gcc/tree.h:2518
> #define TYPE_METHOD_BASETYPE(NODE)			\
>    (FUNC_OR_METHOD_CHECK (NODE)->type_non_common.maxval)
> 
> The code doesn't seem to reflect that, perhaps since the underlying
> node type is the same (as far as I can tell, they both inherit from
> tree_type_non_common) it wasn't believed to be necessary.

Curious.  It might also be a remnant of how older code dealt with how 
sometimes a member function changes between FUNCTION_TYPE and 
METHOD_TYPE during parsing.

> Upon looking at DECL_CONTEXT though I see it seems you were thinking of
> FUNCTION_DECL. I haven't observed DECL_CONTEXT being set for
> FUNCTION_DECL nodes though, perhaps it should be? Although it's more
> likely that it is being set and I just haven't noticed, but if that's
> the case I'll have to make a note to make sure it is being set for xobj
> member functions.

I would certainly expect it to be getting set already.

> I was going to say that this seems like a redundancy, but I realized
> that the type of a member function pointer is tied to the struct, so it
> actually ends up relevant for METHOD_TYPE nodes. I would hazard a guess
> that when forming member function pointers the FUNCTION_DECL node was
> not as easily accessed. If I remember correctly that is not the case
> right now so it might be worthwhile to refactor away from
> TYPE_METHOD_BASETYPE and replace uses of it with DECL_CONTEXT.

For a pointer-to-member, the class type is part of the type, so trying 
to remove it from the type doesn't sound like an improvement to me. 
Specifically, TYPE_PTRMEM_CLASS_TYPE refers to TYPE_METHOD_BASETYPE for 
a pointer to member function.

> I'm getting ahead of myself though, I'll stop here and avoid going on
> too much of a refactoring tangent. I do want this patch to make it into
> GCC14 after all.

Good plan.  :)

>>> 4. I have no comment here, but it does concern me since I don't
>>> understand it at all.
>>
>> In the list near the top of cp-tree.h, DECL_LANG_FLAG_6 for a
>> FUNCTION_DECL is documented to be DECL_THIS_STATIC, which should only be
>> set on the static member.
> 
> Right, I'll try to remember to check this area in the future, but yeah
> that tracks because I did remove that flag. Removing that flag just so
> happened to be the start of this saga of bug fixes but alas, it had to
> be done.
> 
>>> 5. I am pretty sure this is fine for now, but if xobj member functions
>>> ever were to support virtual/override capabilities, then it would be a
>>> problem. Is my understanding correct, or is there some other reason
>>> that iobj member functions have a different value here?
>>
>> It is surprising that an iobj memfn would have a different DECL_ALIGN,
>> but it shouldn't be a problem; the vtables only rely on alignment being
>> at least 2.
> 
> I'll put a note for myself to look into it in the future, it's an
> oddity at minimum and oddities interest me :^).
> 
>>> There are also some differences in the arg param in
>>> cp_build_addr_expr_1 that concerns me, but most of them are reflected
>>> in the differences I have already noted. I had wanted to include these
>>> differences as well but I have been spending too much time staring at
>>> it, it's no longer productive. In short, the indirect_ref node for xobj
>>> member functions has reference_to_this set, while iobj member functions
>>> do not.
>>
>> That's a result of difference 3.
> 
> Okay, makes sense, I'm mildly concerned about any possible side effects
> this might have since we have a function_type node suddenly going
> through execution paths that only method_type went through before. The
> whole "reference_to_this" "pointer_to_this" thing is a little confusing
> because I'm pretty sure that doesn't refer to the actual `this` object
> argument or parameter since I've seen it all over the place. Hopefully
> it's benign.

Yes, "pointer_to_this" is just a cache of the type that is a pointer to 
the type you're looking at.  You are correct that it has nothing to do 
with the C++ 'this'.

>>> The baselink binfo field has the private flag set for xobj
>>> member functions, iobj member functions does not.
>>
>> TREE_PRIVATE on a binfo is part of BINFO_ACCESS, which isn't a fixed
>> value, but gets updated during member search. Perhaps the differences
>> in consideration of conversion to a base led to it being set or cleared
>> differently? I wouldn't worry too much about it unless you see
>> differences in access control.
> 
> Unfortunately I don't have any tests for private/public access yet,
> it's one of the area's I've neglected. Unfortunately I probably won't
> put too much effort into writing TOO many more right now as it takes up
> a lot of my time. I still have to clean up the ones I currently have
> and I have a few I wanted to write that are not yet written.

Makes sense.  I wouldn't expect access control to need specific changes.

>>> I've spent too much time on this write up, so I am calling it here, it
>>> wasn't all a waste of time because half of what I was doing here are
>>> things I was going to need to do anyway at least. I still feel like I
>>> spent too much time on it. Hopefully it's of some value for me/others
>>> later.
>>
>> I hope my responses are helpful as well.
> 
> Very much so, thank you!
> 
> An update on the regression testing, I had one test fail comparing
> against commit a4d2b108cf234e7893322a32a7956ca24e283b05 (GCC13) and I'm
> not sure if I need to be concerned about it.
> libgomp.c++/../libgomp.c-c++-common/for-16.c execution test

No, that test has been pretty flaky for me, you can ignore it.

> I'm going to be starting tests for my patch against trunk now, once
> that is finished I should be ready to format a patch for review.

Great!

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-03 18:05                                                 ` Jason Merrill
@ 2023-11-04  6:40                                                   ` waffl3x
  2023-11-09 18:44                                                     ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-04  6:40 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

I'm unfortunately going down a rabbit hole again.

--function.h:608
```
/* If pointers to member functions use the least significant bit to
   indicate whether a function is virtual, ensure a pointer
   to this function will have that bit clear.  */
#define MINIMUM_METHOD_BOUNDARY \
  ((TARGET_PTRMEMFUNC_VBIT_LOCATION == ptrmemfunc_vbit_in_pfn)	     \
   ? MAX (FUNCTION_BOUNDARY, 2 * BITS_PER_UNIT) : FUNCTION_BOUNDARY)
```
--decl.cc:10400 grokfndecl
```
  if (TREE_CODE (type) == METHOD_TYPE)
    {
      tree parm = build_this_parm (decl, type, quals);
      DECL_CHAIN (parm) = parms;
      parms = parm;

      /* Allocate space to hold the vptr bit if needed.  */
      SET_DECL_ALIGN (decl, MINIMUM_METHOD_BOUNDARY);
    }
```
The good news is I think I found where the alignment is being set, so
it can be addressed later if desired.

I stumbled upon this while cleaning up the patch, grokfndecl is just so
full of cruft it's crazy hard to reason about. There's more than one
block that I am near certain is completely dead code. I would like to
just ignore them for now but some of them unfortunately pertain to xobj
functions. I just don't feel good about putting in any hacks, but to
really get any modifications in here correct it would need to be
refactored much more than I should be doing in this patch.

Here's another example that I'm not sure how I want to address it.

~decl.cc:10331 grokfndecl
```
  int staticp = ctype && TREE_CODE (type) == FUNCTION_TYPE;
```
~decl.cc:10506 grokfndecl
```
  /* If this decl has namespace scope, set that up.  */
  if (in_namespace)
    set_decl_namespace (decl, in_namespace, friendp);
  else if (ctype)
    DECL_CONTEXT (decl) = ctype;
  else
    DECL_CONTEXT (decl) = FROB_CONTEXT (current_decl_namespace ());
```
And just a few lines down;
~decl.cc:10529
```
  /* Should probably propagate const out from type to decl I bet (mrs).  */
  if (staticp)
    {
      DECL_STATIC_FUNCTION_P (decl) = 1;
      DECL_CONTEXT (decl) = ctype;
    }
```

If staticp is true, ctype must have been non-null, and if ctype is
non-null, the context for decl should have been set in the second
block. So why was the code in the second block added?

commit f3665bd1111c1799c0421490b5e655f977570354
Author: Nathan Sidwell <nathan@acm.org>
Date:   Tue Jul 28 08:57:36 2020 -0700

    c++: Set more DECL_CONTEXTs
    
    I discovered we were not setting DECL_CONTEXT in a few cases, and
    grokfndecl's control flow wasn't making it clear that we were doing it
    in all cases.
    
            gcc/cp/
            * cp-gimplify.c (cp_genericize_r): Set IMPORTED_DECL's context.
            * cp-objcp-common.c (cp_pushdecl): Set decl's context.
            * decl.c (grokfndecl): Make DECL_CONTEXT setting clearer.

According to the commit, it was because it was not clear, which quite
frankly I can agree to, it just wasn't determined that the code below
is redundantly setting the context so it wasn't removed.

This puts me in a dilemma though, do I put another condition in that
code block for the xobj case even though the code is nearly dead? Or do
I give it a minor refactor for it to make a little more sense? If I add
to the code I feel like it's just going to add to the problem, while if
I give it a minor refactor it still won't look great and has a greater
chance of breaking something.

In this case I'm going to risk refactoring it, staticp is only used in
that 1 place so I will just rip it out. I am not concerned with decl's
type spontaneously changing to something that is not FUNCTION_TYPE, and
if it did I think there are bigger problems afoot.

I guess I'll know if I went too far with the refactoring when the patch
reaches you, do let me know about this one specifically though because
it took up a lot of my time trying to decide how to address it.

> > The code doesn't seem to reflect that, perhaps since the underlying
> > node type is the same (as far as I can tell, they both inherit from
> > tree_type_non_common) it wasn't believed to be necessary.
> 
> 
> Curious. It might also be a remnant of how older code dealt with how
> sometimes a member function changes between FUNCTION_TYPE and
> METHOD_TYPE during parsing.

Sounds plausible.

> > Upon looking at DECL_CONTEXT though I see it seems you were thinking of
> > FUNCTION_DECL. I haven't observed DECL_CONTEXT being set for
> > FUNCTION_DECL nodes though, perhaps it should be? Although it's more
> > likely that it is being set and I just haven't noticed, but if that's
> > the case I'll have to make a note to make sure it is being set for xobj
> > member functions.
> 
> 
> I would certainly expect it to be getting set already.

This being on my mind is partially what sent me down the rabbit hole
above, but yeah, it seems like it is.

> > I was going to say that this seems like a redundancy, but I realized
> > that the type of a member function pointer is tied to the struct, so it
> > actually ends up relevant for METHOD_TYPE nodes. I would hazard a guess
> > that when forming member function pointers the FUNCTION_DECL node was
> > not as easily accessed. If I remember correctly that is not the case
> > right now so it might be worthwhile to refactor away from
> > TYPE_METHOD_BASETYPE and replace uses of it with DECL_CONTEXT.
> 
> 
> For a pointer-to-member, the class type is part of the type, so trying
> to remove it from the type doesn't sound like an improvement to me.
> Specifically, TYPE_PTRMEM_CLASS_TYPE refers to TYPE_METHOD_BASETYPE for
> a pointer to member function.

I suppose it's not a good idea given that other decl nodes use the same
field, but redundancies do feel icky to me. It makes stuff way harder
to reason about for me.

> > 
> > Unfortunately I don't have any tests for private/public access yet,
> > it's one of the area's I've neglected. Unfortunately I probably won't
> > put too much effort into writing TOO many more right now as it takes up
> > a lot of my time. I still have to clean up the ones I currently have
> > and I have a few I wanted to write that are not yet written.
> 
> 
> Makes sense. I wouldn't expect access control to need specific changes.

I think I saw something that concerned me regarding this in grokfndecl,
but I agree that it shouldn't need specific changes, so I'm going to
assume I imagined it unless problems start to pop up :).

> No, that test has been pretty flaky for me, you can ignore it.

Noted.

> > I'm going to be starting tests for my patch against trunk now, once
> > that is finished I should be ready to format a patch for review.
> 
> 
> Great!

All tests seemed to pass when applied to GCC14, but the results did
something funny where it said tests disappeared and new tests appeared
and passed. The ones that disappeared and the new ones that appeared
looked like they were identical so I'm not worrying about it. Just
mentioning it in case this is something I do need to look into.

Back to work now, thank you as always.

Alex



^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-04  6:40                                                   ` waffl3x
@ 2023-11-09 18:44                                                     ` Jason Merrill
  2023-11-10  4:24                                                       ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-09 18:44 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/4/23 02:40, waffl3x wrote:
> I'm unfortunately going down a rabbit hole again.
> 
> --function.h:608
> ```
> /* If pointers to member functions use the least significant bit to
>     indicate whether a function is virtual, ensure a pointer
>     to this function will have that bit clear.  */
> #define MINIMUM_METHOD_BOUNDARY \
>    ((TARGET_PTRMEMFUNC_VBIT_LOCATION == ptrmemfunc_vbit_in_pfn)	     \
>     ? MAX (FUNCTION_BOUNDARY, 2 * BITS_PER_UNIT) : FUNCTION_BOUNDARY)
> ```

So yes, it was for PMFs using the low bit of the pointer to indicate a 
virtual member function.  Since an xob memfn can't be virtual, it's 
correct for them to have the same alignment as a static memfn.

> I stumbled upon this while cleaning up the patch, grokfndecl is just so
> full of cruft it's crazy hard to reason about. There's more than one
> block that I am near certain is completely dead code. I would like to
> just ignore them for now but some of them unfortunately pertain to xobj
> functions. I just don't feel good about putting in any hacks, but to
> really get any modifications in here correct it would need to be
> refactored much more than I should be doing in this patch.
> 
> Here's another example that I'm not sure how I want to address it.
> 
> ~decl.cc:10331 grokfndecl
> ```
>    int staticp = ctype && TREE_CODE (type) == FUNCTION_TYPE;
> ```
> ~decl.cc:10506 grokfndecl
> ```
>    /* If this decl has namespace scope, set that up.  */
>    if (in_namespace)
>      set_decl_namespace (decl, in_namespace, friendp);
>    else if (ctype)
>      DECL_CONTEXT (decl) = ctype;
>    else
>      DECL_CONTEXT (decl) = FROB_CONTEXT (current_decl_namespace ());
> ```
> And just a few lines down;
> ~decl.cc:10529
> ```
>    /* Should probably propagate const out from type to decl I bet (mrs).  */
>    if (staticp)
>      {
>        DECL_STATIC_FUNCTION_P (decl) = 1;
>        DECL_CONTEXT (decl) = ctype;
>      }
> ```
> 
> If staticp is true, ctype must have been non-null, and if ctype is
> non-null, the context for decl should have been set in the second
> block. So why was the code in the second block added?
>
> commit f3665bd1111c1799c0421490b5e655f977570354
> Author: Nathan Sidwell <nathan@acm.org>
> Date:   Tue Jul 28 08:57:36 2020 -0700
> 
>      c++: Set more DECL_CONTEXTs
>      
>      I discovered we were not setting DECL_CONTEXT in a few cases, and
>      grokfndecl's control flow wasn't making it clear that we were doing it
>      in all cases.
>      
>              gcc/cp/
>              * cp-gimplify.c (cp_genericize_r): Set IMPORTED_DECL's context.
>              * cp-objcp-common.c (cp_pushdecl): Set decl's context.
>              * decl.c (grokfndecl): Make DECL_CONTEXT setting clearer.
> 
> According to the commit, it was because it was not clear, which quite
> frankly I can agree to, it just wasn't determined that the code below
> is redundantly setting the context so it wasn't removed.
> 
> This puts me in a dilemma though, do I put another condition in that
> code block for the xobj case even though the code is nearly dead? Or do
> I give it a minor refactor for it to make a little more sense? If I add
> to the code I feel like it's just going to add to the problem, while if
> I give it a minor refactor it still won't look great and has a greater
> chance of breaking something.
> 
> In this case I'm going to risk refactoring it, staticp is only used in
> that 1 place so I will just rip it out. I am not concerned with decl's
> type spontaneously changing to something that is not FUNCTION_TYPE, and
> if it did I think there are bigger problems afoot.
> 
> I guess I'll know if I went too far with the refactoring when the patch
> reaches you, do let me know about this one specifically though because
> it took up a lot of my time trying to decide how to address it.

Removing the redundant DECL_CONTEXT setting seems appropriate, and 
changing how staticp is handled to reflect that xobfns can also have 
FUNCTION_TYPE.

> All tests seemed to pass when applied to GCC14, but the results did
> something funny where it said tests disappeared and new tests appeared
> and passed. The ones that disappeared and the new ones that appeared
> looked like they were identical so I'm not worrying about it. Just
> mentioning it in case this is something I do need to look into.

That doesn't sound like a problem, but I'm curious about the specific 
output you're seeing.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-09 18:44                                                     ` Jason Merrill
@ 2023-11-10  4:24                                                       ` waffl3x
  2023-11-10 23:12                                                         ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-10  4:24 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 5588 bytes --]


> > I'm unfortunately going down a rabbit hole again.
> > 
> > --function.h:608
> > `/* If pointers to member functions use the least significant bit to indicate whether a function is virtual, ensure a pointer to this function will have that bit clear. */ #define MINIMUM_METHOD_BOUNDARY \\ ((TARGET_PTRMEMFUNC_VBIT_LOCATION == ptrmemfunc_vbit_in_pfn) \\ ? MAX (FUNCTION_BOUNDARY, 2 * BITS_PER_UNIT) : FUNCTION_BOUNDARY)`
> 
> 
> So yes, it was for PMFs using the low bit of the pointer to indicate a
> virtual member function. Since an xob memfn can't be virtual, it's
> correct for them to have the same alignment as a static memfn.

Is it worth considering whether we want to support virtual xobj member
functions in the future? If that were the case would it be better if we
aligned things a little differently here? Or might it be better if we
wanted to support it as an extension to just effectively translate the
declaration back to one that is a METHOD_TYPE? I imagine this would be
the best solution for non-standard support of the syntax. We would
simply have to forbid by-value and conversion semantics and on the
user's side they would get consistent syntax.

However, this flies in the face of the defective/contradictory spec for
virtual function overrides. So I'm not really sure whether we would
want to do this. I just want to raise the question before we lock in
the alignment, if pushing the patch locks it in that is, I'm not really
sure if it needs to be stable or not.

> > I stumbled upon this while cleaning up the patch, grokfndecl is just so
> > full of cruft it's crazy hard to reason about. There's more than one
> > block that I am near certain is completely dead code. I would like to
> > just ignore them for now but some of them unfortunately pertain to xobj
> > functions. I just don't feel good about putting in any hacks, but to
> > really get any modifications in here correct it would need to be
> > refactored much more than I should be doing in this patch.
> > 
> > Here's another example that I'm not sure how I want to address it.
> > 
> > :10331~decl.cc grokfndecl
> > `int staticp = ctype && TREE_CODE (type) == FUNCTION_TYPE;`
> > :10506~decl.cc grokfndecl
> > `/* If this decl has namespace scope, set that up. */ if (in_namespace) set_decl_namespace (decl, in_namespace, friendp); else if (ctype) DECL_CONTEXT (decl) = ctype; else DECL_CONTEXT (decl) = FROB_CONTEXT (current_decl_namespace ());`
> > And just a few lines down;
> > :10529~decl.cc
> > `/* Should probably propagate const out from type to decl I bet (mrs). */ if (staticp) { DECL_STATIC_FUNCTION_P (decl) = 1; DECL_CONTEXT (decl) = ctype; }`
> > 
> > If staticp is true, ctype must have been non-null, and if ctype is
> > non-null, the context for decl should have been set in the second
> > block. So why was the code in the second block added?
> > 
> > commit f3665bd1111c1799c0421490b5e655f977570354
> > Author: Nathan Sidwell nathan@acm.org
> > Date: Tue Jul 28 08:57:36 2020 -0700
> > 
> > c++: Set more DECL_CONTEXTs
> > 
> > I discovered we were not setting DECL_CONTEXT in a few cases, and
> > grokfndecl's control flow wasn't making it clear that we were doing it
> > in all cases.
> > 
> > gcc/cp/
> > * cp-gimplify.c (cp_genericize_r): Set IMPORTED_DECL's context.
> > * cp-objcp-common.c (cp_pushdecl): Set decl's context.
> > * decl.c (grokfndecl): Make DECL_CONTEXT setting clearer.
> > 
> > According to the commit, it was because it was not clear, which quite
> > frankly I can agree to, it just wasn't determined that the code below
> > is redundantly setting the context so it wasn't removed.
> > 
> > This puts me in a dilemma though, do I put another condition in that
> > code block for the xobj case even though the code is nearly dead? Or do
> > I give it a minor refactor for it to make a little more sense? If I add
> > to the code I feel like it's just going to add to the problem, while if
> > I give it a minor refactor it still won't look great and has a greater
> > chance of breaking something.
> > 
> > In this case I'm going to risk refactoring it, staticp is only used in
> > that 1 place so I will just rip it out. I am not concerned with decl's
> > type spontaneously changing to something that is not FUNCTION_TYPE, and
> > if it did I think there are bigger problems afoot.
> > 
> > I guess I'll know if I went too far with the refactoring when the patch
> > reaches you, do let me know about this one specifically though because
> > it took up a lot of my time trying to decide how to address it.
> 
> 
> Removing the redundant DECL_CONTEXT setting seems appropriate, and
> changing how staticp is handled to reflect that xobfns can also have
> FUNCTION_TYPE.

I removed static_p as it was only used in that one case, I'm pretty
happy with the resulting code but I saw you replied on the patch as
well so I'll see if you commented on it in the review and address your
thoughts there.

> > All tests seemed to pass when applied to GCC14, but the results did
> > something funny where it said tests disappeared and new tests appeared
> > and passed. The ones that disappeared and the new ones that appeared
> > looked like they were identical so I'm not worrying about it. Just
> > mentioning it in case this is something I do need to look into.
> 
> 
> That doesn't sound like a problem, but I'm curious about the specific
> output you're seeing.
> 
> Jason

I've attached a few test result comparisons so you can take a look.

Alex

[-- Attachment #2: results1 --]
[-- Type: application/octet-stream, Size: 10292 bytes --]

# Comparing directories
## Dir1=/home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/: 8 sum files
## Dir2=/home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/: 8 sum files

# Comparing 8 common sum files
## /bin/sh /home/waffl3x/projects/gcc-dev/git-repo/contrib/compare_tests  /tmp/gxx-sum1.132820 /tmp/gxx-sum2.132820
Tests that now work, but didn't before (1 tests):

libgomp.fortran/nestedfn5.f90   -O0  execution test

New tests that FAIL (13 tests):

g++.dg/cpp23/explicit-obj-by-value1.C  -std=c++23 execution test
g++.dg/cpp23/explicit-obj-by-value1.C  -std=c++26 execution test
g++.dg/cpp23/explicit-obj-by-value2.C  -std=c++23 execution test
g++.dg/cpp23/explicit-obj-by-value2.C  -std=c++26 execution test
g++.dg/cpp23/explicit-obj-by-value3.C  -std=c++23 execution test
g++.dg/cpp23/explicit-obj-by-value3.C  -std=c++26 execution test
g++.dg/cpp23/explicit-obj-lambda1.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-lambda1.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-requires-mem.C  -std=gnu++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-requires-mem.C  -std=gnu++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C  -std=c++26 (test for excess errors)
libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test

New tests that PASS (48 tests):

g++.dg/cpp23/explicit-obj-basic1.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-basic1.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-basic2.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-basic2.C  -std=c++23 execution test
g++.dg/cpp23/explicit-obj-basic2.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-basic2.C  -std=c++26 execution test
g++.dg/cpp23/explicit-obj-by-value1.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value1.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value2.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value2.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value3.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value3.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++23  (test for errors, line 17)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++23  (test for errors, line 18)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++26  (test for errors, line 17)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++26  (test for errors, line 18)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-arrow.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-arrow.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-assignment.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-assignment.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-call.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-call.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-subscript.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-subscript.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C  -std=c++26 (test for excess errors)
libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/throwdown.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)

Old tests that passed, that have disappeared (18 tests): (Eeek!)

libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/throwdown.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)

Old tests that failed, that have disappeared (1 tests): (Eeek!)

libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test

## Differences found: 
# 1 differences in 8 common sum files found

[-- Attachment #3: results0 --]
[-- Type: application/octet-stream, Size: 6951 bytes --]

# Comparing directories
## Dir1=/home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/: 8 sum files
## Dir2=/home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/: 8 sum files

# Comparing 8 common sum files
## /bin/sh /home/waffl3x/projects/gcc-dev/git-repo/contrib/compare_tests  /tmp/gxx-sum1.155292 /tmp/gxx-sum2.155292
Tests that now work, but didn't before (1 tests):

libgomp.fortran/nestedfn5.f90   -O0  execution test

New tests that FAIL (1 tests):

libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test

New tests that PASS (18 tests):

libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/throwdown.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)

Old tests that passed, that have disappeared (18 tests): (Eeek!)

libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/throwdown.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)

Old tests that failed, that have disappeared (1 tests): (Eeek!)

libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test

## Differences found: 
# 1 differences in 8 common sum files found

[-- Attachment #4: results2 --]
[-- Type: application/octet-stream, Size: 49129 bytes --]

# Comparing directories
## Dir1=/home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/: 8 sum files
## Dir2=/home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/: 8 sum files

# Comparing 8 common sum files
## /bin/sh /home/waffl3x/projects/gcc-dev/git-repo/contrib/compare_tests  /tmp/gxx-sum1.1972547 /tmp/gxx-sum2.1972547
Tests that now work, but didn't before (1 tests):

libgomp.fortran/nestedfn5.f90   -O0  execution test

New tests that FAIL (39 tests):

g++.dg/cpp23/explicit-obj-by-value1.C  -std=c++23 execution test
g++.dg/cpp23/explicit-obj-by-value1.C  -std=c++26 execution test
g++.dg/cpp23/explicit-obj-by-value2.C  -std=c++23 execution test
g++.dg/cpp23/explicit-obj-by-value2.C  -std=c++26 execution test
g++.dg/cpp23/explicit-obj-by-value3.C  -std=c++23 execution test
g++.dg/cpp23/explicit-obj-by-value3.C  -std=c++26 execution test
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for errors, line 53)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for errors, line 56)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for errors, line 59)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for warnings, line 52)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for warnings, line 55)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for warnings, line 58)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for errors, line 13)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for errors, line 16)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for errors, line 19)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for warnings, line 12)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for warnings, line 15)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for warnings, line 18)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for errors, line 53)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for errors, line 56)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for errors, line 59)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for warnings, line 52)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for warnings, line 55)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of iobj member functions without a ref qualifier as xobj member functions is known to be broken (test for warnings, line 58)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for errors, line 13)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for errors, line 16)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for errors, line 19)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for warnings, line 12)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for warnings, line 15)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 detecting redeclarations of xobj member functions as iobj member functions without a ref qualifier is known to be broken (test for warnings, line 18)
g++.dg/cpp23/explicit-obj-diagnostics7.C  -std=c++23 disallowing address of unqualified explicit object member function is not implemented yet (test for errors, line 22)
g++.dg/cpp23/explicit-obj-diagnostics7.C  -std=c++26 disallowing address of unqualified explicit object member function is not implemented yet (test for errors, line 22)
g++.dg/cpp23/explicit-obj-lambda1.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-lambda1.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-requires-mem.C  -std=gnu++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-requires-mem.C  -std=gnu++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C  -std=c++26 (test for excess errors)
libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test

New tests that PASS (458 tests):

g++.dg/cpp23/explicit-obj-basic1.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-basic1.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-basic2.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-basic2.C  -std=c++23 execution test
g++.dg/cpp23/explicit-obj-basic2.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-basic2.C  -std=c++26 execution test
g++.dg/cpp23/explicit-obj-by-value1.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value1.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value2.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value2.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value3.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value3.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++23  (test for errors, line 17)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++23  (test for errors, line 18)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++26  (test for errors, line 17)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++26  (test for errors, line 18)
g++.dg/cpp23/explicit-obj-by-value4.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-A.C  -std=c++23  (test for bogus messages, line 5)
g++.dg/cpp23/explicit-obj-cxx-dialect-A.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-A.C  -std=c++26  (test for bogus messages, line 5)
g++.dg/cpp23/explicit-obj-cxx-dialect-A.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-B.C  -std=gnu++14  (test for errors, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-B.C  -std=gnu++14 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-B.C  -std=gnu++17  (test for errors, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-B.C  -std=gnu++17 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-B.C  -std=gnu++20  (test for errors, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-B.C  -std=gnu++20 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-B.C  -std=gnu++98  (test for errors, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-B.C  -std=gnu++98 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-C.C  -std=gnu++14  (test for warnings, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-C.C  -std=gnu++14 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-C.C  -std=gnu++17  (test for warnings, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-C.C  -std=gnu++17 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-C.C  -std=gnu++20  (test for warnings, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-C.C  -std=gnu++20 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-C.C  -std=gnu++98  (test for warnings, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-C.C  -std=gnu++98 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-D.C  -std=gnu++14  (test for bogus messages, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-D.C  -std=gnu++14 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-D.C  -std=gnu++17  (test for bogus messages, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-D.C  -std=gnu++17 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-D.C  -std=gnu++20  (test for bogus messages, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-D.C  -std=gnu++20 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-D.C  -std=gnu++98  (test for bogus messages, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-D.C  -std=gnu++98 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-E.C  -std=gnu++14  (test for bogus messages, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-E.C  -std=gnu++14 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-E.C  -std=gnu++17  (test for bogus messages, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-E.C  -std=gnu++17 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-E.C  -std=gnu++20  (test for bogus messages, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-E.C  -std=gnu++20 (test for excess errors)
g++.dg/cpp23/explicit-obj-cxx-dialect-E.C  -std=gnu++98  (test for bogus messages, line 6)
g++.dg/cpp23/explicit-obj-cxx-dialect-E.C  -std=gnu++98 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 10)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 100)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 101)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 103)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 104)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 105)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 106)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 107)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 108)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 109)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 11)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 110)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 111)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 112)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 113)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 115)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 116)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 117)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 118)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 119)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 12)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 120)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 121)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 122)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 123)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 124)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 125)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 127)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 128)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 129)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 13)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 130)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 131)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 132)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 133)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 134)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 135)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 136)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 137)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 14)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 15)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 16)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 17)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 19)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 20)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 21)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 22)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 23)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 24)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 25)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 26)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 27)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 28)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 29)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 31)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 32)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 33)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 34)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 35)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 36)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 37)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 38)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 39)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 40)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 41)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 43)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 44)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 45)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 46)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 47)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 48)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 49)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 50)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 51)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 52)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 53)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 55)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 56)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 57)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 58)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 59)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 60)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 61)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 62)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 63)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 64)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 65)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 67)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 68)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 69)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 7)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 70)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 71)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 72)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 73)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 74)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 75)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 76)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 77)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 79)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 8)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 80)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 81)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 82)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 83)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 84)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 85)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 86)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 87)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 88)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 89)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 9)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 91)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 92)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 93)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 94)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 95)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 96)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 97)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 98)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23  (test for errors, line 99)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 10)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 100)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 101)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 103)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 104)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 105)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 106)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 107)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 108)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 109)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 11)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 110)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 111)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 112)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 113)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 115)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 116)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 117)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 118)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 119)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 12)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 120)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 121)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 122)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 123)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 124)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 125)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 127)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 128)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 129)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 13)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 130)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 131)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 132)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 133)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 134)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 135)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 136)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 137)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 14)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 15)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 16)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 17)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 19)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 20)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 21)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 22)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 23)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 24)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 25)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 26)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 27)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 28)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 29)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 31)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 32)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 33)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 34)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 35)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 36)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 37)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 38)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 39)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 40)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 41)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 43)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 44)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 45)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 46)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 47)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 48)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 49)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 50)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 51)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 52)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 53)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 55)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 56)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 57)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 58)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 59)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 60)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 61)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 62)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 63)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 64)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 65)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 67)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 68)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 69)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 7)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 70)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 71)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 72)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 73)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 74)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 75)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 76)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 77)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 79)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 8)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 80)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 81)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 82)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 83)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 84)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 85)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 86)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 87)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 88)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 89)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 9)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 91)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 92)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 93)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 94)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 95)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 96)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 97)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 98)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26  (test for errors, line 99)
g++.dg/cpp23/explicit-obj-diagnostics1.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  (test for errors, line 24)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  (test for errors, line 25)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  at line 11 (test for errors, line 10)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  at line 12 (test for warnings, line 10)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  at line 17 (test for errors, line 15)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  at line 18 (test for warnings, line 15)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  at line 21 (test for errors, line 20)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  at line 22 (test for warnings, line 20)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  at line 7 (test for errors, line 6)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23  at line 8 (test for warnings, line 6)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  (test for errors, line 24)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  (test for errors, line 25)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  at line 11 (test for errors, line 10)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  at line 12 (test for warnings, line 10)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  at line 17 (test for errors, line 15)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  at line 18 (test for warnings, line 15)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  at line 21 (test for errors, line 20)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  at line 22 (test for warnings, line 20)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  at line 7 (test for errors, line 6)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26  at line 8 (test for warnings, line 6)
g++.dg/cpp23/explicit-obj-diagnostics2.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 10)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 22)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 25)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 28)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 31)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 34)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 37)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 40)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 43)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 50)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 62)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 65)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 68)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 71)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 74)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 77)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 80)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for errors, line 83)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 21)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 24)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 27)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 30)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 33)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 36)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 39)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 42)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 49)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 61)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 64)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 67)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 70)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 73)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 76)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 79)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 82)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23  (test for warnings, line 9)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 10)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 22)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 25)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 28)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 31)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 34)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 37)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 40)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 43)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 50)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 62)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 65)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 68)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 71)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 74)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 77)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 80)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for errors, line 83)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 21)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 24)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 27)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 30)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 33)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 36)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 39)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 42)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 49)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 61)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 64)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 67)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 70)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 73)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 76)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 79)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 82)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26  (test for warnings, line 9)
g++.dg/cpp23/explicit-obj-diagnostics3.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++23  (test for errors, line 10)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++23  (test for errors, line 11)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++23  (test for errors, line 16)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++23  (test for errors, line 19)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++23  (test for errors, line 7)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++23  (test for errors, line 8)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++26  (test for errors, line 10)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++26  (test for errors, line 11)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++26  (test for errors, line 16)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++26  (test for errors, line 19)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++26  (test for errors, line 7)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++26  (test for errors, line 8)
g++.dg/cpp23/explicit-obj-diagnostics4.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics5.C  -std=c++23  (test for errors, line 13)
g++.dg/cpp23/explicit-obj-diagnostics5.C  -std=c++23  at line 7 (test for warnings, line )
g++.dg/cpp23/explicit-obj-diagnostics5.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics5.C  -std=c++26  (test for errors, line 13)
g++.dg/cpp23/explicit-obj-diagnostics5.C  -std=c++26  at line 7 (test for warnings, line )
g++.dg/cpp23/explicit-obj-diagnostics5.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++23  (test for errors, line 11)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++23  (test for errors, line 20)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++23  (test for warnings, line 10)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++23  (test for warnings, line 19)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++23  at line 6 (test for warnings, line )
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++26  (test for errors, line 11)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++26  (test for errors, line 20)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++26  (test for warnings, line 10)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++26  (test for warnings, line 19)
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++26  at line 6 (test for warnings, line )
g++.dg/cpp23/explicit-obj-diagnostics6.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics7.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-diagnostics7.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-arrow.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-arrow.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-assignment.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-assignment.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-call.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-call.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-subscript.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-mem-subscript.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C  -std=c++26 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C  -std=c++23 (test for excess errors)
g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C  -std=c++26 (test for excess errors)
libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/throwdown.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/xobj-patch-testing/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)

Old tests that passed, that have disappeared (18 tests): (Eeek!)

libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-1.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-2.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-3.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-4.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/eh-5.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-pr91488.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/libstdc++-safeexc.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)
libitm.c++/newdelete.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test
libitm.c++/throwdown.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs (test for excess errors)

Old tests that failed, that have disappeared (1 tests): (Eeek!)

libitm.c++/dropref.C -B /home/waffl3x/projects/gcc-dev/bootstrap/other/obj/pristine-gcc14/x86_64-pc-linux-gnu/./libitm/../libstdc++-v3/src/.libs execution test

## Differences found: 
# 1 differences in 8 common sum files found

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-10  4:24                                                       ` waffl3x
@ 2023-11-10 23:12                                                         ` Jason Merrill
  2023-11-11 10:43                                                           ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-10 23:12 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

[combined reply to all three threads]

On 11/9/23 23:24, waffl3x wrote:
> 
>>> I'm unfortunately going down a rabbit hole again.
>>>
>>> --function.h:608
>>> `/* If pointers to member functions use the least significant bit to indicate whether a function is virtual, ensure a pointer to this function will have that bit clear. */ #define MINIMUM_METHOD_BOUNDARY \\ ((TARGET_PTRMEMFUNC_VBIT_LOCATION == ptrmemfunc_vbit_in_pfn) \\ ? MAX (FUNCTION_BOUNDARY, 2 * BITS_PER_UNIT) : FUNCTION_BOUNDARY)`
>>
>>
>> So yes, it was for PMFs using the low bit of the pointer to indicate a
>> virtual member function. Since an xob memfn can't be virtual, it's
>> correct for them to have the same alignment as a static memfn.
> 
> Is it worth considering whether we want to support virtual xobj member
> functions in the future? If that were the case would it be better if we
> aligned things a little differently here? Or might it be better if we
> wanted to support it as an extension to just effectively translate the
> declaration back to one that is a METHOD_TYPE? I imagine this would be
> the best solution for non-standard support of the syntax. We would
> simply have to forbid by-value and conversion semantics and on the
> user's side they would get consistent syntax.
> 
> However, this flies in the face of the defective/contradictory spec for
> virtual function overrides. So I'm not really sure whether we would
> want to do this. I just want to raise the question before we lock in
> the alignment, if pushing the patch locks it in that is, I'm not really
> sure if it needs to be stable or not.

It doesn't need to be stable; we can increase the alignment of decls as 
needed in new code without breaking older code.

>>> All tests seemed to pass when applied to GCC14, but the results did
>>> something funny where it said tests disappeared and new tests appeared
>>> and passed. The ones that disappeared and the new ones that appeared
>>> looked like they were identical so I'm not worrying about it. Just
>>> mentioning it in case this is something I do need to look into.
>>
>> That doesn't sound like a problem, but I'm curious about the specific
>> output you're seeing.
> 
> I've attached a few test result comparisons so you can take a look.

Looks like you're comparing results from different build directories and 
the libitm test wrongly includes the build directory in the test "name". 
  So yeah, just noise.

> 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.

Agreed.

>>> 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.

That sounds like it might cause trouble with

struct A {
    void f(this A);
};

int main()
{
   (&A::f) (A());
}

> 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
> :^).

Right, first_arg is only actually used for the implicit object argument, 
it's just easier to store it separately from the arguments in ().  I'm 
not sure which code you mean is no longer valid?

>>> 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.

Yeah, that could use some refactoring.  IMO ctype should be set iff we 
want to push_nested_class, and the code for setting up 'this' should 
check for METHOD_TYPE instead of referring to ctype.

>>> I had wanted to write about some of my frustrations with trying to
>>> write a test for virtual specifiers and errors/warnings for
>>> shadowing/overloading virtual functions, but I am a bit too tired at
>>> the moment and I don't want to delay getting this up for another night.
>>> In short, the standard does not properly specify the criteria for
>>> overriding functions, which leaves a lot of ambiguity in how exactly we
>>> should be handling these cases.
>>
>> Agreed, this issue came up in the C++ committee meeting today. See
>>
>> https://cplusplus.github.io/CWG/issues/2553.html
>> https://cplusplus.github.io/CWG/issues/2554.html
>>
>> for draft changes to clarify some of these issues.
> 
> Ah I guess I should have read all your responses before sending any
> back instead of going one by one. I ended up writing about this again I
> think.
> 
> struct B {
>     virtual void f() const {}
> };
> 
> struct S0 : B {
>     void f() {}
> };
> 
> struct S1 : B {
>     void f(this S1&) {}
> };
> 
> I had a bit of a debate with a peer, I initially disagreed with the
> standard resolution because it disallows S1's declaration of f, while
> S0's is an overload. I won't bore you with all the details of going
> back and forth about the proposed wording, my feelings are mostly that
> being able to overload something with an iobj member function but not
> being able to with an xobj member function was inconsistent. He argued
> that keeping restrictions high at first and lowering them later is
> better, and overloading virtual functions is already not something you
> should really ever do, so he was in favor of the proposed wording.

Right.  As the author of this proposal said in discussion, "We want very 
liberal overriding for explicit object member functions so that it'll 
blow up."

> In light of our debate, my stance is that we should implement things as
> per the proposed wording. 
> 
> struct B {
>   virtual void foo() const&;
> };
> 
> struct D : B {
>   void foo(this int);
> };
> 
> This would be ill-formed now according to the change in wording. My
> laymans interpretation of the semantics are that, the parameter list is
> the same when the xobj parameter is ignore, so it overrides. And since
> it overrides, and xobj member functions are not allowed to override, it
> is an error.

Yes.

> To be honest, I still don't understand where the wording accounts for
> the qualifiers of B::f, but my friend assured me that it is.

The qualifiers of B::f are part of the object parameter which we're 
ignoring because the derived function is xobj.

> Somewhat related is some warnings I wanted to implement for impossible
> to call by-value xobj member functions. Ones that involve an unrelated
> type get dicey because people could in theory have legitimate use cases
> for that, P0847R7 includes an example of this combining recursive
> lambdas and the overload pattern to create a visitor. However, I think
> it would be reasonable to warn when a by-value xobj member function can
> not be called due to the presence of overloads that take references.
> Off the top of my head I don't recall how many overloads it would take
> to prevent a by-value version from being called, nor have I looked into
> how to implement this.

Hmm, is it possible to make it un-callable?  A function taking A and a 
function taking A&& are ambiguous when called with an rvalue argument, 
the by-value overload isn't hidden.  Similarly, A and A& are ambiguous 
when called with a cv-unqualified lvalue.

> But I do know that redeclarations of xobj member
> functions as iobj member functions (and vice-versa) are not properly
> identified when ref qualifiers are omitted from the corresponding (is
> this the correct term here?) iobj member function.

That would be the code in add_method discussed later in that message.

>>> + /* If */
>>
>> I think this comment doesn't add much. :)
> 
> I wish I remembered what I even meant to put here, oh well, must not
> have been all that important. Oh wait, I remember it now, I was going
> to note that the next element in the chain of declarators being null
> seems to indicate that the declaration is a function type. I will
> probably put that comment in again, it's on the line of commenting
> exactly what the code does but it was cryptic to figure this out, I'm
> not sure if I should lean towards including the comment here or not.

I always lean towards including comments. :)

>>> + /* I can imagine doing a fixit here, suggesting replacing
>>> + this / *this / this-> with &name / name / "name." but it would be
>>> + very difficult to get it perfect and I've been advised against
>>> + making imperfect fixits.
>>> + Perhaps it would be as simple as the replacements listed,
>>> + even if one is move'ing/forward'ing, if the replacement is just
>>> + done in the same place, it will be exactly what the user wants?
>>> + Even if this is true though, there's still a problem of getting the
>>> + context of the expression to find which tokens to replace.
>>> + I would really like for this to be possible though.
>>> + I will decide whether or not to persue this after review. */
>>
>> You could pass the location of 'this' from the parser to
>> finish_this_expr and always replace it with (&name)?
> 
> I've been reluctant to make any changes to parameters, but if you're
> suggesting it I will consider it. Just replacing it with '(&name)'
> seems really mediocre to me though. Yeah, sure, it's always going to be
> syntactically valid but I worry that it wont be semantically correct
> often enough.
> 
> Yeah, considering that I think I will leave it for now until I have
> time to come up with a robust solution. I might still pass in the
> location of the this token though and use error_at instead of error.
> I'll see how I feel once I finish higher priority stuff.

Makes sense.

> To be clear, I should be replacing { dg-options "-Wno-error=pedantic" }
> with { dg-options "" } and you would prefer I just remove { dg-options
> "-pedantic-errors" } yeah?

Yes.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-10 23:12                                                         ` Jason Merrill
@ 2023-11-11 10:43                                                           ` waffl3x
  2023-11-11 11:24                                                             ` waffl3x
  2023-11-14  3:48                                                             ` Jason Merrill
  0 siblings, 2 replies; 100+ messages in thread
From: waffl3x @ 2023-11-11 10:43 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

> [combined reply to all three threads]
> 
> On 11/9/23 23:24, waffl3x wrote:
> 
> > > > I'm unfortunately going down a rabbit hole again.
> > > > 
> > > > --function.h:608
> > > > `/* If pointers to member functions use the least significant bit to indicate whether a function is virtual, ensure a pointer to this function will have that bit clear. */ #define MINIMUM_METHOD_BOUNDARY \\\\ ((TARGET_PTRMEMFUNC_VBIT_LOCATION == ptrmemfunc_vbit_in_pfn) \\\\ ? MAX (FUNCTION_BOUNDARY, 2 * BITS_PER_UNIT) : FUNCTION_BOUNDARY)`
> > > 
> > > So yes, it was for PMFs using the low bit of the pointer to indicate a
> > > virtual member function. Since an xob memfn can't be virtual, it's
> > > correct for them to have the same alignment as a static memfn.
> > 
> > Is it worth considering whether we want to support virtual xobj member
> > functions in the future? If that were the case would it be better if we
> > aligned things a little differently here? Or might it be better if we
> > wanted to support it as an extension to just effectively translate the
> > declaration back to one that is a METHOD_TYPE? I imagine this would be
> > the best solution for non-standard support of the syntax. We would
> > simply have to forbid by-value and conversion semantics and on the
> > user's side they would get consistent syntax.
> > 
> > However, this flies in the face of the defective/contradictory spec for
> > virtual function overrides. So I'm not really sure whether we would
> > want to do this. I just want to raise the question before we lock in
> > the alignment, if pushing the patch locks it in that is, I'm not really
> > sure if it needs to be stable or not.
> 
> 
> It doesn't need to be stable; we can increase the alignment of decls as
> needed in new code without breaking older code.

Okay great, good to know, it seems so obvious when you put it that way.

> > > > All tests seemed to pass when applied to GCC14, but the results did
> > > > something funny where it said tests disappeared and new tests appeared
> > > > and passed. The ones that disappeared and the new ones that appeared
> > > > looked like they were identical so I'm not worrying about it. Just
> > > > mentioning it in case this is something I do need to look into.
> > > 
> > > That doesn't sound like a problem, but I'm curious about the specific
> > > output you're seeing.
> > 
> > I've attached a few test result comparisons so you can take a look.
> 
> 
> Looks like you're comparing results from different build directories and
> the libitm test wrongly includes the build directory in the test "name".
> So yeah, just noise.

AH okay that makes sense.

> > 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.
> 
> 
> Agreed.

Will do then.

> > > > 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.
> 
> 
> That sounds like it might cause trouble with
> 
> struct A {
> void f(this A);
> };
> 
> int main()
> {
> (&A::f) (A());
> }

I will check to see what the behavior with this is. This sounds related
to the next question I asked as well.

> > 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
> > :^).
> 
> 
> Right, first_arg is only actually used for the implicit object argument,
> it's just easier to store it separately from the arguments in (). I'm
> not sure which code you mean is no longer valid?

Yeah I agree that it's easier to store it separately.

-- call.cc:build_over_call
```
  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
    {
      tree arg = build_this (first_arg != NULL_TREE
                             ? first_arg
                             : (*args)[arg_index]);
```

The trouble is, the code (shown above) does not assume that this holds
true. It handles the case where the implicit object argument was passed
in with the rest of the arguments. As far as I've observed, it seems
like it's always passed in through the first_arg member of cand, which
is what I was referring to here.

> > 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.

Since it wasn't clear what I was referring to, here is the code that I
wrote (copied from the loop really) handling the case. In case it isn't
obvious, I didn't snip the code in the METHOD_TYPE block, it's just
snipped here as it's not code I've modified. I'm hopeful that the case
you mentioned above is not problematic, but like I said I will be sure
to test it.

-- call.cc:build_over_call
```
  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
    {
      /* SNIP */
    }
  else if (DECL_XOBJ_MEMBER_FUNC_P (fn))
    {
      gcc_assert (cand->first_arg);
      gcc_assert (cand->num_convs > 0);
      tree type = TREE_VALUE (parm);
      tree arg = cand->first_arg;
      bool conversion_warning = true;

      conv = convs[0];

      /* Set user_conv_p on the argument conversions, so rvalue/base handling
         knows not to allow any more UDCs.  This needs to happen after we
         process cand->warnings.  */
      if (flags & LOOKUP_NO_CONVERSION)
        conv->user_conv_p = true;

      tsubst_flags_t arg_complain = complain;
      if (!conversion_warning)
        arg_complain &= ~tf_warning;

      if (arg_complain & tf_warning)
        maybe_warn_pessimizing_move (arg, type, /*return_p*/false);

      val = convert_like_with_context (conv, arg, fn, 0,
                                       arg_complain);
      val = convert_for_arg_passing (type, val, arg_complain);


      if (val == error_mark_node)
        return error_mark_node;
      else
        argarray[j++] = val;
      /* Advance parameter chain, don't advance arg index */
      parm = TREE_CHAIN (parm);
      // ++arg_index;
      ++i;
      is_method = 1;
      first_arg = NULL_TREE;
    }
```

Anyway, I hope we can assume that first_argument is populated for all
non-static member function calls, and if we can't perhaps that is
something we can change. The assumptions we can make are unclear with
how the code handling METHOD_TYPE is right now. If we make it a
guarantee that first_argument is populated for non-static member
function calls that code can be vastly simplified.

> > > > 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.
> 
> 
> Yeah, that could use some refactoring. IMO ctype should be set iff we
> want to push_nested_class, and the code for setting up 'this' should
> check for METHOD_TYPE instead of referring to ctype.

I'll put it on the todo list for after this patch is done. I'll note
that it seemed like not doing push_nested_class seemed to work just
fine for xobj member functions, and only caused problems once I was
working on lambdas. For this patch, I think I will change the condition
for setting up 'this' instead. On the other hand, are we sure that
lambdas don't assume 'this' was setup when accessing their captures? I
will investigate this briefly but now that I've thought of that, maybe
I won't mess around with this after all. On the other other hand, I'm
pretty sure captures still work for lambdas even though that block
isn't being handled now... or maybe I haven't tested them.

Actually, I think it doesn't matter if lambdas do use that or not for
their captures, because handling lambdas with an xobj parameter
correctly will still involve changing how lambdas access their
captures. PR102609 has a reply providing a test case when calling a
lambdas call operator where the first argument is a type unrelated to
the lambda's closure type. This might actually be relevant because I
can think of a case where one could conditionally (if constexpr) access
captures based on the type of the xobj parameter. THEN AGAIN, I don't
think it's possible to pass an unrelated implicit object parameter to a
lambda's call operator. (Not without explicitly calling it by function
pointer of course.) There are types that are derived from the lambda,
but those are still related so I assume that captures should be able to
be accessed in such a case.

Man, that raises even more questions for myself. If we are supposed to
implicitly use the explicit object parameter for accessing captures...

([PR102609] Gašper Ažman from comment #17)
> When a lambda has captures, the explicit object parameter is used to get at
> them *silently*, if they are related, otherwise the program is ill-formed:

...then what happens when the type of the explicit object parameter is
a class derived from the lambda, and the derived class has a member
variable with the same name as a capture we are trying to access?

I'm going to sit on this for a while and wait for feedback, I'm not
planning on working on this section today anyway.

> > > > I had wanted to write about some of my frustrations with trying to
> > > > write a test for virtual specifiers and errors/warnings for
> > > > shadowing/overloading virtual functions, but I am a bit too tired at
> > > > the moment and I don't want to delay getting this up for another night.
> > > > In short, the standard does not properly specify the criteria for
> > > > overriding functions, which leaves a lot of ambiguity in how exactly we
> > > > should be handling these cases.
> > > 
> > > Agreed, this issue came up in the C++ committee meeting today. See
> > > 
> > > https://cplusplus.github.io/CWG/issues/2553.html
> > > https://cplusplus.github.io/CWG/issues/2554.html
> > > 
> > > for draft changes to clarify some of these issues.
> > 
> > Ah I guess I should have read all your responses before sending any
> > back instead of going one by one. I ended up writing about this again I
> > think.
> > 
> > struct B {
> > virtual void f() const {}
> > };
> > 
> > struct S0 : B {
> > void f() {}
> > };
> > 
> > struct S1 : B {
> > void f(this S1&) {}
> > };
> > 
> > I had a bit of a debate with a peer, I initially disagreed with the
> > standard resolution because it disallows S1's declaration of f, while
> > S0's is an overload. I won't bore you with all the details of going
> > back and forth about the proposed wording, my feelings are mostly that
> > being able to overload something with an iobj member function but not
> > being able to with an xobj member function was inconsistent. He argued
> > that keeping restrictions high at first and lowering them later is
> > better, and overloading virtual functions is already not something you
> > should really ever do, so he was in favor of the proposed wording.
> 
> 
> Right. As the author of this proposal said in discussion, "We want very
> liberal overriding for explicit object member functions so that it'll
> blow up."

Yeah okay that makes sense, that was definitely what it felt like my
friend was arguing, and I've come to agree with it.

> > In light of our debate, my stance is that we should implement things as
> > per the proposed wording.
> > 
> > struct B {
> > virtual void foo() const&;
> > };
> > 
> > struct D : B {
> > void foo(this int);
> > };
> > 
> > This would be ill-formed now according to the change in wording. My
> > laymans interpretation of the semantics are that, the parameter list is
> > the same when the xobj parameter is ignore, so it overrides. And since
> > it overrides, and xobj member functions are not allowed to override, it
> > is an error.
> 
> 
> Yes.
> 
> > To be honest, I still don't understand where the wording accounts for
> > the qualifiers of B::f, but my friend assured me that it is.
> 
> 
> The qualifiers of B::f are part of the object parameter which we're
> ignoring because the derived function is xobj.

OKAY I get it now. Correspondence of the declarations involves the type
of the object parameter, while whether something overrides or not
doesn't take the object parameter into account, and it only takes the
ref qualifiers of the function into account for iobj member functions.
While for xobj member functions it ignores ref qualifiers. Alright, got
it.

> > Somewhat related is some warnings I wanted to implement for impossible
> > to call by-value xobj member functions. Ones that involve an unrelated
> > type get dicey because people could in theory have legitimate use cases
> > for that, P0847R7 includes an example of this combining recursive
> > lambdas and the overload pattern to create a visitor. However, I think
> > it would be reasonable to warn when a by-value xobj member function can
> > not be called due to the presence of overloads that take references.
> > Off the top of my head I don't recall how many overloads it would take
> > to prevent a by-value version from being called, nor have I looked into
> > how to implement this.
> 
> 
> Hmm, is it possible to make it un-callable? A function taking A and a
> function taking A&& are ambiguous when called with an rvalue argument,
> the by-value overload isn't hidden. Similarly, A and A& are ambiguous
> when called with a cv-unqualified lvalue.

I believe so, consider the following:

struct S {
  void f(this S) {}
  void f(this S const&) {}
  void f(this S&&) {}
};

I'm not 100% what the rules on this are, if I were to hazard a guess I
bet that the reference overloads are taken because they have one less
conversion?

To be clear, I recognize that you could still select the by-value
candidate by assigning it to a pointer to function type. I just don't
really think this is a useful or typical case, while accidentally
preventing a by-value xobj member function from being callable seems
like something that could easily happen, especially if we had the
following.

struct S {
  template<typename Self>
  void f(this Self&&) {}
  /* Our class is small enough, optimize const calls here.  */
  void f(this S) {}
}

I could see this being a typical enough refactor that I would want a
warning to alert me that my function that I'm writing for optimization
purposes won't ever be called in the cases I intend it to be called in.

Granted, I fully admit that I don't fully understand the semantics with
overload resolution here so my concerns might be entirely unfounded.
This is especially true in the second case since I'm not sure if the
deduced case actually will eat up all the calls of the member function
or not.

> > But I do know that redeclarations of xobj member
> > functions as iobj member functions (and vice-versa) are not properly
> > identified when ref qualifiers are omitted from the corresponding (is
> > this the correct term here?) iobj member function.
> 
> 
> That would be the code in add_method discussed later in that message.

Yeah, which I should get to before we hit our deadline.

> > > > + /* If */
> > > 
> > > I think this comment doesn't add much. :)
> > 
> > I wish I remembered what I even meant to put here, oh well, must not
> > have been all that important. Oh wait, I remember it now, I was going
> > to note that the next element in the chain of declarators being null
> > seems to indicate that the declaration is a function type. I will
> > probably put that comment in again, it's on the line of commenting
> > exactly what the code does but it was cryptic to figure this out, I'm
> > not sure if I should lean towards including the comment here or not.
> 
> 
> I always lean towards including comments. :)

I personally prefer to write code that doesn't need comments, but in a
code base like this we don't really have that luxury. It would take
massive amounts of refactoring to communicate assumptions through code
instead of through comments.

While I would like to move towards this, for now I will also lean
towards including comments, and in this particular case I'll make sure
to include what I wrote in the comment.

> > > > + /* I can imagine doing a fixit here, suggesting replacing
> > > > + this / *this / this-> with &name / name / "name." but it would be
> > > > + very difficult to get it perfect and I've been advised against
> > > > + making imperfect fixits.
> > > > + Perhaps it would be as simple as the replacements listed,
> > > > + even if one is move'ing/forward'ing, if the replacement is just
> > > > + done in the same place, it will be exactly what the user wants?
> > > > + Even if this is true though, there's still a problem of getting the
> > > > + context of the expression to find which tokens to replace.
> > > > + I would really like for this to be possible though.
> > > > + I will decide whether or not to persue this after review. */
> > > 
> > > You could pass the location of 'this' from the parser to
> > > finish_this_expr and always replace it with (&name)?
> > 
> > I've been reluctant to make any changes to parameters, but if you're
> > suggesting it I will consider it. Just replacing it with '(&name)'
> > seems really mediocre to me though. Yeah, sure, it's always going to be
> > syntactically valid but I worry that it wont be semantically correct
> > often enough.
> > 
> > Yeah, considering that I think I will leave it for now until I have
> > time to come up with a robust solution. I might still pass in the
> > location of the this token though and use error_at instead of error.
> > I'll see how I feel once I finish higher priority stuff.
> 
> 
> Makes sense.
> 
> > To be clear, I should be replacing { dg-options "-Wno-error=pedantic" }
> > with { dg-options "" } and you would prefer I just remove { dg-options
> > "-pedantic-errors" } yeah?
> 
> 
> Yes.

Sure thing then.

I'll try to get a new version of the patch out ASAP, I'm not sure I'll
include everything we talked about right away but I'll stuff in as much
as I can. I'll focus on the missing semantics before the more
complicated diagnostics changes. I think detection of redeclarations,
the correct overriding behavior, and the additional warnings I have
been discussing, will come later.

I think I'm also going to leave investigation of requires expressions
in non-dependent contexts that I had mentioned for another time as
well. The tests related to those have been fixed and work as expected
right now, and I think diving in and trying to figure out whether I can
do those tests with non-dependent operands would be a bad use of my
time.

Of course I will make sure all the small changes you requested make it
in, that might be the first thing I work on.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-11 10:43                                                           ` waffl3x
@ 2023-11-11 11:24                                                             ` waffl3x
  2023-11-14  3:48                                                             ` Jason Merrill
  1 sibling, 0 replies; 100+ messages in thread
From: waffl3x @ 2023-11-11 11:24 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

Just a quick addition here as I was starting to work on things I
realized where some misunderstandings were coming from. (Please also
see my previous e-mail, it is all still relevant, I just wanted to
clarify this.)

(From the other thread)
> > @@ -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.

(This thread)
> > 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.
> 
> 
> That sounds like it might cause trouble with
> 
> struct A {
> void f(this A);
> };
> 
> int main()
> {
> (&A::f) (A());
> }
> 

Upon reviewing your reply in the other thread, I noticed you are
maybe misunderstanding the assertion a little bit.
```
  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
    {
      /* SNIP */
      if (first_arg != NULL_TREE)
        first_arg = NULL_TREE;
      else
        ++arg_index;
      ++i;
      is_method = 1;
    }
  else if (DECL_XOBJ_MEMBER_FUNC_P (fn))
    {
      gcc_assert (cand->first_arg);
      /* SNIP */
      first_arg = NULL_TREE;
    }
```
I already showed my code here but I didn't think to include the
previous conditional block to demonstrate how the first_arg variable
gets handled in it. Although, maybe you understood they set it to
NULL_TREE by the end of the block and I just poorly communicated that I
would be doing the same.

Perhaps you think it better to handle the implicit object argument
within the loop, but given that it is stored in first_arg I don't think
that would be correct. Granted, as I previously said, maybe there are
some situations where it isn't that I haven't encountered yet. The
other thing is there is some handling in the loop that isn't relevant
to the implicit object argument. Well, I would also just argue this
function needs a fair degree of refactoring, but I've already talked
about that.

Anyway, hopefully that clarifies things, and hopefully things actually
needed to be clarified and I'm not being a fool here. :) That's all I
had to add about this, back to work now.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-11 10:43                                                           ` waffl3x
  2023-11-11 11:24                                                             ` waffl3x
@ 2023-11-14  3:48                                                             ` Jason Merrill
  2023-11-14  4:36                                                               ` waffl3x
  1 sibling, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-14  3:48 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/11/23 05:43, waffl3x wrote:
>> [combined reply to all three threads]
>>
>> On 11/9/23 23:24, waffl3x wrote:
>>
>>>>> 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.
>>
>>
>> That sounds like it might cause trouble with
>>
>> struct A {
>> void f(this A);
>> };
>>
>> int main()
>> {
>> (&A::f) (A());
>> }
> 
> I will check to see what the behavior with this is. This sounds related
> to the next question I asked as well.
> 
>>> 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
>>> :^).
>>
>>
>> Right, first_arg is only actually used for the implicit object argument,
>> it's just easier to store it separately from the arguments in (). I'm
>> not sure which code you mean is no longer valid?
> 
> Yeah I agree that it's easier to store it separately.
> 
> -- call.cc:build_over_call
> ```
>    else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
>      {
>        tree arg = build_this (first_arg != NULL_TREE
>                               ? first_arg
>                               : (*args)[arg_index]);
> ```
> 
> The trouble is, the code (shown above) does not assume that this holds
> true. It handles the case where the implicit object argument was passed
> in with the rest of the arguments. As far as I've observed, it seems
> like it's always passed in through the first_arg member of cand, which
> is what I was referring to here.
> 
>>> 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.
> 
> Since it wasn't clear what I was referring to, here is the code that I
> wrote (copied from the loop really) handling the case. In case it isn't
> obvious, I didn't snip the code in the METHOD_TYPE block, it's just
> snipped here as it's not code I've modified. I'm hopeful that the case
> you mentioned above is not problematic, but like I said I will be sure
> to test it.
> 
> -- call.cc:build_over_call
> ```
>    else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
>      {
>       /* SNIP */
>       if (first_arg != NULL_TREE)
>         first_arg = NULL_TREE;
>       else
>         ++arg_index;
>       ++i;
>       is_method = 1;
>      }
>    else if (DECL_XOBJ_MEMBER_FUNC_P (fn))
>      {
>        gcc_assert (cand->first_arg);
>        gcc_assert (cand->num_convs > 0);
>        tree type = TREE_VALUE (parm);
>        tree arg = cand->first_arg;
>        bool conversion_warning = true;
> 
>        conv = convs[0];
> 
>        /* Set user_conv_p on the argument conversions, so rvalue/base handling
>           knows not to allow any more UDCs.  This needs to happen after we
>           process cand->warnings.  */
>        if (flags & LOOKUP_NO_CONVERSION)
>          conv->user_conv_p = true;
> 
>        tsubst_flags_t arg_complain = complain;
>        if (!conversion_warning)
>          arg_complain &= ~tf_warning;
> 
>        if (arg_complain & tf_warning)
>          maybe_warn_pessimizing_move (arg, type, /*return_p*/false);
> 
>        val = convert_like_with_context (conv, arg, fn, 0,
>                                         arg_complain);
>        val = convert_for_arg_passing (type, val, arg_complain);
> 
> 
>        if (val == error_mark_node)
>          return error_mark_node;
>        else
>          argarray[j++] = val;
>        /* Advance parameter chain, don't advance arg index */
>        parm = TREE_CHAIN (parm);
>        // ++arg_index;
>        ++i;
>        is_method = 1;
>        first_arg = NULL_TREE;
>      }
> ```
> 
> Anyway, I hope we can assume that first_argument is populated for all
> non-static member function calls, and if we can't perhaps that is
> something we can change. The assumptions we can make are unclear with
> how the code handling METHOD_TYPE is right now. If we make it a
> guarantee that first_argument is populated for non-static member
> function calls that code can be vastly simplified.

Somewhat, but the current METHOD_TYPE code works fine, so let's not mess 
with it.  There might be corner cases that end up with the object 
argument in the args vec.

[other reply]

> I already showed my code here but I didn't think to include the
> previous conditional block to demonstrate how the first_arg variable
> gets handled in it. Although, maybe you understood they set it to
> NULL_TREE by the end of the block and I just poorly communicated that I
> would be doing the same.

I meant that I expect the

> gcc_assert (cand->first_arg);

to fail on my example.

> Perhaps you think it better to handle the implicit object argument
> within the loop, but given that it is stored in first_arg I don't think
> that would be correct. Granted, as I previously said, maybe there are
> some situations where it isn't that I haven't encountered yet. The
> other thing is there is some handling in the loop that isn't relevant
> to the implicit object argument. Well, I would also just argue this
> function needs a fair degree of refactoring, but I've already talked
> about that.

Factoring out the loop body (into a lambda?) rather than copying it 
would make sense to me.

>>>>> 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.
>>
>>
>> Yeah, that could use some refactoring. IMO ctype should be set iff we
>> want to push_nested_class, and the code for setting up 'this' should
>> check for METHOD_TYPE instead of referring to ctype.
> 
> I'll put it on the todo list for after this patch is done. I'll note
> that it seemed like not doing push_nested_class seemed to work just
> fine for xobj member functions, and only caused problems once I was
> working on lambdas.

Hmm, we want push_nested_class for any member function so that name 
lookup within the function finds class members.

> For this patch, I think I will change the condition
> for setting up 'this' instead. On the other hand, are we sure that
> lambdas don't assume 'this' was setup when accessing their captures? I
> will investigate this briefly but now that I've thought of that, maybe
> I won't mess around with this after all. On the other other hand, I'm
> pretty sure captures still work for lambdas even though that block
> isn't being handled now... or maybe I haven't tested them.

Captures don't rely on being able to name the object parameter, they use 
it directly without name lookup.  And indeed 'this' doesn't name the 
object parameter for the lambda op(), finish_this_expr looks past it.

> Actually, I think it doesn't matter if lambdas do use that or not for
> their captures, because handling lambdas with an xobj parameter
> correctly will still involve changing how lambdas access their
> captures. PR102609 has a reply providing a test case when calling a
> lambdas call operator where the first argument is a type unrelated to
> the lambda's closure type. This might actually be relevant because I
> can think of a case where one could conditionally (if constexpr) access
> captures based on the type of the xobj parameter. THEN AGAIN, I don't
> think it's possible to pass an unrelated implicit object parameter to a
> lambda's call operator. (Not without explicitly calling it by function
> pointer of course.) There are types that are derived from the lambda,
> but those are still related so I assume that captures should be able to
> be accessed in such a case.
> 
> Man, that raises even more questions for myself. If we are supposed to
> implicitly use the explicit object parameter for accessing captures...
> 
> ([PR102609] Gašper Ažman from comment #17)
>> When a lambda has captures, the explicit object parameter is used to get at
>> them *silently*, if they are related, otherwise the program is ill-formed:
> 
> ...then what happens when the type of the explicit object parameter is
> a class derived from the lambda, and the derived class has a member
> variable with the same name as a capture we are trying to access?

That ought to work fine; capture fields don't have the name of the 
variables they capture, they are only accessible through the capture 
proxy (from build_capture_proxy), which in turn refers directly to the 
member by its _DECL, not by name lookup.

>>> Somewhat related is some warnings I wanted to implement for impossible
>>> to call by-value xobj member functions. Ones that involve an unrelated
>>> type get dicey because people could in theory have legitimate use cases
>>> for that, P0847R7 includes an example of this combining recursive
>>> lambdas and the overload pattern to create a visitor. However, I think
>>> it would be reasonable to warn when a by-value xobj member function can
>>> not be called due to the presence of overloads that take references.
>>> Off the top of my head I don't recall how many overloads it would take
>>> to prevent a by-value version from being called, nor have I looked into
>>> how to implement this.
>>
>> Hmm, is it possible to make it un-callable? A function taking A and a
>> function taking A&& are ambiguous when called with an rvalue argument,
>> the by-value overload isn't hidden. Similarly, A and A& are ambiguous
>> when called with a cv-unqualified lvalue.
> 
> I believe so, consider the following:
> 
> struct S {
>    void f(this S) {}
>    void f(this S const&) {}
>    void f(this S&&) {}
> };
> 
> I'm not 100% what the rules on this are, if I were to hazard a guess I
> bet that the reference overloads are taken because they have one less
> conversion?

Initializing a by-value parameter isn't an extra conversion, it's an 
identity conversion, so ambiguous with the reference overloads, just as with

struct A { };
void f (A);
void f (const A&);
void f (A&&);

int main()
{
   A a;
   f (a);   // ambiguous
   f (A()); // ambiguous
}

> To be clear, I recognize that you could still select the by-value
> candidate by assigning it to a pointer to function type. I just don't
> really think this is a useful or typical case, while accidentally
> preventing a by-value xobj member function from being callable seems
> like something that could easily happen, especially if we had the
> following.
> 
> struct S {
>    template<typename Self>
>    void f(this Self&&) {}
>    /* Our class is small enough, optimize const calls here.  */
>    void f(this S) {}
> }
> 
> I could see this being a typical enough refactor that I would want a
> warning to alert me that my function that I'm writing for optimization
> purposes won't ever be called in the cases I intend it to be called in.
> 
> Granted, I fully admit that I don't fully understand the semantics with
> overload resolution here so my concerns might be entirely unfounded.
> This is especially true in the second case since I'm not sure if the
> deduced case actually will eat up all the calls of the member function
> or not.

Rather, in the deduced case the by-value overload always wins because 
overload resolution prefers a non-template if the conversions are 
equally good.

struct A { };
void f (A);
template <class T> void f (T&&);

int main()
{
   A a;
   f (a);   // calls non-template
   f (A()); // calls non-template
}

> I'll try to get a new version of the patch out ASAP, I'm not sure I'll
> include everything we talked about right away but I'll stuff in as much
> as I can. I'll focus on the missing semantics before the more
> complicated diagnostics changes. I think detection of redeclarations,
> the correct overriding behavior, and the additional warnings I have
> been discussing, will come later.

Makes sense.

> I think I'm also going to leave investigation of requires expressions
> in non-dependent contexts that I had mentioned for another time as
> well. The tests related to those have been fixed and work as expected
> right now, and I think diving in and trying to figure out whether I can
> do those tests with non-dependent operands would be a bad use of my
> time.

Agreed.

> Of course I will make sure all the small changes you requested make it
> in, that might be the first thing I work on.

Also note that revisions can slip into Stage 3, though preferably not 
too far.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-14  3:48                                                             ` Jason Merrill
@ 2023-11-14  4:36                                                               ` waffl3x
  2023-11-18  6:43                                                                 ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-14  4:36 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

On Monday, November 13th, 2023 at 8:48 PM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 11/11/23 05:43, waffl3x wrote:
> 
> > > [combined reply to all three threads]
> > > 
> > > On 11/9/23 23:24, waffl3x wrote:
> > > 
> > > > > > 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.
> > > 
> > > That sounds like it might cause trouble with
> > > 
> > > struct A {
> > > void f(this A);
> > > };
> > > 
> > > int main()
> > > {
> > > (&A::f) (A());
> > > }
> > 
> > I will check to see what the behavior with this is. This sounds related
> > to the next question I asked as well.
> > 
> > > > 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
> > > > :^).
> > > 
> > > Right, first_arg is only actually used for the implicit object argument,
> > > it's just easier to store it separately from the arguments in (). I'm
> > > not sure which code you mean is no longer valid?
> > 
> > Yeah I agree that it's easier to store it separately.
> > 
> > -- call.cc:build_over_call
> > `else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE) { tree arg = build_this (first_arg != NULL_TREE ? first_arg : (*args)[arg_index]);`
> > 
> > The trouble is, the code (shown above) does not assume that this holds
> > true. It handles the case where the implicit object argument was passed
> > in with the rest of the arguments. As far as I've observed, it seems
> > like it's always passed in through the first_arg member of cand, which
> > is what I was referring to here.
> > 
> > > > 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.
> > 
> > Since it wasn't clear what I was referring to, here is the code that I
> > wrote (copied from the loop really) handling the case. In case it isn't
> > obvious, I didn't snip the code in the METHOD_TYPE block, it's just
> > snipped here as it's not code I've modified. I'm hopeful that the case
> > you mentioned above is not problematic, but like I said I will be sure
> > to test it.
> > 
> > -- call.cc:build_over_call
> > ```
> > else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
> > {
> > /* SNIP */
> > if (first_arg != NULL_TREE)
> > first_arg = NULL_TREE;
> > else
> > ++arg_index;
> > ++i;
> > is_method = 1;
> > }
> > else if (DECL_XOBJ_MEMBER_FUNC_P (fn))
> > {
> > gcc_assert (cand->first_arg);
> > gcc_assert (cand->num_convs > 0);
> > tree type = TREE_VALUE (parm);
> > tree arg = cand->first_arg;
> > bool conversion_warning = true;
> > 
> > conv = convs[0];
> > 
> > /* Set user_conv_p on the argument conversions, so rvalue/base handling
> > knows not to allow any more UDCs. This needs to happen after we
> > process cand->warnings. */
> > if (flags & LOOKUP_NO_CONVERSION)
> > conv->user_conv_p = true;
> > 
> > tsubst_flags_t arg_complain = complain;
> > if (!conversion_warning)
> > arg_complain &= ~tf_warning;
> > 
> > if (arg_complain & tf_warning)
> > maybe_warn_pessimizing_move (arg, type, /return_p/false);
> > 
> > val = convert_like_with_context (conv, arg, fn, 0,
> > arg_complain);
> > val = convert_for_arg_passing (type, val, arg_complain);
> > 
> > if (val == error_mark_node)
> > return error_mark_node;
> > else
> > argarray[j++] = val;
> > /* Advance parameter chain, don't advance arg index */
> > parm = TREE_CHAIN (parm);
> > // ++arg_index;
> > ++i;
> > is_method = 1;
> > first_arg = NULL_TREE;
> > }
> > ```
> > 
> > Anyway, I hope we can assume that first_argument is populated for all
> > non-static member function calls, and if we can't perhaps that is
> > something we can change. The assumptions we can make are unclear with
> > how the code handling METHOD_TYPE is right now. If we make it a
> > guarantee that first_argument is populated for non-static member
> > function calls that code can be vastly simplified.
> 
> 
> Somewhat, but the current METHOD_TYPE code works fine, so let's not mess
> with it. There might be corner cases that end up with the object
> argument in the args vec.

Yeah I haven't messed with it, I fully agree that it's not something
that should be a part of this patch. I do want to thoroughly
investigate whether these corner cases exist in the future though.

> [other reply]
> 
> > I already showed my code here but I didn't think to include the
> > previous conditional block to demonstrate how the first_arg variable
> > gets handled in it. Although, maybe you understood they set it to
> > NULL_TREE by the end of the block and I just poorly communicated that I
> > would be doing the same.
> 
> 
> I meant that I expect the
> 
> > gcc_assert (cand->first_arg);
> 
> 
> to fail on my example.
> 
> > Perhaps you think it better to handle the implicit object argument
> > within the loop, but given that it is stored in first_arg I don't think
> > that would be correct. Granted, as I previously said, maybe there are
> > some situations where it isn't that I haven't encountered yet. The
> > other thing is there is some handling in the loop that isn't relevant
> > to the implicit object argument. Well, I would also just argue this
> > function needs a fair degree of refactoring, but I've already talked
> > about that.
> 
> 
> Factoring out the loop body (into a lambda?) rather than copying it
> would make sense to me.

Gladly, the duplication made me uneasy for sure.

> > > > > > 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.
> > > 
> > > Yeah, that could use some refactoring. IMO ctype should be set iff we
> > > want to push_nested_class, and the code for setting up 'this' should
> > > check for METHOD_TYPE instead of referring to ctype.
> > 
> > I'll put it on the todo list for after this patch is done. I'll note
> > that it seemed like not doing push_nested_class seemed to work just
> > fine for xobj member functions, and only caused problems once I was
> > working on lambdas.
> 
> 
> Hmm, we want push_nested_class for any member function so that name
> lookup within the function finds class members.

Okay, thanks for noting this explicitly, I probably would have run into
problems otherwise. Come to think of it, I might have to be careful to
make sure that I'm handling calling a function of base from a derived
class correctly. I definitely need tests for this case.

> > For this patch, I think I will change the condition
> > for setting up 'this' instead. On the other hand, are we sure that
> > lambdas don't assume 'this' was setup when accessing their captures? I
> > will investigate this briefly but now that I've thought of that, maybe
> > I won't mess around with this after all. On the other other hand, I'm
> > pretty sure captures still work for lambdas even though that block
> > isn't being handled now... or maybe I haven't tested them.
> 
> 
> Captures don't rely on being able to name the object parameter, they use
> it directly without name lookup. And indeed 'this' doesn't name the
> object parameter for the lambda op(), finish_this_expr looks past it.

Wonderful, that should be helpful.

> > Actually, I think it doesn't matter if lambdas do use that or not for
> > their captures, because handling lambdas with an xobj parameter
> > correctly will still involve changing how lambdas access their
> > captures. PR102609 has a reply providing a test case when calling a
> > lambdas call operator where the first argument is a type unrelated to
> > the lambda's closure type. This might actually be relevant because I
> > can think of a case where one could conditionally (if constexpr) access
> > captures based on the type of the xobj parameter. THEN AGAIN, I don't
> > think it's possible to pass an unrelated implicit object parameter to a
> > lambda's call operator. (Not without explicitly calling it by function
> > pointer of course.) There are types that are derived from the lambda,
> > but those are still related so I assume that captures should be able to
> > be accessed in such a case.
> > 
> > Man, that raises even more questions for myself. If we are supposed to
> > implicitly use the explicit object parameter for accessing captures...
> > 
> > ([PR102609] Gašper Ažman from comment #17)
> > 
> > > When a lambda has captures, the explicit object parameter is used to get at
> > > them silently, if they are related, otherwise the program is ill-formed:
> > 
> > ...then what happens when the type of the explicit object parameter is
> > a class derived from the lambda, and the derived class has a member
> > variable with the same name as a capture we are trying to access?
> 
> 
> That ought to work fine; capture fields don't have the name of the
> variables they capture, they are only accessible through the capture
> proxy (from build_capture_proxy), which in turn refers directly to the
> member by its _DECL, not by name lookup.

Okay, so just adding error checking for an unrelated object parameter
type should do the trick for us then.

> > > > Somewhat related is some warnings I wanted to implement for impossible
> > > > to call by-value xobj member functions. Ones that involve an unrelated
> > > > type get dicey because people could in theory have legitimate use cases
> > > > for that, P0847R7 includes an example of this combining recursive
> > > > lambdas and the overload pattern to create a visitor. However, I think
> > > > it would be reasonable to warn when a by-value xobj member function can
> > > > not be called due to the presence of overloads that take references.
> > > > Off the top of my head I don't recall how many overloads it would take
> > > > to prevent a by-value version from being called, nor have I looked into
> > > > how to implement this.
> > > 
> > > Hmm, is it possible to make it un-callable? A function taking A and a
> > > function taking A&& are ambiguous when called with an rvalue argument,
> > > the by-value overload isn't hidden. Similarly, A and A& are ambiguous
> > > when called with a cv-unqualified lvalue.
> > 
> > I believe so, consider the following:
> > 
> > struct S {
> > void f(this S) {}
> > void f(this S const&) {}
> > void f(this S&&) {}
> > };
> > 
> > I'm not 100% what the rules on this are, if I were to hazard a guess I
> > bet that the reference overloads are taken because they have one less
> > conversion?
> 
> 
> Initializing a by-value parameter isn't an extra conversion, it's an
> identity conversion, so ambiguous with the reference overloads, just as with

I will have to check again, but I recall seeing cand had conversion to
rvalue for arguments that initialize by-value parameters. I assume I'm
either mistaken or misunderstanding what I was looking at though.

> struct A { };
> void f (A);
> void f (const A&);
> void f (A&&);
> 
> int main()
> {
> A a;
> f (a); // ambiguous
> f (A()); // ambiguous
> }

Ambiguous is good though, we might not need to issue a warning at all.

> > To be clear, I recognize that you could still select the by-value
> > candidate by assigning it to a pointer to function type. I just don't
> > really think this is a useful or typical case, while accidentally
> > preventing a by-value xobj member function from being callable seems
> > like something that could easily happen, especially if we had the
> > following.
> > 
> > struct S {
> > template<typename Self>
> > void f(this Self&&) {}
> > /* Our class is small enough, optimize const calls here. */
> > void f(this S) {}
> > }
> > 
> > I could see this being a typical enough refactor that I would want a
> > warning to alert me that my function that I'm writing for optimization
> > purposes won't ever be called in the cases I intend it to be called in.
> > 
> > Granted, I fully admit that I don't fully understand the semantics with
> > overload resolution here so my concerns might be entirely unfounded.
> > This is especially true in the second case since I'm not sure if the
> > deduced case actually will eat up all the calls of the member function
> > or not.
> 
> 
> Rather, in the deduced case the by-value overload always wins because
> overload resolution prefers a non-template if the conversions are
> equally good.
> 
> struct A { };
> void f (A);
> template <class T> void f (T&&);
> 
> 
> int main()
> {
> A a;
> f (a); // calls non-template
> f (A()); // calls non-template
> }

That's less good, maybe we want a warning in the other direction now
then? I had expected it to prioritize the template for this. I can
definitely imagine someone getting confused why their object isn't
mutating in a case like this if they were delegating to other functions
in the function call.

Well, I tried to write an example and I'm realizing that maybe my
concerns are imagined. So I'm not going to worry about it for now. I'm
thinking that the cases where someone wants to deduce Self and also
have a by-value overload are not that common. On the other hand, maybe
we still want to warn for it in case people try to do it.

I'll put it in the back of my mind for now, it doesn't seem important
enough to worry about anymore.

> > I'll try to get a new version of the patch out ASAP, I'm not sure I'll
> > include everything we talked about right away but I'll stuff in as much
> > as I can. I'll focus on the missing semantics before the more
> > complicated diagnostics changes. I think detection of redeclarations,
> > the correct overriding behavior, and the additional warnings I have
> > been discussing, will come later.
> 
> 
> Makes sense.
> 
> > I think I'm also going to leave investigation of requires expressions
> > in non-dependent contexts that I had mentioned for another time as
> > well. The tests related to those have been fixed and work as expected
> > right now, and I think diving in and trying to figure out whether I can
> > do those tests with non-dependent operands would be a bad use of my
> > time.
> 
> 
> Agreed.

Unfortunately I was a bit under the weather this weekend so I haven't
gotten much work done, I've been going strong today though. Plan stays
the same though.

> > Of course I will make sure all the small changes you requested make it
> > in, that might be the first thing I work on.
> 
> 
> Also note that revisions can slip into Stage 3, though preferably not
> too far.
> 
> Jason

I'll try not to let it come to that, better I don't consider it an
option anyway. :^)

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-14  4:36                                                               ` waffl3x
@ 2023-11-18  6:43                                                                 ` waffl3x
  2023-11-19  6:22                                                                   ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-18  6:43 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

The patch is coming along, I just have a quick question regarding
style. I make use of IILE's (immediately invoked lambda expression) a
whole lot in my own code. I know that their use is controversial in
general so I would prefer to ask instead of just submitting the patch
using them a bunch suddenly. I wouldn't have bothered either but this
part is really miserable without them.

If that would be okay, I would suggest an additional exception to
bracing style for lambdas.
This:
[](){
  // stuff
};
Instead of this:
[]()
  {
    // stuff
  };

This is especially important for IILE pattern IMO, else it looks really
mediocre. If this isn't okay okay I'll refactor all the IILE's that I
added, or just name them and call them instead. Whatever you think is
most appropriate.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-18  6:43                                                                 ` waffl3x
@ 2023-11-19  6:22                                                                   ` Jason Merrill
  2023-11-19 13:39                                                                     ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-19  6:22 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On Sat, Nov 18, 2023 at 1:43 AM waffl3x <waffl3x@protonmail.com> wrote:
>
> The patch is coming along, I just have a quick question regarding
> style. I make use of IILE's (immediately invoked lambda expression) a
> whole lot in my own code. I know that their use is controversial in
> general so I would prefer to ask instead of just submitting the patch
> using them a bunch suddenly. I wouldn't have bothered either but this
> part is really miserable without them.

We don't use them much currently, but I'm not opposed to them either,
if they help make the code clearer.

> If that would be okay, I would suggest an additional exception to
> bracing style for lambdas.
> This:
> [](){
>   // stuff
> };
> Instead of this:
> []()
>   {
>     // stuff
>   };

Makes sense.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-19  6:22                                                                   ` Jason Merrill
@ 2023-11-19 13:39                                                                     ` waffl3x
  2023-11-19 16:31                                                                       ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-19 13:39 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

Funny enough I ended up removing the ones I was thinking about, seems
to always happen when I ask style questions but I'm glad to hear it's
okay going forward.

I'm having trouble fixing this bug, based on what Gasper said in
PR102609 I am pretty sure I know what the semantics should be. Since
the capture is not used in the body of the function, it should be well
formed to call the function with an unrelated type.

I had begun trying to tackle the case that Gasper mentioned and got the
following ICE. I also have another case that ICEs so I've been thinking
I don't get to do little changes to fix this. I've been looking at this
for a few hours now and given we are past the deadline now I figured I
should see what others think.

int main()
{
  int x = 42;
  auto f1 = [x](this auto&& self) {};

  static_cast<int(*)(int&)>(decltype(f1)::operator());
}

explicit-obj-lambdaX3.C: In instantiation of 'main()::<lambda(auto:1&&)> static [with auto:1 = int&]':
explicit-obj-lambdaX3.C:33:53:   required from here
   33 |   static_cast<int(*)(int&)>(decltype(f1)::operator());
      |                                                     ^
explicit-obj-lambdaX3.C:31:33: internal compiler error: tree check: expected record_type or union_type or qual_union_type, have integer_type in finish_non_static_data_member, at cp/semantics.cc:2294
   31 |   auto f1 = [x](this auto&& self) {};
      |                                 ^
0x1c66dda tree_check_failed(tree_node const*, char const*, int, char const*, ...)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/tree.cc:8949
0xb2e125 tree_check3(tree_node*, char const*, int, char const*, tree_code, tree_code, tree_code)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/tree.h:3638
0xedfaf4 finish_non_static_data_member(tree_node*, tree_node*, tree_node*, int)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/semantics.cc:2294
0xe8b9b8 tsubst_expr(tree_node*, tree_node*, int, tree_node*)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/pt.cc:20864
0xe6d713 tsubst_decl
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/pt.cc:15387
0xe6fb1b tsubst(tree_node*, tree_node*, int, tree_node*)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/pt.cc:15967
0xe7bd81 tsubst_stmt
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/pt.cc:18299
0xe7df18 tsubst_stmt
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/pt.cc:18554
0xea6982 instantiate_body
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/pt.cc:26743
0xea83e9 instantiate_decl(tree_node*, bool, bool)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/pt.cc:27030
0xb5f9c9 resolve_address_of_overloaded_function
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/class.cc:8802
0xb60be1 instantiate_type(tree_node*, tree_node*, int)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/class.cc:9061
0xaf9992 standard_conversion
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/call.cc:1244
0xafcb57 implicit_conversion
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/call.cc:2081
0xb2a8cb perform_direct_initialization_if_possible(tree_node*, tree_node*, bool, int)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/call.cc:13456
0xf69db8 build_static_cast_1
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/typeck.cc:8356
0xf6af1b build_static_cast(unsigned int, tree_node*, tree_node*, int)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/typeck.cc:8566
0xd9fc02 cp_parser_postfix_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:7531
0xda45af cp_parser_unary_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:9244
0xda5db4 cp_parser_cast_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:10148

As I said, there is also this case that also ICEs in the same region.
It's making me think that some core assumptions are being violated in
the code leading up to finish_non_static_data_member.

int main()
{
  int x = 42;
  auto f1 = [x](this auto self) {};
}

explicit-obj-lambdaX3.C: In lambda function:
explicit-obj-lambdaX3.C:31:31: internal compiler error: Segmentation fault
   31 |   auto f1 = [x](this auto self) {};
      |                               ^
0x1869eaa crash_signal
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/toplev.cc:315
0xb2ea0b strip_array_types(tree_node*)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/tree.h:4955
0xf773e2 cp_type_quals(tree_node const*)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/typeck.cc:11509
0xedf993 finish_non_static_data_member(tree_node*, tree_node*, tree_node*, int)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/semantics.cc:2271
0xcc3e12 build_capture_proxy
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/lambda.cc:410
0xcce99a start_lambda_function(tree_node*, tree_node*)
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/lambda.cc:1684
0xdab196 cp_parser_lambda_body
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:12004
0xda8f64 cp_parser_lambda_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:11293
0xd9c03a cp_parser_primary_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:5921
0xda0b33 cp_parser_postfix_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:7868
0xda45af cp_parser_unary_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:9244
0xda5db4 cp_parser_cast_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:10148
0xda5ecd cp_parser_binary_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:10251
0xda6fd9 cp_parser_assignment_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:10595
0xda77b3 cp_parser_constant_expression
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:10885
0xdc9a36 cp_parser_initializer_clause
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:25844
0xdc981d cp_parser_initializer
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:25783
0xdc40bd cp_parser_init_declarator
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:23332
0xdb46d2 cp_parser_simple_declaration
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:15690
0xdb41e5 cp_parser_block_declaration
	/home/waffl3x/projects/gcc-dev/workspace/src/xobj-next/gcc/cp/parser.cc:15510

Any insight is appreciated here, I've been going through with GDB and I
do feel like I'm making progress, but I'm not much closer to figuring
out what the correct solution (to the first) is.

Right now, my plan is when trying to access captures, it should be
checking to see if the type of the xobj param passed in is related to
the context of the function decl. I just haven't figured out exactly
where to insert it yet. With any luck, now that I've written an e-mail
saying I'm having trouble I'll immediately figure out what to do. It's
happened every previous time so far. :)

Alex

On Saturday, November 18th, 2023 at 11:22 PM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On Sat, Nov 18, 2023 at 1:43 AM waffl3x waffl3x@protonmail.com wrote:
> 
> > The patch is coming along, I just have a quick question regarding
> > style. I make use of IILE's (immediately invoked lambda expression) a
> > whole lot in my own code. I know that their use is controversial in
> > general so I would prefer to ask instead of just submitting the patch
> > using them a bunch suddenly. I wouldn't have bothered either but this
> > part is really miserable without them.
> 
> 
> We don't use them much currently, but I'm not opposed to them either,
> if they help make the code clearer.
> 
> > If that would be okay, I would suggest an additional exception to
> > bracing style for lambdas.
> > This:
> > {
> > // stuff
> > };
> > Instead of this:
> > 
> > {
> > // stuff
> > };
> 
> 
> Makes sense.
> 
> Jason

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-19 13:39                                                                     ` waffl3x
@ 2023-11-19 16:31                                                                       ` Jason Merrill
  2023-11-19 18:36                                                                         ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-19 16:31 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/19/23 08:39, waffl3x wrote:
> Funny enough I ended up removing the ones I was thinking about, seems
> to always happen when I ask style questions but I'm glad to hear it's
> okay going forward.
> 
> I'm having trouble fixing this bug, based on what Gasper said in
> PR102609 I am pretty sure I know what the semantics should be. Since
> the capture is not used in the body of the function, it should be well
> formed to call the function with an unrelated type.

I don't think so: https://eel.is/c++draft/expr.prim#lambda.closure-5
says the type of the xobj parameter must be related.

> I had begun trying to tackle the case that Gasper mentioned and got the
> following ICE. I also have another case that ICEs so I've been thinking
> I don't get to do little changes to fix this. I've been looking at this
> for a few hours now and given we are past the deadline now I figured I
> should see what others think.
> 
> int main()
> {
>    int x = 42;
>    auto f1 = [x](this auto&& self) {};
> 
>    static_cast<int(*)(int&)>(decltype(f1)::operator());
> }

This should be rejected when we try to instantiate the op() with int.

> As I said, there is also this case that also ICEs in the same region.
> It's making me think that some core assumptions are being violated in
> the code leading up to finish_non_static_data_member.
> 
> int main()
> {
>    int x = 42;
>    auto f1 = [x](this auto self) {};
> }

Here I think the problem is in build_capture_proxy:

>   /* The proxy variable forwards to the capture field.  */
>   object = build_fold_indirect_ref (DECL_ARGUMENTS (fn));
>   object = finish_non_static_data_member (member, object, NULL_TREE);

The call to build_fold_indirect_ref assumes that 'this' is a pointer, 
which it is not here.  I think you can just make that conditional on it 
being a pointer or reference?

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-19 16:31                                                                       ` Jason Merrill
@ 2023-11-19 18:36                                                                         ` waffl3x
  2023-11-19 20:34                                                                           ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-19 18:36 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

On Sunday, November 19th, 2023 at 9:31 AM, Jason Merrill <jason@redhat.com> wrote:


>
>
> On 11/19/23 08:39, waffl3x wrote:
>
> > Funny enough I ended up removing the ones I was thinking about, seems
> > to always happen when I ask style questions but I'm glad to hear it's
> > okay going forward.
> >
> > I'm having trouble fixing this bug, based on what Gasper said in
> > PR102609 I am pretty sure I know what the semantics should be. Since
> > the capture is not used in the body of the function, it should be well
> > formed to call the function with an unrelated type.
>
>
> I don't think so: https://eel.is/c++draft/expr.prim#lambda.closure-5
> says the type of the xobj parameter must be related.

Well, thanks for bringing that to my attention, that makes things
easier but I'm kinda disappointed, I almost wanted an excuse to write
more code. I wonder why Gasper thought this wasn't the case, perhaps it
was a later decision?

Okay, I checked, that paragraph is in the original paper, I thought I
could just take what one of the paper's authors said for granted but I
guess not, I'm not going to make that mistake again. On the other hand,
maybe he just misunderstood my question, what can you do.

Well regardless, that reduces the things I have left to do by a whole
lot.

> > I had begun trying to tackle the case that Gasper mentioned and got the
> > following ICE. I also have another case that ICEs so I've been thinking
> > I don't get to do little changes to fix this. I've been looking at this
> > for a few hours now and given we are past the deadline now I figured I
> > should see what others think.
> >
> > int main()
> > {
> > int x = 42;
> > auto f1 = [x](this auto&& self) {};
> >
> > static_cast<int(*)(int&)>(decltype(f1)::operator());
> > }
>
>
> This should be rejected when we try to instantiate the op() with int.

Yep, absolutely, that is clear now.

Just for the record, clang accepts it, but since I can't think of any
use cases for this I don't think there's any value in supporting it.

>
> > As I said, there is also this case that also ICEs in the same region.
> > It's making me think that some core assumptions are being violated in
> > the code leading up to finish_non_static_data_member.
> >
> > int main()
> > {
> > int x = 42;
> > auto f1 = [x](this auto self) {};
> > }
>
>
> Here I think the problem is in build_capture_proxy:
>
> > /* The proxy variable forwards to the capture field. */
> > object = build_fold_indirect_ref (DECL_ARGUMENTS (fn));
> > object = finish_non_static_data_member (member, object, NULL_TREE);
>
>
> The call to build_fold_indirect_ref assumes that 'this' is a pointer,
> which it is not here. I think you can just make that conditional on it
> being a pointer or reference?
>
> Jason

Thanks, I will take a look at that area.

I'm having trouble fixing the error for this case, the control flow
when the functions are overloaded is much more complex.

struct S {
  void f(this S&) {}
  void f(this S&, int)

  void g() {
    void (*fp)(S&) = &f;
  }
};

This seemed to have fixed the non overloaded case, but I'm also not
very happy with it, it feels kind of icky. Especially since the expr's
location isn't available here, although, it just occurred to me that
the expr's location is probably stored in the node.

typeck.cc:cp_build_addr_expr_1
```
    case BASELINK:
      arg = BASELINK_FUNCTIONS (arg);
      if (DECL_XOBJ_MEMBER_FUNC_P (
        {
          error ("You must qualify taking address of xobj member functions");
	  return error_mark_node;
        }

Anyway, I'm quite tired but I'll to finish off the lambda stuff before
calling it, then I'll run a bootstrap and tests and if all is well I
will submit the patch. I will probably skimp on the changelog and
commit message as that's the part I have the hardest time on,
especially when I'm tired.

Alex
```

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-19 18:36                                                                         ` waffl3x
@ 2023-11-19 20:34                                                                           ` Jason Merrill
  2023-11-19 21:44                                                                             ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-19 20:34 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/19/23 13:36, waffl3x wrote:
> I'm having trouble fixing the error for this case, the control flow
> when the functions are overloaded is much more complex.
> 
> struct S {
>    void f(this S&) {}
>    void f(this S&, int)
> 
>    void g() {
>      void (*fp)(S&) = &f;
>    }
> };
> 
> This seemed to have fixed the non overloaded case, but I'm also not
> very happy with it, it feels kind of icky. Especially since the expr's
> location isn't available here, although, it just occurred to me that
> the expr's location is probably stored in the node.
> 
> typeck.cc:cp_build_addr_expr_1
> ```
>      case BASELINK:
>        arg = BASELINK_FUNCTIONS (arg);
>        if (DECL_XOBJ_MEMBER_FUNC_P (
>          {
>            error ("You must qualify taking address of xobj member functions");
> 	  return error_mark_node;
>          }

The loc variable was set earlier in the function, you can use that.

The overloaded case we want to handle here in 
resolve_address_of_overloaded_function:

>   if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
>       && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
>     {
>       static int explained;
> 
>       if (!(complain & tf_error))
>         return error_mark_node;
> 
>       auto_diagnostic_group d;
>       if (permerror (input_location, "assuming pointer to member %qD", fn)
>           && !explained)
>         {
>           inform (input_location, "(a pointer to member can only be "
>                   "formed with %<&%E%>)", fn);
>           explained = 1;
>         }
>     }

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-19 20:34                                                                           ` Jason Merrill
@ 2023-11-19 21:44                                                                             ` waffl3x
  2023-11-20 14:35                                                                               ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-19 21:44 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches






On Sunday, November 19th, 2023 at 1:34 PM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 11/19/23 13:36, waffl3x wrote:
> 
> > I'm having trouble fixing the error for this case, the control flow
> > when the functions are overloaded is much more complex.
> > 
> > struct S {
> > void f(this S&) {}
> > void f(this S&, int)
> > 
> > void g() {
> > void (*fp)(S&) = &f;
> > }
> > };
> > 
> > This seemed to have fixed the non overloaded case, but I'm also not
> > very happy with it, it feels kind of icky. Especially since the expr's
> > location isn't available here, although, it just occurred to me that
> > the expr's location is probably stored in the node.
> > 
> > typeck.cc:cp_build_addr_expr_1
> > ```
> > case BASELINK:
> > arg = BASELINK_FUNCTIONS (arg);
> > if (DECL_XOBJ_MEMBER_FUNC_P (
> > {
> > error ("You must qualify taking address of xobj member functions");
> > return error_mark_node;
> > }
> 
> 
> The loc variable was set earlier in the function, you can use that.

Will do.

> The overloaded case we want to handle here in
> resolve_address_of_overloaded_function:
> 
> > if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
> > && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
> > {
> > static int explained;
> > 
> > if (!(complain & tf_error))
> > return error_mark_node;
> > 
> > auto_diagnostic_group d;
> > if (permerror (input_location, "assuming pointer to member %qD", fn)
> > && !explained)
> > {
> > inform (input_location, "(a pointer to member can only be "
> > "formed with %<&%E%>)", fn);
> > explained = 1;
> > }
> > }
> 
> 
> Jason

I'll check that out now, I just mostly finished the first lambda crash.

What is the proper way to error out of instantiate_body? What I have
right now is just not recursing down further if theres a problem. Also,
I'm starting to wonder if I should actually be erroring in
instantiate_decl instead.

I guess it will be better to just finish and you can share your
comments upon review though.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-19 21:44                                                                             ` waffl3x
@ 2023-11-20 14:35                                                                               ` Jason Merrill
       [not found]                                                                                 ` <1MdaTybBd=5Fo4uw-Gb23fYyd5GNz7qFqoSe=5Ff5h90LY=5FBdzM2ge2qPSyCuiCLYoYcZSjmVv13fw1LmjQC=5FM2L8raS1fydY5pEJ=5Fvwvv5Z-0k=3D@protonmail.com>
  2023-11-21 10:02                                                                                 ` waffl3x
  0 siblings, 2 replies; 100+ messages in thread
From: Jason Merrill @ 2023-11-20 14:35 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/19/23 16:44, waffl3x wrote:
> 
> 
> 
> 
> 
> On Sunday, November 19th, 2023 at 1:34 PM, Jason Merrill <jason@redhat.com> wrote:
> 
> 
>>
>>
>> On 11/19/23 13:36, waffl3x wrote:
>>
>>> I'm having trouble fixing the error for this case, the control flow
>>> when the functions are overloaded is much more complex.
>>>
>>> struct S {
>>> void f(this S&) {}
>>> void f(this S&, int)
>>>
>>> void g() {
>>> void (*fp)(S&) = &f;
>>> }
>>> };
>>>
>>> This seemed to have fixed the non overloaded case, but I'm also not
>>> very happy with it, it feels kind of icky. Especially since the expr's
>>> location isn't available here, although, it just occurred to me that
>>> the expr's location is probably stored in the node.
>>>
>>> typeck.cc:cp_build_addr_expr_1
>>> ```
>>> case BASELINK:
>>> arg = BASELINK_FUNCTIONS (arg);
>>> if (DECL_XOBJ_MEMBER_FUNC_P (
>>> {
>>> error ("You must qualify taking address of xobj member functions");
>>> return error_mark_node;
>>> }
>>
>>
>> The loc variable was set earlier in the function, you can use that.
> 
> Will do.
> 
>> The overloaded case we want to handle here in
>> resolve_address_of_overloaded_function:
>>
>>> if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
>>> && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
>>> {
>>> static int explained;
>>>
>>> if (!(complain & tf_error))
>>> return error_mark_node;
>>>
>>> auto_diagnostic_group d;
>>> if (permerror (input_location, "assuming pointer to member %qD", fn)
>>> && !explained)
>>> {
>>> inform (input_location, "(a pointer to member can only be "
>>> "formed with %<&%E%>)", fn);
>>> explained = 1;
>>> }
>>> }
>>
>>
>> Jason
> 
> I'll check that out now, I just mostly finished the first lambda crash.
> 
> What is the proper way to error out of instantiate_body? What I have
> right now is just not recursing down further if theres a problem. Also,
> I'm starting to wonder if I should actually be erroring in
> instantiate_decl instead.

I think you want to error in start_preparsed_function, to handle 
template and non-template cases in the same place.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v3 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-20 14:35                                                                               ` Jason Merrill
       [not found]                                                                                 ` <1MdaTybBd=5Fo4uw-Gb23fYyd5GNz7qFqoSe=5Ff5h90LY=5FBdzM2ge2qPSyCuiCLYoYcZSjmVv13fw1LmjQC=5FM2L8raS1fydY5pEJ=5Fvwvv5Z-0k=3D@protonmail.com>
@ 2023-11-21 10:02                                                                                 ` waffl3x
  2023-11-21 13:04                                                                                   ` [PATCH v5 1/1] " waffl3x
  1 sibling, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-21 10:02 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches






On Monday, November 20th, 2023 at 7:35 AM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 11/19/23 16:44, waffl3x wrote:
> 
> > On Sunday, November 19th, 2023 at 1:34 PM, Jason Merrill jason@redhat.com wrote:
> > 
> > > On 11/19/23 13:36, waffl3x wrote:
> > > 
> > > > I'm having trouble fixing the error for this case, the control flow
> > > > when the functions are overloaded is much more complex.
> > > > 
> > > > struct S {
> > > > void f(this S&) {}
> > > > void f(this S&, int)
> > > > 
> > > > void g() {
> > > > void (*fp)(S&) = &f;
> > > > }
> > > > };
> > > > 
> > > > This seemed to have fixed the non overloaded case, but I'm also not
> > > > very happy with it, it feels kind of icky. Especially since the expr's
> > > > location isn't available here, although, it just occurred to me that
> > > > the expr's location is probably stored in the node.
> > > > 
> > > > typeck.cc:cp_build_addr_expr_1
> > > > ```
> > > > case BASELINK:
> > > > arg = BASELINK_FUNCTIONS (arg);
> > > > if (DECL_XOBJ_MEMBER_FUNC_P (
> > > > {
> > > > error ("You must qualify taking address of xobj member functions");
> > > > return error_mark_node;
> > > > }
> > > 
> > > The loc variable was set earlier in the function, you can use that.
> > 
> > Will do.
> > 
> > > The overloaded case we want to handle here in
> > > resolve_address_of_overloaded_function:
> > > 
> > > > if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
> > > > && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
> > > > {
> > > > static int explained;
> > > > 
> > > > if (!(complain & tf_error))
> > > > return error_mark_node;
> > > > 
> > > > auto_diagnostic_group d;
> > > > if (permerror (input_location, "assuming pointer to member %qD", fn)
> > > > && !explained)
> > > > {
> > > > inform (input_location, "(a pointer to member can only be "
> > > > "formed with %<&%E%>)", fn);
> > > > explained = 1;
> > > > }
> > > > }
> > > 
> > > Jason
> > 
> > I'll check that out now, I just mostly finished the first lambda crash.
> > 
> > What is the proper way to error out of instantiate_body? What I have
> > right now is just not recursing down further if theres a problem. Also,
> > I'm starting to wonder if I should actually be erroring in
> > instantiate_decl instead.
> 
> 
> I think you want to error in start_preparsed_function, to handle
> template and non-template cases in the same place.
> 
> Jason

I just started a bootstrap, hopefully everything comes out just fine.
If I don't pass out before the tests finish (and the tests are all
fine) then I'll send it in for review tonight.

I stared at start_preparsed_function for a long while and couldn't
figure out where to start off at. So for now the error handling is
split up between instantiate_body and cp_parser_lambda_declarator_opt.
The latter is super not correct but I've been stuck on this for a long
time now though so I wanted to actually get something that works and
then try to make it better.

This patch is not as final as I would have liked as you can probably
deduce from the previous paragraph. I still have to write tests for a
number of cases, but I'm pretty sure everything works. I was going to
say except for by-value xobj parameters in lambdas with captures, but
that's magically working now too. I was also going to say I don't know
why, but I found where my mistake was (that I fixed without realizing
it was causing the aforementioned problem) so I do know what did it.

So rambling aside, I think everything should work now. To reiterate, I
still have to finish the tests for a few things. There's some
diagnostics I'm super not happy with, and lambda's names still say
static, but I already know how to fix that I think.

I will make an attempt at moving the diagnostics for an unrelated
explicit object parameter in a lambda with captures tomorrow. I just
want to get the almost fully featured patch reviewed ASAP, even if it's
still got some cruft.

As soon as these tests pass I will submit the patch, I'm not going to
split it up today, I'm simply too tired, but I assure you the final
version will properly be split up with a correct commit message and
everything.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-21 10:02                                                                                 ` waffl3x
@ 2023-11-21 13:04                                                                                   ` waffl3x
  2023-11-22  3:22                                                                                     ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-21 13:04 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 754 bytes --]

Bootstrapped and tested on x86_64-linux with no regressions.

Hopefully this patch is legible enough for reviewing purposes, I've not
been feeling the greatest so it was a task to get this finished.
Tomorrow I will look at putting the diagnostics in
start_preparsed_function and also fixing up anything else.

To reiterate in case it wasn't abundantly clear by the barren changelog
and commit message, this version is not intended as the final revision.

Handling re-declarations was kind of nightmarish, so the comments in
there are lengthy, but I am fairly certain I implemented them correctly.

I am going to get some sleep now, hopefully I will feel better tomorrow
and be ready to polish off the patch. Thanks for the patience.

Alex

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-temp-deducing-this.patch --]
[-- Type: text/x-patch; name=0001-temp-deducing-this.patch, Size: 174488 bytes --]

From 2c0fc2b4704743558d595eb00ec046f5554643a6 Mon Sep 17 00:00:00 2001
From: waffl3x <waffl3x@protonmail.com>
Date: Tue, 21 Nov 2023 05:42:38 -0700
Subject: [PATCH] This is a temporary message. gcc/cp/ChangeLog:

	* call.cc (add_candidates):
	(build_over_call):
	* class.cc (add_method):
	(resolve_address_of_overloaded_function):
	* cp-tree.h (struct lang_decl_fn):
	(DECL_IOBJ_MEMBER_FUNC_P):
	(DECL_FUNCTION_XOBJ_FLAG):
	(DECL_XOBJ_MEMBER_FUNC_P):
	(DECL_OBJECT_MEMBER_FUNC_P):
	(DECL_FUNCTION_MEMBER_P):
	(enum auto_deduction_context):
	(TFF_XOBJ_FUNC):
	(enum cp_decl_spec):
	* decl.cc (grokfndecl):
	(grokdeclarator):
	(grok_op_properties):
	(start_preparsed_function):
	* error.cc (dump_function_decl):
	(dump_parameters):
	(function_category):
	* lambda.cc (build_capture_proxy):
	* module.cc (trees_out::lang_decl_bools):
	(trees_in::lang_decl_bools):
	* parser.cc (cp_parser_lambda_declarator_opt):
	(cp_parser_decl_specifier_seq):
	(cp_parser_parameter_declaration):
	(set_and_check_decl_spec_loc):
	* pt.cc (instantiate_body):
	* search.cc (look_for_overrides_here):
	(look_for_overrides_r):
	* semantics.cc (finish_this_expr):
	* tree.cc (build_min_non_dep_op_overload):
	* typeck.cc (cp_build_addr_expr_1):

gcc/testsuite/ChangeLog:

	* g++.dg/cpp23/explicit-obj-basic1.C: New test.
	* g++.dg/cpp23/explicit-obj-basic2.C: New test.
	* g++.dg/cpp23/explicit-obj-basic3.C: New test.
	* g++.dg/cpp23/explicit-obj-basic4.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-cxx-dialect-A.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-B.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-C.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-D.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-E.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics1.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics2.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics4.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics5.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics6.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics7.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda1.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX2.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.
	* g++.dg/cpp23/explicit-obj-redecl.C: New test.
	* g++.dg/cpp23/explicit-obj-redecl2.C: New test.
---
 gcc/cp/call.cc                                | 155 +++---
 gcc/cp/class.cc                               | 205 +++++++-
 gcc/cp/cp-tree.h                              |  41 +-
 gcc/cp/decl.cc                                | 181 ++++++-
 gcc/cp/error.cc                               |   8 +-
 gcc/cp/lambda.cc                              |   8 +-
 gcc/cp/module.cc                              |   2 +
 gcc/cp/parser.cc                              | 159 +++++-
 gcc/cp/pt.cc                                  |  22 +-
 gcc/cp/search.cc                              |  16 +-
 gcc/cp/semantics.cc                           |  35 +-
 gcc/cp/tree.cc                                |  25 +-
 gcc/cp/typeck.cc                              |  23 +
 .../g++.dg/cpp23/explicit-obj-basic1.C        | 113 ++++
 .../g++.dg/cpp23/explicit-obj-basic2.C        |  27 +
 .../g++.dg/cpp23/explicit-obj-basic3.C        | 495 ++++++++++++++++++
 .../g++.dg/cpp23/explicit-obj-basic4.C        | 111 ++++
 .../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-cxx-dialect-A.C |   6 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-B.C |   6 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-C.C |   8 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-D.C |   7 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-E.C |   7 +
 .../g++.dg/cpp23/explicit-obj-diagnostics1.C  | 138 +++++
 .../g++.dg/cpp23/explicit-obj-diagnostics2.C  |  25 +
 .../g++.dg/cpp23/explicit-obj-diagnostics4.C  |  19 +
 .../g++.dg/cpp23/explicit-obj-diagnostics5.C  |  15 +
 .../g++.dg/cpp23/explicit-obj-diagnostics6.C  |  22 +
 .../g++.dg/cpp23/explicit-obj-diagnostics7.C  |  25 +
 .../g++.dg/cpp23/explicit-obj-lambda1.C       |  11 +
 .../g++.dg/cpp23/explicit-obj-lambdaX2.C      |  22 +
 .../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   | 209 ++++++++
 .../cpp23/explicit-obj-ops-requires-mem.C     | 170 ++++++
 .../cpp23/explicit-obj-ops-requires-non-mem.C | 236 +++++++++
 .../g++.dg/cpp23/explicit-obj-redecl.C        | 245 +++++++++
 .../g++.dg/cpp23/explicit-obj-redecl2.C       | 160 ++++++
 45 files changed, 3229 insertions(+), 141 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-basic3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.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-cxx-dialect-A.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX2.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
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 2eb54b5b6ed..82ae5facd20 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -6527,7 +6527,7 @@ 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))
+      if (DECL_OBJECT_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
@@ -9728,14 +9728,9 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
   const vec<tree, va_gc> *args = cand->args;
   tree first_arg = cand->first_arg;
   conversion **convs = cand->convs;
-  conversion *conv;
   tree parm = TYPE_ARG_TYPES (TREE_TYPE (fn));
   int parmlen;
   tree val;
-  int i = 0;
-  int j = 0;
-  unsigned int arg_index = 0;
-  int is_method = 0;
   int nargs;
   tree *argarray;
   bool already_used = false;
@@ -9921,45 +9916,70 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
   if (immediate_invocation_p (STRIP_TEMPLATE (fn)))
     in_consteval_if_p = true;
 
+  auto handle_arg = [fn, flags, complain](tree type,
+  					  tree arg,
+					  int const param_index,
+					  conversion *conv,
+					  bool const conversion_warning)
+    {
+      /* Set user_conv_p on the argument conversions, so rvalue/base handling
+	 knows not to allow any more UDCs.  This needs to happen after we
+	 process cand->warnings.  */
+      if (flags & LOOKUP_NO_CONVERSION)
+	conv->user_conv_p = true;
+
+      tsubst_flags_t const arg_complain
+	= conversion_warning ? complain : complain & ~tf_warning;
+
+      if (arg_complain & tf_warning)
+	maybe_warn_pessimizing_move (arg, type, /*return_p*/false);
+
+      tree val = convert_like_with_context (conv, arg, fn,
+      					    param_index, arg_complain);
+      val = convert_for_arg_passing (type, val, arg_complain);
+      return val;
+    };
+
+  int argarray_size = 0;
+  unsigned int arg_index = 0;
+  int conv_index = 0;
+  int param_index = 0;
+
+  auto consume_object_arg = [&arg_index, &first_arg, args]()
+    {
+      if (!first_arg)
+	return (*args)[arg_index++];
+      tree object_arg = first_arg;
+      first_arg = NULL_TREE;
+      return object_arg;
+    };
+
   /* The implicit parameters to a constructor are not considered by overload
      resolution, and must be of the proper type.  */
   if (DECL_CONSTRUCTOR_P (fn))
     {
-      tree object_arg;
-      if (first_arg != NULL_TREE)
-	{
-	  object_arg = first_arg;
-	  first_arg = NULL_TREE;
-	}
-      else
-	{
-	  object_arg = (*args)[arg_index];
-	  ++arg_index;
-	}
-      argarray[j++] = build_this (object_arg);
+      tree object_arg = consume_object_arg ();
+      argarray[argarray_size++] = build_this (object_arg);
       parm = TREE_CHAIN (parm);
       /* We should never try to call the abstract constructor.  */
       gcc_assert (!DECL_HAS_IN_CHARGE_PARM_P (fn));
 
       if (DECL_HAS_VTT_PARM_P (fn))
 	{
-	  argarray[j++] = (*args)[arg_index];
+	  argarray[argarray_size++] = (*args)[arg_index];
 	  ++arg_index;
 	  parm = TREE_CHAIN (parm);
 	}
     }
   /* Bypass access control for 'this' parameter.  */
-  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
+  else if (DECL_IOBJ_MEMBER_FUNC_P (fn))
     {
-      tree arg = build_this (first_arg != NULL_TREE
-			     ? first_arg
-			     : (*args)[arg_index]);
+      tree arg = build_this (consume_object_arg ());
       tree argtype = TREE_TYPE (arg);
 
       if (arg == error_mark_node)
 	return error_mark_node;
-
-      if (convs[i]->bad_p)
+      if (convs[conv_index++]->bad_p)
 	{
 	  if (complain & tf_error)
 	    {
@@ -10034,25 +10054,37 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
       tree converted_arg = build_base_path (PLUS_EXPR, arg,
 					    base_binfo, 1, complain);
 
-      argarray[j++] = converted_arg;
+      argarray[argarray_size++] = converted_arg;
       parm = TREE_CHAIN (parm);
-      if (first_arg != NULL_TREE)
-	first_arg = NULL_TREE;
+    }
+  else if (DECL_XOBJ_MEMBER_FUNC_P (fn))
+    {
+      /* We currently handle for the case where first_arg is NULL_TREE
+	 in the future this should be changed and the assert reactivated.  */
+      #if 0
+      gcc_assert (first_arg);
+      #endif
+      gcc_assert (cand->num_convs > 0);
+      static constexpr bool conversion_warning = true;
+      tree object_arg = consume_object_arg ();
+      val = handle_arg(TREE_VALUE (parm),
+		       object_arg,
+		       param_index++,
+		       convs[conv_index++],
+		       conversion_warning);
+
+      if (val == error_mark_node)
+        return error_mark_node;
       else
-	++arg_index;
-      ++i;
-      is_method = 1;
+        argarray[argarray_size++] = val;
+      parm = TREE_CHAIN (parm);
     }
 
   gcc_assert (first_arg == NULL_TREE);
   for (; arg_index < vec_safe_length (args) && parm;
-       parm = TREE_CHAIN (parm), ++arg_index, ++i)
+       parm = TREE_CHAIN (parm), ++arg_index, ++param_index, ++conv_index)
     {
-      tree type = TREE_VALUE (parm);
-      tree arg = (*args)[arg_index];
-      bool conversion_warning = true;
-
-      conv = convs[i];
+      tree current_arg = (*args)[arg_index];
 
       /* If the argument is NULL and used to (implicitly) instantiate a
          template function (and bind one of the template arguments to
@@ -10074,47 +10106,38 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
              func(NULL);
            }
       */
-      if (null_node_p (arg)
-          && DECL_TEMPLATE_INFO (fn)
-          && cand->template_decl
-	  && !cand->explicit_targs)
-        conversion_warning = false;
-
-      /* Set user_conv_p on the argument conversions, so rvalue/base handling
-	 knows not to allow any more UDCs.  This needs to happen after we
-	 process cand->warnings.  */
-      if (flags & LOOKUP_NO_CONVERSION)
-	conv->user_conv_p = true;
-
-      tsubst_flags_t arg_complain = complain;
-      if (!conversion_warning)
-	arg_complain &= ~tf_warning;
-
-      if (arg_complain & tf_warning)
-	maybe_warn_pessimizing_move (arg, type, /*return_p*/false);
+      auto determine_conversion_warning = [&]()
+	{
+	  return !(null_node_p (current_arg)
+	    && DECL_TEMPLATE_INFO (fn)
+	    && cand->template_decl
+	    && !cand->explicit_targs);
+	};
 
-      val = convert_like_with_context (conv, arg, fn, i - is_method,
-				       arg_complain);
-      val = convert_for_arg_passing (type, val, arg_complain);
+      val = handle_arg (TREE_VALUE (parm),
+			current_arg,
+			param_index,
+			convs[conv_index],
+			determine_conversion_warning ());
 
       if (val == error_mark_node)
         return error_mark_node;
       else
-        argarray[j++] = val;
+        argarray[argarray_size++] = val;
     }
 
   /* Default arguments */
-  for (; parm && parm != void_list_node; parm = TREE_CHAIN (parm), i++)
+  for (; parm && parm != void_list_node; parm = TREE_CHAIN (parm), param_index++)
     {
       if (TREE_VALUE (parm) == error_mark_node)
 	return error_mark_node;
       val = convert_default_arg (TREE_VALUE (parm),
 				 TREE_PURPOSE (parm),
-				 fn, i - is_method,
+				 fn, param_index,
 				 complain);
       if (val == error_mark_node)
         return error_mark_node;
-      argarray[j++] = val;
+      argarray[argarray_size++] = val;
     }
 
   /* Ellipsis */
@@ -10151,11 +10174,11 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
 	a = convert_arg_to_ellipsis (a, complain);
       if (a == error_mark_node)
 	return error_mark_node;
-      argarray[j++] = a;
+      argarray[argarray_size++] = a;
     }
 
-  gcc_assert (j <= nargs);
-  nargs = j;
+  gcc_assert (argarray_size <= nargs);
+  nargs = argarray_size;
   icip.reset ();
 
   /* Avoid performing argument transformation if warnings are disabled.
@@ -10171,7 +10194,7 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
     {
       tree *fargs = (!nargs ? argarray
 			    : (tree *) alloca (nargs * sizeof (tree)));
-      for (j = 0; j < nargs; j++)
+      for (int j = 0; j < nargs; j++)
 	{
 	  /* For -Wformat undo the implicit passing by hidden reference
 	     done by convert_arg_to_ellipsis.  */
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index 0d8b780ba2f..4537de15fdc 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -1079,8 +1079,8 @@ add_method (tree type, tree method, bool via_using)
       /* Compare the quals on the 'this' parm.  Don't compare
 	 the whole types, as used functions are treated as
 	 coming from the using class in overload resolution.  */
-      if (! DECL_STATIC_FUNCTION_P (fn)
-	  && ! DECL_STATIC_FUNCTION_P (method)
+      if (DECL_IOBJ_MEMBER_FUNC_P (fn)
+	  && DECL_IOBJ_MEMBER_FUNC_P (method)
 	  /* Either both or neither need to be ref-qualified for
 	     differing quals to allow overloading.  */
 	  && (FUNCTION_REF_QUALIFIED (fn_type)
@@ -1089,6 +1089,164 @@ add_method (tree type, tree method, bool via_using)
 	      || type_memfn_rqual (fn_type) != type_memfn_rqual (method_type)))
 	  continue;
 
+      auto get_object_param = [](tree fn)
+	{
+	  gcc_assert (DECL_OBJECT_MEMBER_FUNC_P (fn));
+	  return TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (fn)));
+	};
+      auto reference_qual = [](tree ref)
+	{
+	  gcc_assert (TYPE_REF_P (ref));
+	  return TYPE_REF_IS_RVALUE (ref) ? REF_QUAL_RVALUE
+					  : REF_QUAL_LVALUE;
+	};
+
+      /* Handle special correspondence rules for xobj vs xobj and xobj vs iobj
+	 member function declarations.
+	 We don't worry about static member functions here.  */
+      if ((!DECL_XOBJ_MEMBER_FUNC_P (fn) && !DECL_XOBJ_MEMBER_FUNC_P (method))
+	   || DECL_STATIC_FUNCTION_P (fn) || DECL_STATIC_FUNCTION_P (method))
+        /* Early escape.  */;
+      else if (DECL_XOBJ_MEMBER_FUNC_P (fn)
+	       && DECL_XOBJ_MEMBER_FUNC_P (method))
+        {
+	  tree fn_param = get_object_param (fn);
+	  tree method_param = get_object_param (method);
+	  if (!same_type_p (fn_param, method_param))
+	    continue;
+	}
+      else if (DECL_XOBJ_MEMBER_FUNC_P (fn)
+	       || DECL_XOBJ_MEMBER_FUNC_P (method))
+        {
+	  tree xobj_fn = DECL_XOBJ_MEMBER_FUNC_P (fn) ? fn : method;
+	  /* A reference, pointer, or something else.  */
+	  tree xobj_param = get_object_param (xobj_fn);
+
+	  /* We know an iobj parameter must be a reference. If our xobj
+	     parameter is a pointer, we know this is not a redeclaration.
+	     This also catches array parameters, those are pointers too.  */
+	  if (TYPE_PTR_P (xobj_param))
+	    continue;
+
+	  /* An iobj member function's object parameter can't be an unrelated
+	     type, we know this can't be a redeclaration if the xobj member
+	     function's object parameter is an unrelated type.
+	     If the iobj member function was introduced with a using
+	     declaration, then the type of it's object parameter is still
+	     that of the class we are currently adding a member function to,
+	     so this assumption holds true in that case as well.
+
+	     [over.match.funcs.general.4]
+	     For non-conversion functions that are implicit object member
+	     functions nominated by a using-declaration in a derived class,
+	     the function is considered to be a member of the derived class
+	     for the purpose of defining the type of the implicit object
+	     parameter.
+	     
+	     We don't get to bail yet out even if the xobj parameter is
+	     by-value as elaborated on below.  */
+	  if (DECL_CONTEXT (xobj_fn)
+	      != TYPE_MAIN_VARIANT (non_reference (xobj_param)))
+	    continue;
+
+	  /* From this point on, we know we are dealing with an xobj parameter
+	     that has an object parameter of the same type as the class it
+	     was declared in.
+	     We still don't know if we have a reference or by-value parameter
+	     yet though.  */
+
+	  tree iobj_fn = DECL_IOBJ_MEMBER_FUNC_P (fn) ? fn : method;
+	  tree iobj_fn_type = TREE_TYPE (iobj_fn);
+	  cp_ref_qualifier const iobj_ref_qual
+	    = type_memfn_rqual (iobj_fn_type);
+	  /* I am concerned about the other qualifier bits, for now I will mask
+	     them off.  */
+	  static constexpr cp_cv_quals cv_bits = TYPE_QUAL_VOLATILE
+					       | TYPE_QUAL_CONST;
+	  cp_cv_quals const iobj_cv_quals
+	    = type_memfn_quals (iobj_fn_type) & cv_bits;
+	  /* We need to ignore the ref qualifier of the xobj parameter if the
+	     iobj member function lacks a ref qualifier.
+
+	     [basic.scope.scope.3]
+	     Two non-static member functions have corresponding object
+	     parameters if:
+	     -- exactly one is an implicit object member function with no
+		ref-qualifier and the types of their object parameters
+		([dcl.fct]), after removing top-level references, are the
+		same, or
+	     -- their object parameters have the same type.
+
+	     The cv qualifiers of a by-value parameter are supposed to be
+	     discarded, so we ignore them.
+
+	     [dcl.fct.5]
+	     After producing the list of parameter types, any top-level
+	     cv-qualifiers modifying a parameter type are deleted when
+	     forming the function type.
+
+	     They still need to be taken into account when our xobj parameter
+	     is a reference that is being ignored (according to
+	     [basic.scope.scope.3] quoted above), but when we are actually
+	     dealing with a by-value xobj parameter we can procede following
+	     this table.
+	     | iobj | xobj | equal |
+	     | none | none |   X   |
+	     | none |    c |   X   |
+	     | none |    v |   X   |
+	     | none |   cv |   X   |
+	     |    c | none |   O   |
+	     |    c |    c |   O   |
+	     |    c |    v |   O   |
+	     |    c |   cv |   O   |
+	     |    v | none |   O   |
+	     |    v |    c |   O   |
+	     |    v |    v |   O   |
+	     |    v |   cv |   O   |
+	     |   cv | none |   O   |
+	     |   cv |    c |   O   |
+	     |   cv |    v |   O   |
+	     |   cv |   cv |   O   |
+
+	     Additionally, if the iobj member function is ref qualified,
+	     we aren't ignoring the ref qualifier of the iobj parameter,
+	     so we can't be dealing with a redeclaration in that case either.
+
+	     So to recap, if we have a by-value xobj parameter, we know for
+	     sure that we aren't dealing with a redeclaration if the iobj
+	     member function has any cv-ref qualifiers. The only case where
+	     we might still be dealing with a redeclaration is when the iobj
+	     member function lacks any cv-ref qualification.  */
+	  if (!TYPE_REF_P (xobj_param))
+	    {
+	      if (iobj_ref_qual || iobj_cv_quals)
+		continue;
+	    }
+	  else
+	    {
+	      /* We are dealing with an xobj parameter that is a reference now,
+		 but due to [basic.scope.scope.3] we need to ignore it's
+		 reference qualifier.  */
+	      cp_ref_qualifier const xobj_ref_qual
+		= !TYPE_REF_P (xobj_param) || !iobj_ref_qual
+		  ? REF_QUAL_NONE : reference_qual (xobj_param);
+
+	      /* Even if we are ignoring the reference qualifier, the xobj
+		 parameter was still a reference so we still take the cv
+		 qualifiers into account.  */
+	      cp_cv_quals const xobj_cv_quals
+		= cp_type_quals (TREE_TYPE (xobj_param)) & cv_bits;
+
+	      /* Finally, if the qualifications don't match exactly, this
+		 definitely isn't a redeclaration.  */
+	      if (iobj_ref_qual != xobj_ref_qual
+		  || iobj_cv_quals != xobj_cv_quals)
+		continue;
+	    }
+	}
+      else
+	gcc_unreachable ();
+
       tree real_fn = fn;
       tree real_method = method;
 
@@ -8724,21 +8882,40 @@ resolve_address_of_overloaded_function (tree target_type,
   /* Good, exactly one match.  Now, convert it to the correct type.  */
   fn = TREE_PURPOSE (matches);
 
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
-      && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
+  if (DECL_OBJECT_MEMBER_FUNC_P (fn)
+      && !(complain & tf_ptrmem_ok))
     {
-      static int explained;
-
-      if (!(complain & tf_error))
-	return error_mark_node;
-
-      auto_diagnostic_group d;
-      if (permerror (input_location, "assuming pointer to member %qD", fn)
-	  && !explained)
+      /* Don't disable error even if -fms-extensions is passed, this is a new
+	 feature so we (hopefully) don't need to support the behavior. */
+      if (DECL_XOBJ_MEMBER_FUNC_P (fn))
 	{
-	  inform (input_location, "(a pointer to member can only be "
+	  if (!(complain & tf_error))
+	    return error_mark_node;
+	  auto_diagnostic_group d;
+	  /* Error message could be better, fixit would be ideal.
+	     Should also match the error in typeck.cc:cp_build_addr_expr_1.  */
+	  error_at (input_location,
+		    "taking the address of an explicit object member "
+		    "function must be qualified");
+	  inform (input_location,
+		  "(a pointer to explicit object member function can only be "
 		  "formed with %<&%E%>)", fn);
-	  explained = 1;
+	}
+      else if (DECL_IOBJ_MEMBER_FUNC_P (fn) && !flag_ms_extensions)
+	{
+	  static int explained;
+
+	  if (!(complain & tf_error))
+	    return error_mark_node;
+
+	  auto_diagnostic_group d;
+	  if (permerror (input_location, "assuming pointer to member %qD", fn)
+	      && !explained)
+	    {
+	      inform (input_location, "(a pointer to member can only be "
+				      "formed with %<&%E%>)", fn);
+	      explained = 1;
+	    }
 	}
     }
 
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 98b29e9cf81..cdc98504c99 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,34 @@ 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, use DECL_IOBJ_MEMBER_FUNC_P instead.  */
 #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
   (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
 
+/* Nonzero for FUNCTION_DECL means that this decl is an implicit object
+   member function.  */
+#define DECL_IOBJ_MEMBER_FUNC_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 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 if NODE is a member function with an object argument,
+   in other words, a non-static member function.  */
+#define DECL_OBJECT_MEMBER_FUNC_P(NODE) \
+  (DECL_IOBJ_MEMBER_FUNC_P (NODE) || DECL_XOBJ_MEMBER_FUNC_P (NODE))
+
 /* Nonzero for FUNCTION_DECL means that this decl is a member function
-   (static or non-static).  */
+   (static or object).  */
 #define DECL_FUNCTION_MEMBER_P(NODE) \
-  (DECL_NONSTATIC_MEMBER_FUNCTION_P (NODE) || DECL_STATIC_FUNCTION_P (NODE))
+  (DECL_OBJECT_MEMBER_FUNC_P (NODE) || DECL_STATIC_FUNCTION_P (NODE)) \
 
 /* Nonzero for FUNCTION_DECL means that this member function
    has `this' as const X *const.  */
@@ -6104,7 +6125,9 @@ enum auto_deduction_context
        identical to their defaults.
    TFF_NO_TEMPLATE_BINDINGS: do not print information about the template
        arguments for a function template specialization.
-   TFF_POINTER: we are printing a pointer type.  */
+   TFF_POINTER: we are printing a pointer type.
+   TFF_XOBJ_FUNC: we are printing an explicit object member function's
+       parameters.  */
 
 #define TFF_PLAIN_IDENTIFIER			(0)
 #define TFF_SCOPE				(1)
@@ -6122,6 +6145,7 @@ enum auto_deduction_context
 #define TFF_NO_OMIT_DEFAULT_TEMPLATE_ARGUMENTS	(1 << 12)
 #define TFF_NO_TEMPLATE_BINDINGS		(1 << 13)
 #define TFF_POINTER		                (1 << 14)
+#define TFF_XOBJ_FUNC				(1 << 15)
 
 /* These constants can be used as bit flags to control strip_typedefs.
 
@@ -6264,11 +6288,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 +6311,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..1234544b4fb 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;
@@ -10610,24 +10607,30 @@ grokfndecl (tree ctype,
 	TREE_TYPE (decl) = apply_memfn_quals (TREE_TYPE (decl),
 					      TYPE_UNQUALIFIED,
 					      REF_QUAL_NONE);
-
+      auto_diagnostic_group d;
       if (quals)
-	{
-	  error (ctype
+	error (!ctype
+	       ? G_("non-member function %qD cannot have cv-qualifier")
+	       : !xobj_func_p
 		 ? G_("static member function %qD cannot have cv-qualifier")
-		 : G_("non-member function %qD cannot have cv-qualifier"),
-		 decl);
-	  quals = TYPE_UNQUALIFIED;
-	}
-
+		 : G_("explicit object member function "
+		      "%qD cannot have cv-qualifier"),
+	       decl);
       if (rqual)
-	{
-	  error (ctype
+	error (!ctype
+	       ? G_("non-member function %qD cannot have ref-qualifier")
+	       : !xobj_func_p
 		 ? G_("static member function %qD cannot have ref-qualifier")
-		 : G_("non-member function %qD cannot have ref-qualifier"),
+		 : G_("explicit object member function "
+		      "%qD cannot have ref-qualifier"),
 		 decl);
-	  rqual = REF_QUAL_NONE;
-	}
+
+      if (xobj_func_p && (quals || rqual))
+	inform (DECL_SOURCE_LOCATION (DECL_ARGUMENTS (decl)),
+		"explicit object parameter declared here");
+      quals = TYPE_UNQUALIFIED;
+      rqual = REF_QUAL_NONE;
+
     }
 
   if (deduction_guide_p (decl))
@@ -12998,6 +13001,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 +13118,92 @@ 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 || TREE_PURPOSE (parm_list) != this_identifier)
+		  return NULL_TREE;
+		/* 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 TREE_VALUE (parm_list);
+	      };
+
+	    tree xobj_parm
+	      = find_xobj_parm (declarator->u.function.parameters);
+	    is_xobj_member_function = xobj_parm;
+
+	    if (xobj_parm && cxx_dialect < cxx23)
+	      pedwarn (DECL_SOURCE_LOCATION (xobj_parm), OPT_Wc__23_extensions,
+		       "explicit object member function only available "
+		       "with %<-std=c++23%> or %<-std=gnu++23%>");
+
+	    if (xobj_parm && decl_context == TYPENAME)
+	      {
+		/* We inform in every case, just differently depending on what
+		   case it is.  */
+		auto_diagnostic_group d;
+		bool ptr_type = true;
+		/* If declarator->kind is cdk_function and we are at the end of
+		   the declarator chain, we are looking at a function type.  */
+		if (!declarator->declarator)
+		  {
+		    error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			      "a function type cannot "
+			      "have an explicit object parameter");
+		    ptr_type = false;
+		  }
+		else if (declarator->declarator->kind == cdk_pointer)
+		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			 /* "a pointer to function type cannot "?  */
+			    "a function pointer type cannot "
+			    "have an explicit object parameter");
+		else if (declarator->declarator->kind == cdk_ptrmem)
+		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			    "a pointer to member function type "
+			    "cannot have an explicit object parameter");
+		else
+		  gcc_unreachable ();
+		
+		/* The locations being used here are probably not correct.  */
+		if (ptr_type)
+		  inform (DECL_SOURCE_LOCATION (xobj_parm),
+			  "the type of a pointer to explicit object member "
+			  "function is a regular pointer to function type");
+		else
+		  inform (DECL_SOURCE_LOCATION (xobj_parm),
+			  "the type of an explicit object "
+			  "member function is a regular function type");
+		/* Ideally we should synthesize the correct syntax
+		   for the user, perhaps this could be added later.  */
+	      }
+	    /* Since a valid xobj parm has it's purpose cleared in find_xobj_parm
+	       the first parm node will never erroneously be detected here.  */
+	    {
+	      auto_diagnostic_group d;
+	      bool bad_xobj_parm_encountered = false;
+	      for (tree parm = declarator->u.function.parameters;
+		   parm && parm != void_list_node;
+		   parm = TREE_CHAIN (parm))
+		{
+		  if (TREE_PURPOSE (parm) != this_identifier)
+		    continue;
+		  bad_xobj_parm_encountered = true;
+		  gcc_rich_location bad_xobj_parm
+		    (DECL_SOURCE_LOCATION (TREE_VALUE (parm)));
+		  /* I'm keeping it more basic for now.  */
+		  error_at (&bad_xobj_parm,
+			  "Only the first parameter of a member function "
+			  "can be declared as an explicit object parameter");
+		}
+	      if (bad_xobj_parm_encountered && xobj_parm)
+		inform (DECL_SOURCE_LOCATION (xobj_parm),
+			"Valid explicit object parameter declared here");
+	    }
+
 	    if (reqs)
 	      error_at (location_of (reqs), "requires-clause on return type");
 	    reqs = declarator->u.function.requires_clause;
@@ -13400,6 +13491,38 @@ grokdeclarator (const cp_declarator *declarator,
 		  explicitp = 2;
 	      }
 
+	    if (xobj_parm)
+	      {
+		if (!ctype
+		    && decl_context == NORMAL
+		    && (in_namespace
+			|| !declarator->declarator->u.id.qualifying_scope))
+		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			    "a non-member function cannot have "
+			    "an explicit object parameter");
+		else
+		  {
+		    if (virtualp)
+		      {
+			auto_diagnostic_group d;
+			error_at (declspecs->locations[ds_virtual],
+				  "an explicit object member function cannot be "
+				  "%<virtual%>");
+			inform (DECL_SOURCE_LOCATION (xobj_parm),
+				"explicit object parameter declared here");
+			virtualp = false;
+		      }
+		    if (staticp >= 2)
+		      {
+			auto_diagnostic_group d;
+			error_at (declspecs->locations[ds_storage_class],
+				  "an explicit object member function cannot be "
+				  "%<static%>");
+			inform (DECL_SOURCE_LOCATION (xobj_parm),
+				"explicit object parameter declared here");
+		      }
+		  }
+	      }
 	    tree pushed_scope = NULL_TREE;
 	    if (funcdecl_p
 		&& decl_context != FIELD
@@ -14177,6 +14300,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 +14523,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 +14859,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 +15754,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
@@ -17382,6 +17509,8 @@ start_preparsed_function (tree decl1, tree attrs, int flags)
   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);
   else
     {
       ctype = DECL_FRIEND_CONTEXT (decl1);
@@ -17609,7 +17738,9 @@ start_preparsed_function (tree decl1, tree attrs, int flags)
   /* Start the statement-tree, start the tree now.  */
   DECL_SAVED_TREE (decl1) = push_stmt_list ();
 
-  if (ctype && !doing_friend && !DECL_STATIC_FUNCTION_P (decl1))
+  /* 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)))
     {
       /* We know that this was set up by `grokclassfn'.  We do not
 	 wait until `store_parm_decls', since evil parse errors may
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 0ed69bca6fc..c9870d45706 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -1831,7 +1831,9 @@ dump_function_decl (cxx_pretty_printer *pp, tree t, int flags)
 
   if (!(flags & TFF_NO_FUNCTION_ARGUMENTS))
     {
-      dump_parameters (pp, parmtypes, flags);
+      dump_parameters (pp, parmtypes,
+		       DECL_XOBJ_MEMBER_FUNC_P (t) ? TFF_XOBJ_FUNC | flags
+						    : flags);
 
       if (TREE_CODE (fntype) == METHOD_TYPE)
 	{
@@ -1910,6 +1912,8 @@ dump_parameters (cxx_pretty_printer *pp, tree parmtypes, int flags)
   for (first = 1; parmtypes != void_list_node;
        parmtypes = TREE_CHAIN (parmtypes))
     {
+      if (first && flags & TFF_XOBJ_FUNC)
+	pp_string (pp, "this ");
       if (!first)
 	pp_separate_with_comma (pp);
       first = 0;
@@ -3685,6 +3689,8 @@ function_category (tree fn)
 	return _("In destructor %qD");
       else if (LAMBDA_FUNCTION_P (fn))
 	return _("In lambda function");
+      else if (DECL_XOBJ_MEMBER_FUNC_P (fn))
+	return _("In explicit object member function %qD");
       else
 	return _("In member function %qD");
     }
diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
index a359bc6ee8d..53d678f2bed 100644
--- a/gcc/cp/lambda.cc
+++ b/gcc/cp/lambda.cc
@@ -405,8 +405,12 @@ build_capture_proxy (tree member, tree init)
   fn = lambda_function (closure);
   lam = CLASSTYPE_LAMBDA_EXPR (closure);
 
-  /* The proxy variable forwards to the capture field.  */
-  object = build_fold_indirect_ref (DECL_ARGUMENTS (fn));
+  object = DECL_ARGUMENTS (fn);
+  /* Making this conditional prevents the ICE, but does not actually work
+     correctly.  */
+  if (INDIRECT_TYPE_P (TREE_TYPE (object)))
+    /* The proxy variable forwards to the capture field.  */
+    object = build_fold_indirect_ref (object);
   object = finish_non_static_data_member (member, object, NULL_TREE);
   if (REFERENCE_REF_P (object))
     object = TREE_OPERAND (object, 0);
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index c1c8c226bc1..8723f23c4ae 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5683,6 +5683,7 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      WB (lang->u.fn.xobj_func);
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5751,6 +5752,7 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      RB (lang->u.fn.xobj_func);
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 20e18365906..f2deeca40f2 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -11750,6 +11750,31 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
     }
   else if (cxx_dialect < cxx23)
     omitted_parms_loc = cp_lexer_peek_token (parser->lexer)->location;
+  /* We just need to peek here,
+     grokdeclarator does the rest with this so don't mutate it.  */
+  tree const xobj_param
+    = param_list && TREE_PURPOSE (param_list) == this_identifier
+      ? TREE_VALUE (param_list) : NULL_TREE;
+
+  if (xobj_param
+      && (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE
+	  || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr)))
+    {
+      /* Since a lambda's type is anonymous, we can assume an explicit object
+	 parameter is unrelated... at least I believe so. I can think of a
+	 paradoxical case that almost works, where a class is forward declared
+	 and then defined deriving from a lambda that has an explicit object
+	 parameter of that class type, but the only way that can work is with
+	 decltype... okay yeah I think it can work for a struct defined in
+	 block scope, but I'm leaving this as is until I can confirm my
+	 hypothesis.  */
+      if (!TEMPLATE_PARM_P (non_reference (TREE_TYPE (xobj_param))))
+	{
+          error("a lambda with captures may not have an explicit object "
+		"parameter of an unrelated type");
+	  LAMBDA_EXPR_CAPTURE_LIST (lambda_expr) = NULL_TREE;
+	}
+    }
 
   /* In the decl-specifier-seq of the lambda-declarator, each
      decl-specifier shall either be mutable or constexpr.  */
@@ -11767,24 +11792,81 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
 	       "%<-std=gnu++2b%>");
       omitted_parms_loc = UNKNOWN_LOCATION;
     }
-
   if (lambda_specs.storage_class == sc_mutable)
     {
-      LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
-      quals = TYPE_UNQUALIFIED;
+      /* This might not be the most appropriate place for this, but I'm going
+	 to hold back on changing it for the time being since we are short on
+	 time. It's not really a parser error, it's more a semantic error.
+	 But this is where the other errors were so...  */
+      if (xobj_param)
+	{
+	  auto_diagnostic_group d;
+	  error_at (lambda_specs.locations[ds_storage_class],
+		    "%<mutable%> lambda specifier "
+		    "with explicit object parameter");
+	  inform (DECL_SOURCE_LOCATION(xobj_param),
+		  "explicit object parameter declared here");
+	  /* Error reporting here is a little awkward, if the type of the
+	     object parameter is deduced, we should tell them the lambda
+	     is effectively already const, or to make the param const if it is
+	     not, but if it is deduced and taken by value shouldn't we say
+	     that it's taken by copy and won't mutate?
+	     Seems right to me, but it's a little strange.  */
+
+	  /* An xobj parameter with an unrelated type should already have been
+	     diagnosed, that means we definitely have a template type param.
+	     We don't suppress these informs right now when the xobj param is
+	     unrelated, we probably should though.  */
+	  if (!TYPE_REF_P (TREE_TYPE (xobj_param)))
+	    inform (DECL_SOURCE_LOCATION(xobj_param),
+		    "a by-value explicit object parameter will never be "
+		    "mutated");
+	  else if (TYPE_READONLY (TREE_TYPE (TREE_TYPE (xobj_param))))
+	    inform (DECL_SOURCE_LOCATION(xobj_param),
+		    "declare the explicit object parameter without const");
+	  else
+	    inform (DECL_SOURCE_LOCATION(xobj_param),
+		    "explicit object parameter is already mutable");
+	}
+      else
+	{
+	  LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
+	  quals = TYPE_UNQUALIFIED;
+	}
     }
   else if (lambda_specs.storage_class == sc_static)
     {
+      bool error_emitted = false;
       if (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE
 	  || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr))
-	error_at (lambda_specs.locations[ds_storage_class],
-		  "%<static%> lambda specifier with lambda capture");
-      else
+	{
+	  error_at (lambda_specs.locations[ds_storage_class],
+		    "%<static%> lambda specifier with lambda capture");
+	  error_emitted = true;
+	}
+      if (xobj_param)
+	{
+	  auto_diagnostic_group d;
+	  error_at (lambda_specs.locations[ds_storage_class],
+		    "%<static%> lambda specifier "
+		    "with explicit object parameter");
+	  inform (DECL_SOURCE_LOCATION(xobj_param),
+		  "explicit object parameter declared here");
+	  error_emitted = true;
+	}
+      if (!error_emitted)
 	{
 	  LAMBDA_EXPR_STATIC_P (lambda_expr) = 1;
 	  quals = TYPE_UNQUALIFIED;
 	}
     }
+  if (xobj_param)
+    {
+      quals = TYPE_UNQUALIFIED;
+      if (TYPE_REF_P (xobj_param)
+	  && !(cp_type_quals (TREE_TYPE (xobj_param)) & TYPE_QUAL_CONST))
+        LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
+    }
 
   tx_qual = cp_parser_tx_qualifier_opt (parser);
   if (omitted_parms_loc && tx_qual)
@@ -11899,7 +11981,8 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
       {
 	DECL_INITIALIZED_IN_CLASS_P (fco) = 1;
 	DECL_ARTIFICIAL (fco) = 1;
-	if (!LAMBDA_EXPR_STATIC_P (lambda_expr))
+	if (!LAMBDA_EXPR_STATIC_P (lambda_expr)
+	    && !DECL_XOBJ_MEMBER_FUNC_P (fco))
 	  /* Give the object parameter a different name.  */
 	  DECL_NAME (DECL_ARGUMENTS (fco)) = closure_identifier;
 	DECL_SET_LAMBDA_FUNCTION (fco, true);
@@ -16019,6 +16102,8 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
   /* Assume no class or enumeration type is declared.  */
   *declares_class_or_enum = 0;
 
+  /* Keep a token that additionally will be used for diagnostics.  */
+  cp_token *first_specifier = NULL;
   /* Keep reading specifiers until there are no more to read.  */
   while (true)
     {
@@ -16091,6 +16176,40 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
 	    decl_specs->locations[ds_attribute] = token->location;
 	  continue;
 	}
+      /* We know by this point that the token is not part of an attribute.  */
+      if (!first_specifier)
+	first_specifier = token;
+      /* 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);
+	  if (token != first_specifier)
+	    {
+	      /* Don't emit diagnostics if we have already seen "this",
+		 leave it for set_and_check_decl_spec_loc.  */
+	      if (decl_specs->locations[ds_this] == 0)
+		{
+		  auto_diagnostic_group d;
+		  gcc_rich_location richloc (token->location);
+		  /* Ideally we synthesize a full rewrite, at the moment
+		     there are issues with it though.
+		     It rewrites "f(S this & s)" correctly, but fails
+		     to rewrite "f(const this S s)" correctly. It also
+		     does not handle "f(S& this s)" correctly at all.  */
+		  richloc.add_fixit_remove ();
+		  richloc.add_fixit_insert_before (first_specifier->location,
+						   "this ");
+		  error_at (&richloc,
+			    "%<this%> must be the first specifier "
+			    "in a parameter declaration");
+		}
+	    }
+	  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
@@ -25421,12 +25540,14 @@ cp_parser_parameter_declaration (cp_parser *parser,
   /* The restriction on defining new types applies only to the type
      of the parameter, not to the default argument.  */
   parser->type_definition_forbidden_message = saved_message;
-
+  cp_token *eq_token = NULL;
   /* If the next token is `=', then process a default argument.  */
   if (cp_lexer_next_token_is (parser->lexer, CPP_EQ))
     {
       tree type = decl_specifiers.type;
       token = cp_lexer_peek_token (parser->lexer);
+      /* Used for diagnostics with an xobj parameter.  */
+      eq_token = token;
       if (declarator)
 	declarator->init_loc = token->location;
       /* If we are defining a class, then the tokens that make up the
@@ -25495,6 +25616,25 @@ 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))
+    {
+      if (default_argument)
+	{
+	  /* If there is a default_argument, eq_token should always be set.  */
+	  gcc_assert(eq_token);
+	  location_t param_with_init_loc
+	    = make_location (eq_token->location,
+			     decl_spec_token_start->location,
+			     input_location);
+	  error_at (param_with_init_loc,
+		    "an explicit object parameter "
+		    "may not have a default argument");
+	}
+      /* 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 +34013,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/pt.cc b/gcc/cp/pt.cc
index 86c95b278ba..9a8ebb4e5d7 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -26740,8 +26740,26 @@ instantiate_body (tree pattern, tree args, tree d, bool nested_p)
 			tf_warning_or_error, d);
       else
 	{
-	  tsubst_stmt (DECL_SAVED_TREE (code_pattern), args,
-		       tf_warning_or_error, DECL_TI_TEMPLATE (d));
+	  bool error_emitted = false;
+	  if (LAMBDA_FUNCTION_P (d) && DECL_XOBJ_MEMBER_FUNC_P (d))
+	    {
+	      tree lambda_expr = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (d));
+	      if (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE
+		  || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr))
+		{
+		  gcc_assert (TREE_VEC_LENGTH (args) > 0);
+		  tree object_arg = TREE_VEC_ELT (args, 0);
+		  tree lambda = DECL_CONTEXT (d);
+		  if (!same_or_base_type_p (lambda, non_reference (object_arg)))
+		    {
+		      error ("call to lambda with captures object type must be the lambda or derived from the lambda");
+		      error_emitted = true;
+		    }
+		}
+	    }
+	  if (!error_emitted)
+	    tsubst_stmt (DECL_SAVED_TREE (code_pattern), args,
+		         tf_warning_or_error, DECL_TI_TEMPLATE (d));
 
 	  /* Set the current input_location to the end of the function
 	     so that finish_function knows where we are.  */
diff --git a/gcc/cp/search.cc b/gcc/cp/search.cc
index cd80f285ac9..08c22bcd5d8 100644
--- a/gcc/cp/search.cc
+++ b/gcc/cp/search.cc
@@ -2212,11 +2212,14 @@ look_for_overrides_here (tree type, tree fndecl)
 	/* Not a virtual.  */;
       else if (DECL_CONTEXT (fn) != type)
 	/* Introduced with a using declaration.  */;
-      else if (DECL_STATIC_FUNCTION_P (fndecl))
+      else if (DECL_STATIC_FUNCTION_P (fndecl)
+	       || DECL_XOBJ_MEMBER_FUNC_P (fndecl))
 	{
 	  tree btypes = TYPE_ARG_TYPES (TREE_TYPE (fn));
 	  tree dtypes = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
-	  if (compparms (TREE_CHAIN (btypes), dtypes))
+	  if (compparms (TREE_CHAIN (btypes),
+			 DECL_XOBJ_MEMBER_FUNC_P (fndecl)
+			   ? TREE_CHAIN (dtypes) : dtypes))
 	    return fn;
 	}
       else if (same_signature_p (fndecl, fn))
@@ -2243,6 +2246,15 @@ look_for_overrides_r (tree type, tree fndecl)
 	  error ("%q+#D cannot be declared", fndecl);
 	  error ("  since %q+#D declared in base class", fn);
 	}
+      else if (DECL_XOBJ_MEMBER_FUNC_P (fndecl))
+        {
+	  auto_diagnostic_group d;
+	  error_at (DECL_SOURCE_LOCATION (fndecl),
+		    "explicit object member function "
+		    "overrides virtual function");
+	  inform (DECL_SOURCE_LOCATION (fn),
+		  "virtual function declared here");
+	}
       else
 	{
 	  /* It's definitely virtual, even if not explicitly set.  */
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 52044be7af8..512b2f45950 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -45,6 +45,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gomp-constants.h"
 #include "predict.h"
 #include "memmodel.h"
+#include "gcc-rich-location.h"
 
 /* There routines provide a modular interface to perform many parsing
    operations.  They may therefore be used during actual parsing, or
@@ -3085,7 +3086,39 @@ finish_this_expr (void)
     return rvalue (result);
 
   tree fn = current_nonlambda_function ();
-  if (fn && DECL_STATIC_FUNCTION_P (fn))
+  if (fn && DECL_XOBJ_MEMBER_FUNC_P (fn))
+    {
+      auto_diagnostic_group d;
+      error ("%<this%> is unavailable for explicit object member "
+	     "functions");
+      /* I can imagine doing a fixit here, suggesting replacing
+	 this / *this / this-> with &name / name / "name." but it would be
+	 very difficult to get it perfect and I've been advised against
+	 making imperfect fixits.
+	 Perhaps it would be as simple as the replacements listed,
+	 even if one is move'ing/forward'ing, if the replacement is just
+	 done in the same place, it will be exactly what the user wants?
+	 Even if this is true though, there's still a problem of getting the
+	 context of the expression to find which tokens to replace.
+	 I would really like for this to be possible though.
+	 I will decide whether or not to persue this after review.  */
+      tree xobj_parm = DECL_ARGUMENTS (fn);
+      gcc_assert (xobj_parm);
+      tree parm_name = DECL_NAME (xobj_parm);
+      if (parm_name)
+	inform (DECL_SOURCE_LOCATION (xobj_parm),
+		"use explicit object parameter %qD instead",
+		parm_name);
+      else
+	{
+          gcc_rich_location xobj_loc (DECL_SOURCE_LOCATION (xobj_parm));
+	  xobj_loc.add_fixit_insert_after(" self");
+	  inform (DECL_SOURCE_LOCATION (xobj_parm),
+		  "name and use the explicit object parameter instead");
+	  /* Maybe suggest self as a name here?  */
+	}
+    }
+  else if (fn && DECL_STATIC_FUNCTION_P (fn))
     error ("%<this%> is unavailable for static member functions");
   else if (fn && processing_contract_condition && DECL_CONSTRUCTOR_P (fn))
     error ("invalid use of %<this%> before it is valid");
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index 417c92ba76f..d861593d9e4 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -3659,7 +3659,7 @@ build_min_non_dep_op_overload (enum tree_code op,
   nargs = call_expr_nargs (non_dep);
 
   expected_nargs = cp_tree_code_length (op);
-  if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE
+  if (DECL_OBJECT_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 +3677,25 @@ 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 (DECL_OBJECT_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 +3730,7 @@ 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 (DECL_OBJECT_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..1aa6545ca4c 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7133,6 +7133,19 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 
     case BASELINK:
       arg = BASELINK_FUNCTIONS (arg);
+      if (DECL_XOBJ_MEMBER_FUNC_P (arg))
+	{
+	  /* Error should match that in
+	     class.cc:resolve_address_of_overloaded_function.
+	     We really should do a fixit here.  */
+	  error_at (input_location,
+		    "taking the address of an explicit object member "
+		    "function must be qualified");
+	  inform (input_location,
+		  "(a pointer to explicit object member function can only be "
+		  "formed with %<&%E%>)", arg);
+	  return error_mark_node;
+	}
       /* Fall through.  */
 
     case OVERLOAD:
@@ -7164,6 +7177,16 @@ 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;
 
+	/* Pull out the function_decl for a single xobj member function,
+	   similar to the BASELINK case above. If we did the same optimization
+	   as we do for single static member functions (passing in a BASELINK)
+	   then it could be handled the same too.  */
+	if (DECL_XOBJ_MEMBER_FUNC_P (t))
+	  {
+	    arg = t;
+	    break;
+	  }
+
 	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-basic3.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic3.C
new file mode 100644
index 00000000000..5f27d45012e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic3.C
@@ -0,0 +1,495 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// bogus diagnosis of valid declarations as redeclarations
+// tests for by-value are elsewhere (todo: add filename)
+
+// each group has 8 overloads that each take
+// lvalue ref to S
+// rvalue ref to S
+// lvalue c ref to S
+// rvalue c ref to S
+// lvalue v ref to S
+// rvalue v ref to S
+// lvalue cv ref to S
+// rvalue cv ref to S
+// where S is the struct the function is declared in
+
+// only xobj (the most basic case)
+
+struct S {
+  void f(this S &);
+  void f(this S &&);
+  void f(this S const&);
+  void f(this S const&&);
+  void f(this S volatile&);
+  void f(this S volatile&&);
+  void f(this S const volatile&);
+  void f(this S const volatile&&);
+};
+
+// I* has the 1 xobj 7 iobj cases
+// X* has the 7 xobj 1 iobj cases
+// *0 has the unique function first, the rest after
+// *1 has the unique function last, the rest after
+// *2 has the functions in the order stated above
+// xobj first, 1 xobj, 7 iobj
+
+// (yes there are some redundant cases)
+
+// unique first, 1 xobj 7 iobj
+
+struct I0 {
+  void f0(this I0&);
+  void f0() &&;
+  void f0() const&;
+  void f0() const&&;
+  void f0() volatile&;
+  void f0() volatile&&;
+  void f0() const volatile&;
+  void f0() const volatile&&;
+
+  void f1(this I0&&);
+  void f1() &;
+  void f1() const&;
+  void f1() const&&;
+  void f1() volatile&;
+  void f1() volatile&&;
+  void f1() const volatile&;
+  void f1() const volatile&&;
+
+  void fc0(this I0 const&);
+  void fc0() &;
+  void fc0() &&;
+  void fc0() const&&;
+  void fc0() volatile&;
+  void fc0() volatile&&;
+  void fc0() const volatile&;
+  void fc0() const volatile&&;
+
+  void fc1(this I0 const&&);
+  void fc1() &;
+  void fc1() &&;
+  void fc1() const&;
+  void fc1() volatile&;
+  void fc1() volatile&&;
+  void fc1() const volatile&;
+  void fc1() const volatile&&;
+
+  void fv0(this I0 volatile&);
+  void fv0() &;
+  void fv0() &&;
+  void fv0() const&;
+  void fv0() const&&;
+  void fv0() volatile&&;
+  void fv0() const volatile&;
+  void fv0() const volatile&&;
+
+  void fv1(this I0 volatile&&);
+  void fv1() &;
+  void fv1() &&;
+  void fv1() const&;
+  void fv1() const&&;
+  void fv1() volatile&;
+  void fv1() const volatile&;
+  void fv1() const volatile&&;
+
+  void fcv0(this I0 const volatile&);
+  void fcv0() &;
+  void fcv0() &&;
+  void fcv0() const&;
+  void fcv0() const&&;
+  void fcv0() volatile&;
+  void fcv0() volatile&&;
+  void fcv0() const volatile&&;
+
+  void fcv1(this I0 const volatile&&);
+  void fcv1() &;
+  void fcv1() &&;
+  void fcv1() const&;
+  void fcv1() const&&;
+  void fcv1() volatile&;
+  void fcv1() volatile&&;
+  void fcv1() const volatile&;
+};
+
+// unique last, 1 xobj 7 iobj
+
+struct I1 {
+  void f0() &&;
+  void f0() const&;
+  void f0() const&&;
+  void f0() volatile&;
+  void f0() volatile&&;
+  void f0() const volatile&;
+  void f0() const volatile&&;
+  void f0(this I1&);
+
+  void f1() &;
+  void f1() const&;
+  void f1() const&&;
+  void f1() volatile&;
+  void f1() volatile&&;
+  void f1() const volatile&;
+  void f1() const volatile&&;
+  void f1(this I1&&);
+
+  void fc0() &;
+  void fc0() &&;
+  void fc0() const&&;
+  void fc0() volatile&;
+  void fc0() volatile&&;
+  void fc0() const volatile&;
+  void fc0() const volatile&&;
+  void fc0(this I1 const&);
+
+  void fc1() &;
+  void fc1() &&;
+  void fc1() const&;
+  void fc1() volatile&;
+  void fc1() volatile&&;
+  void fc1() const volatile&;
+  void fc1() const volatile&&;
+  void fc1(this I1 const&&);
+
+  void fv0() &;
+  void fv0() &&;
+  void fv0() const&;
+  void fv0() const&&;
+  void fv0() volatile&&;
+  void fv0() const volatile&;
+  void fv0() const volatile&&;
+  void fv0(this I1 volatile&);
+
+  void fv1() &;
+  void fv1() &&;
+  void fv1() const&;
+  void fv1() const&&;
+  void fv1() volatile&;
+  void fv1() const volatile&;
+  void fv1() const volatile&&;
+  void fv1(this I1 volatile&&);
+
+  void fcv0() &;
+  void fcv0() &&;
+  void fcv0() const&;
+  void fcv0() const&&;
+  void fcv0() volatile&;
+  void fcv0() volatile&&;
+  void fcv0() const volatile&&;
+  void fcv0(this I1 const volatile&);
+
+  void fcv1() &;
+  void fcv1() &&;
+  void fcv1() const&;
+  void fcv1() const&&;
+  void fcv1() volatile&;
+  void fcv1() volatile&&;
+  void fcv1() const volatile&;
+  void fcv1(this I1 const volatile&&);
+};
+
+// ordered, 1 xobj 7 iobj
+
+struct I2 {
+  void f0(this I2&);
+  void f0() &&;
+  void f0() const&;
+  void f0() const&&;
+  void f0() volatile&;
+  void f0() volatile&&;
+  void f0() const volatile&;
+  void f0() const volatile&&;
+
+  void f1() &;
+  void f1(this I2&&);
+  void f1() const&;
+  void f1() const&&;
+  void f1() volatile&;
+  void f1() volatile&&;
+  void f1() const volatile&;
+  void f1() const volatile&&;
+
+  void fc0() &;
+  void fc0() &&;
+  void fc0(this I2 const&);
+  void fc0() const&&;
+  void fc0() volatile&;
+  void fc0() volatile&&;
+  void fc0() const volatile&;
+  void fc0() const volatile&&;
+
+  void fc1() &;
+  void fc1() &&;
+  void fc1() const&;
+  void fc1(this I2 const&&);
+  void fc1() volatile&;
+  void fc1() volatile&&;
+  void fc1() const volatile&;
+  void fc1() const volatile&&;
+
+  void fv0() &;
+  void fv0() &&;
+  void fv0() const&;
+  void fv0() const&&;
+  void fv0(this I2 volatile&);
+  void fv0() volatile&&;
+  void fv0() const volatile&;
+  void fv0() const volatile&&;
+
+  void fv1() &;
+  void fv1() &&;
+  void fv1() const&;
+  void fv1() const&&;
+  void fv1() volatile&;
+  void fv1(this I2 volatile&&);
+  void fv1() const volatile&;
+  void fv1() const volatile&&;
+
+  void fcv0() &;
+  void fcv0() &&;
+  void fcv0() const&;
+  void fcv0() const&&;
+  void fcv0() volatile&;
+  void fcv0() volatile&&;
+  void fcv0(this I2 const volatile&);
+  void fcv0() const volatile&&;
+
+  void fcv1() &;
+  void fcv1() &&;
+  void fcv1() const&;
+  void fcv1() const&&;
+  void fcv1() volatile&;
+  void fcv1() volatile&&;
+  void fcv1() const volatile&;
+  void fcv1(this I2 const volatile&&);
+};
+
+
+// iobj first, 7 xobj, 1 iobj
+
+struct X0 {
+  void f0() &;
+  void f0(this X0 &&);
+  void f0(this X0 const&);
+  void f0(this X0 const&&);
+  void f0(this X0 volatile&);
+  void f0(this X0 volatile&&);
+  void f0(this X0 const volatile&);
+  void f0(this X0 const volatile&&);
+
+  void f1() &&;
+  void f1(this X0 &);
+  void f1(this X0 const&);
+  void f1(this X0 const&&);
+  void f1(this X0 volatile&);
+  void f1(this X0 volatile&&);
+  void f1(this X0 const volatile&);
+  void f1(this X0 const volatile&&);
+
+  void fc0() const&;
+  void fc0(this X0 &);
+  void fc0(this X0 &&);
+  void fc0(this X0 const&&);
+  void fc0(this X0 volatile&);
+  void fc0(this X0 volatile&&);
+  void fc0(this X0 const volatile&);
+  void fc0(this X0 const volatile&&);
+
+  void fc1() const&&;
+  void fc1(this X0 &);
+  void fc1(this X0 &&);
+  void fc1(this X0 const&);
+  void fc1(this X0 volatile&);
+  void fc1(this X0 volatile&&);
+  void fc1(this X0 const volatile&);
+  void fc1(this X0 const volatile&&);
+
+  void fv0() volatile&;
+  void fv0(this X0 &);
+  void fv0(this X0 &&);
+  void fv0(this X0 const&);
+  void fv0(this X0 const&&);
+  void fv0(this X0 volatile&&);
+  void fv0(this X0 const volatile&);
+  void fv0(this X0 const volatile&&);
+
+  void fv1() volatile&&;
+  void fv1(this X0 &);
+  void fv1(this X0 &&);
+  void fv1(this X0 const&);
+  void fv1(this X0 const&&);
+  void fv1(this X0 volatile&);
+  void fv1(this X0 const volatile&);
+  void fv1(this X0 const volatile&&);
+
+  void fcv0() const volatile&;
+  void fcv0(this X0 &);
+  void fcv0(this X0 &&);
+  void fcv0(this X0 const&);
+  void fcv0(this X0 const&&);
+  void fcv0(this X0 volatile&);
+  void fcv0(this X0 volatile&&);
+  void fcv0(this X0 const volatile&&);
+
+  void fcv1() const volatile&&;
+  void fcv1(this X0 &);
+  void fcv1(this X0 &&);
+  void fcv1(this X0 const&);
+  void fcv1(this X0 const&&);
+  void fcv1(this X0 volatile&);
+  void fcv1(this X0 volatile&&);
+  void fcv1(this X0 const volatile&);
+};
+
+// iobj last, 7 xobj 1 iobj
+
+struct X1 {
+  void f0(this X1 &&);
+  void f0(this X1 const&);
+  void f0(this X1 const&&);
+  void f0(this X1 volatile&);
+  void f0(this X1 volatile&&);
+  void f0(this X1 const volatile&);
+  void f0(this X1 const volatile&&);
+  void f0() &;
+
+  void f1(this X1 &);
+  void f1(this X1 const&);
+  void f1(this X1 const&&);
+  void f1(this X1 volatile&);
+  void f1(this X1 volatile&&);
+  void f1(this X1 const volatile&);
+  void f1(this X1 const volatile&&);
+  void f1() &&;
+
+  void fc0(this X1 &);
+  void fc0(this X1 &&);
+  void fc0(this X1 const&&);
+  void fc0(this X1 volatile&);
+  void fc0(this X1 volatile&&);
+  void fc0(this X1 const volatile&);
+  void fc0(this X1 const volatile&&);
+  void fc0() const&;
+
+  void fc1(this X1 &);
+  void fc1(this X1 &&);
+  void fc1(this X1 const&);
+  void fc1(this X1 volatile&);
+  void fc1(this X1 volatile&&);
+  void fc1(this X1 const volatile&);
+  void fc1(this X1 const volatile&&);
+  void fc1() const&&;
+
+  void fv0(this X1 &);
+  void fv0(this X1 &&);
+  void fv0(this X1 const&);
+  void fv0(this X1 const&&);
+  void fv0(this X1 volatile&&);
+  void fv0(this X1 const volatile&);
+  void fv0(this X1 const volatile&&);
+  void fv0() volatile&;
+
+  void fv1(this X1 &);
+  void fv1(this X1 &&);
+  void fv1(this X1 const&);
+  void fv1(this X1 const&&);
+  void fv1(this X1 volatile&);
+  void fv1(this X1 const volatile&);
+  void fv1(this X1 const volatile&&);
+  void fv1() volatile&&;
+
+  void fcv0(this X1 &);
+  void fcv0(this X1 &&);
+  void fcv0(this X1 const&);
+  void fcv0(this X1 const&&);
+  void fcv0(this X1 volatile&);
+  void fcv0(this X1 volatile&&);
+  void fcv0(this X1 const volatile&&);
+  void fcv0() const volatile&;
+
+  void fcv1(this X1 &);
+  void fcv1(this X1 &&);
+  void fcv1(this X1 const&);
+  void fcv1(this X1 const&&);
+  void fcv1(this X1 volatile&);
+  void fcv1(this X1 volatile&&);
+  void fcv1(this X1 const volatile&);
+  void fcv1() const volatile&&;
+};
+
+// ordered, 7 xobj 1 iobj
+
+struct X2 {
+  void f0() &;
+  void f0(this X2 &&);
+  void f0(this X2 const&);
+  void f0(this X2 const&&);
+  void f0(this X2 volatile&);
+  void f0(this X2 volatile&&);
+  void f0(this X2 const volatile&);
+  void f0(this X2 const volatile&&);
+
+  void f1(this X2 &);
+  void f1() &&;
+  void f1(this X2 const&);
+  void f1(this X2 const&&);
+  void f1(this X2 volatile&);
+  void f1(this X2 volatile&&);
+  void f1(this X2 const volatile&);
+  void f1(this X2 const volatile&&);
+
+  void fc0(this X2 &);
+  void fc0(this X2 &&);
+  void fc0() const&;
+  void fc0(this X2 const&&);
+  void fc0(this X2 volatile&);
+  void fc0(this X2 volatile&&);
+  void fc0(this X2 const volatile&);
+  void fc0(this X2 const volatile&&);
+
+  void fc1(this X2 &);
+  void fc1(this X2 &&);
+  void fc1(this X2 const&);
+  void fc1() const&&;
+  void fc1(this X2 volatile&);
+  void fc1(this X2 volatile&&);
+  void fc1(this X2 const volatile&);
+  void fc1(this X2 const volatile&&);
+
+  void fv0(this X2 &);
+  void fv0(this X2 &&);
+  void fv0(this X2 const&);
+  void fv0(this X2 const&&);
+  void fv0() volatile&;
+  void fv0(this X2 volatile&&);
+  void fv0(this X2 const volatile&);
+  void fv0(this X2 const volatile&&);
+
+  void fv1(this X2 &);
+  void fv1(this X2 &&);
+  void fv1(this X2 const&);
+  void fv1(this X2 const&&);
+  void fv1(this X2 volatile&);
+  void fv1() volatile&&;
+  void fv1(this X2 const volatile&);
+  void fv1(this X2 const volatile&&);
+
+  void fcv0(this X2 &);
+  void fcv0(this X2 &&);
+  void fcv0(this X2 const&);
+  void fcv0(this X2 const&&);
+  void fcv0(this X2 volatile&);
+  void fcv0(this X2 volatile&&);
+  void fcv0() const volatile&;
+  void fcv0(this X2 const volatile&&);
+
+  void fcv1(this X2 &);
+  void fcv1(this X2 &&);
+  void fcv1(this X2 const&);
+  void fcv1(this X2 const&&);
+  void fcv1(this X2 volatile&);
+  void fcv1(this X2 volatile&&);
+  void fcv1(this X2 const volatile&);
+  void fcv1() const volatile&&;
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C
new file mode 100644
index 00000000000..042ac0478e9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C
@@ -0,0 +1,111 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// valid overloading of iobj member functions without ref qualifiers
+// with xobj member functions (and vice-versa)
+
+// this is the most you can mix these, it may look short but it does test
+// all allowed cases (other than by-value and unrelated types)
+
+// [over.match.funcs.general.4]
+// For implicit object member functions, the type of the implicit
+// object parameter is
+// -- “lvalue reference to cv X” for functions declared
+//    without a ref-qualifier or with the & ref-qualifier
+// -- “rvalue reference to cv X” for functions declared with
+//    the && ref-qualifier
+
+// [basic.scope.scope.3]
+// Two non-static member functions have corresponding object
+// parameters if:
+// -- exactly one is an implicit object member function with no
+//    ref-qualifier and the types of their object parameters
+//    ([dcl.fct]), after removing top-level references, are the
+//    same, or
+
+// in simpler terms, only the cv qualification of the explicit/implicit object
+// member function matters for determining whether these are redeclarations or overloads
+
+// xobj first, iobj last
+
+struct S0 {
+  void f(this S0 &);
+  void f(this S0 &&);
+  void f() const;
+  void f() volatile;
+  void f() const volatile;
+
+  void fc(this S0 const&);
+  void fc(this S0 const&&);
+  void fc();
+  void fc() volatile;
+  void fc() const volatile;
+
+  void fv(this S0 volatile&);
+  void fv(this S0 volatile&&);
+  void fv();
+  void fv() const;
+  void fv() const volatile;
+
+  void fcv(this S0 const volatile&);
+  void fcv(this S0 const volatile&&);
+  void fcv();
+  void fcv() const;
+  void fcv() volatile;
+};
+
+// iobj first, xobj last
+
+struct S1 {
+  void f() const;
+  void f() volatile;
+  void f() const volatile;
+  void f(this S1 &);
+  void f(this S1 &&);
+
+  void fc();
+  void fc() volatile;
+  void fc() const volatile;
+  void fc(this S1 const&);
+  void fc(this S1 const&&);
+
+  void fv();
+  void fv() const;
+  void fv() const volatile;
+  void fv(this S1 volatile&);
+  void fv(this S1 volatile&&);
+
+  void fcv();
+  void fcv() const;
+  void fcv() volatile;
+  void fcv(this S1 const volatile&);
+  void fcv(this S1 const volatile&&);
+};
+
+// in order
+
+struct S2 {
+  void f(this S2 &);
+  void f(this S2 &&);
+  void f() const;
+  void f() volatile;
+  void f() const volatile;
+
+  void fc();
+  void fc(this S2 const&);
+  void fc(this S2 const&&);
+  void fc() volatile;
+  void fc() const volatile;
+
+  void fv();
+  void fv() const;
+  void fv(this S2 volatile&);
+  void fv(this S2 volatile&&);
+  void fv() const volatile;
+
+  void fcv();
+  void fcv() const;
+  void fcv() volatile;
+  void fcv(this S2 const volatile&);
+  void fcv(this S2 const volatile&&);
+};
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-cxx-dialect-A.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-A.C
new file mode 100644
index 00000000000..033745d5784
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-A.C
@@ -0,0 +1,6 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+struct S {
+    void f(this S); // { dg-bogus {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C
new file mode 100644
index 00000000000..4774750255b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C
@@ -0,0 +1,6 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+
+struct S {
+    void f(this S); // { dg-error {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C
new file mode 100644
index 00000000000..7dbdc64a302
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C
@@ -0,0 +1,8 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+// don't pass in -pedantic-errors
+// { dg-options "" }
+
+struct S {
+    void f(this S); // { dg-warning {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C
new file mode 100644
index 00000000000..dccb0cf07df
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C
@@ -0,0 +1,7 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+// { dg-options "-Wno-c++23-extensions" }
+
+struct S {
+    void f(this S); // { dg-bogus {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C
new file mode 100644
index 00000000000..1924212fb23
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C
@@ -0,0 +1,7 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+// { dg-options "-Wno-c++23-extensions -pedantic-errors" }
+
+struct S {
+    void f(this S); // { dg-bogus {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C
new file mode 100644
index 00000000000..7b94f7e9c12
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C
@@ -0,0 +1,138 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of xobj member functions that have member function qualifiers.
+
+struct S {
+    void f_value_0(this S) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_value_1(this S) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_value_2(this S) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_value_3(this S) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_value_4(this S) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_value_5(this S) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_6(this S) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_7(this S) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_8(this S) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_9(this S) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_A(this S) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_ref_0(this S&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_ref_1(this S&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_ref_2(this S&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_ref_3(this S&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_ref_4(this S&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_ref_5(this S&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_6(this S&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_7(this S&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_8(this S&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_9(this S&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_A(this S&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_refref_0(this S&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_refref_1(this S&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_refref_2(this S&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_refref_3(this S&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_refref_4(this S&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_refref_5(this S&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_6(this S&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_7(this S&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_8(this S&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_9(this S&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_A(this S&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_cref_0(this S const&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cref_1(this S const&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cref_2(this S const&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cref_3(this S const&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cref_4(this S const&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cref_5(this S const&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_6(this S const&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_7(this S const&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_8(this S const&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_9(this S const&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_A(this S const&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_crefref_0(this S const&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_crefref_1(this S const&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_crefref_2(this S const&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_crefref_3(this S const&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_crefref_4(this S const&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_crefref_5(this S const&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_6(this S const&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_7(this S const&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_8(this S const&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_9(this S const&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_A(this S const&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_vref_0(this S volatile&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vref_1(this S volatile&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vref_2(this S volatile&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vref_3(this S volatile&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vref_4(this S volatile&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vref_5(this S volatile&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_6(this S volatile&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_7(this S volatile&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_8(this S volatile&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_9(this S volatile&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_A(this S volatile&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_vrefref_0(this S volatile&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vrefref_1(this S volatile&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vrefref_2(this S volatile&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vrefref_3(this S volatile&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vrefref_4(this S volatile&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vrefref_5(this S volatile&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_6(this S volatile&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_7(this S volatile&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_8(this S volatile&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_9(this S volatile&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_A(this S volatile&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_cvref_0(this S const volatile&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvref_1(this S const volatile&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvref_2(this S const volatile&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvref_3(this S const volatile&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvref_4(this S const volatile&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvref_5(this S const volatile&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_6(this S const volatile&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_7(this S const volatile&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_8(this S const volatile&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_9(this S const volatile&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_A(this S const volatile&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_cvrefref_0(this S const volatile&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvrefref_1(this S const volatile&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvrefref_2(this S const volatile&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvrefref_3(this S const volatile&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvrefref_4(this S const volatile&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvrefref_5(this S const volatile&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_6(this S const volatile&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_7(this S const volatile&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_8(this S const volatile&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_9(this S const volatile&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_A(this S const volatile&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    template<typename Self> void d_templ_0(this Self&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    template<typename Self> void d_templ_1(this Self&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    template<typename Self> void d_templ_2(this Self&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    template<typename Self> void d_templ_3(this Self&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    template<typename Self> void d_templ_4(this Self&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    template<typename Self> void d_templ_5(this Self&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_6(this Self&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_7(this Self&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_8(this Self&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_9(this Self&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_A(this Self&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void d_auto_0(this auto&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void d_auto_1(this auto&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void d_auto_2(this auto&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void d_auto_3(this auto&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void d_auto_4(this auto&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void d_auto_5(this auto&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_6(this auto&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_7(this auto&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_8(this auto&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_9(this auto&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_A(this auto&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C
new file mode 100644
index 00000000000..7c930859dae
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C
@@ -0,0 +1,25 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of incorrect uses of 'this' in declarations and definitions
+
+using func_type = void(this int); // { dg-line func_type_line }
+// { dg-error "a function type cannot have an explicit object parameter" "" { target *-*-* } func_type_line }
+// { dg-note "the type of an explicit object member function is a regular function type" "" { target *-*-* } func_type_line }
+
+using func_ptr_type = void(*)(this int); // { dg-line func_ptr_type_line }
+// { dg-error "a function pointer type cannot have an explicit object parameter" "" { target *-*-* } func_ptr_type_line }
+// { dg-note "the type of a pointer to explicit object member function is a regular pointer to function type" "" { target *-*-* } func_ptr_type_line }
+
+struct S {
+    static void f(this S) {} // { dg-line static_member_func_line }
+};
+// { dg-error "an explicit object member function cannot be 'static'" "" { target *-*-* } static_member_func_line }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } static_member_func_line }
+
+using mem_func_type = void (S::*)(this S&); // { dg-line mem_func_type_line }
+// { dg-error "a pointer to member function type cannot have an explicit object parameter" "" { target *-*-* } mem_func_type_line }
+// { dg-note "the type of a pointer to explicit object member function is a regular pointer to function type" "" { target *-*-* } mem_func_type_line }
+
+void f(this int); // { dg-error "a non-member function cannot have an explicit object parameter" }
+void f(this int) {} // { dg-error "a non-member function cannot have an explicit object parameter" }
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C
new file mode 100644
index 00000000000..1f743a8509f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C
@@ -0,0 +1,19 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of an xobj parameter declared with a default argument
+
+struct S {
+  void f0(this S = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+  void f1(this S = {}); // { dg-error "an explicit object parameter may not have a default argument" }
+  void f2(this S);
+  void f10(this S s = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+  void f11(this S s = {}); // { dg-error "an explicit object parameter may not have a default argument" }
+  void f12(this S s);
+};
+
+void S::f1(this S) {}
+void S::f2(this S = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+
+void S::f11(this S s) {}
+void S::f12(this S s = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C
new file mode 100644
index 00000000000..65a5c63f20b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C
@@ -0,0 +1,15 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// location diagnostic text when an error is emitted from an xobj member function
+// this does not test for specific ill-formed code, just the additional diagnostic message
+
+// { dg-message "In explicit object member function" "" { target *-*-* } 0 }
+
+struct S {
+  void f(this S s) {
+    // The specific diagnosis issued here does not matter
+    // we just need to force an error to be emitted
+    +s; // { dg-error "" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C
new file mode 100644
index 00000000000..e56d6265ea1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C
@@ -0,0 +1,22 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of invalid uses of 'this' in body of xobj member functions
+
+// { dg-message "In explicit object member function" "" { target *-*-* } 0 }
+
+struct S0 {
+  int _n;
+  void f(this S0& s) { // { dg-note "use explicit object parameter 's' instead" } 
+    this->_n = 10; // { dg-error "'this' is unavailable for explicit object member functions" }
+    // suppress unused variable warning
+    static_cast<void>(s);
+  }
+};
+
+struct S1 {
+  int _n;
+  void f(this S1&) { // { dg-note "name and use the explicit object parameter instead" }
+    this->_n = 10; // { dg-error "'this' is unavailable for explicit object member functions" }
+  }
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C
new file mode 100644
index 00000000000..17ba23df77e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C
@@ -0,0 +1,25 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis when taking address of an unqualified xobj member function
+
+struct S {
+  static void static_f(S&) {}
+  void iobj_member_f() {}
+  void xobj_member_f(this S&) {}
+
+  void test() {
+    using func_ptr_type = void(*)(S&);
+    // using mem_func_ptr_type = void (S::*)();
+
+    // allowed (not testing for this)
+    // func_ptr_type static_f_ptr = &static_f;
+
+    // not allowed (also not testing for this)
+    // mem_func_ptr_type iobj_mem_f_ptr = &iobj_member_f;
+
+    // not allowed (this is what we are testing for)
+    func_ptr_type xobj_mem_f_ptr = &xobj_member_f; // { dg-error "taking the address of an explicit object member function must be qualified" }
+  }
+};
+
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-lambdaX2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX2.C
new file mode 100644
index 00000000000..e792eafb80b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX2.C
@@ -0,0 +1,22 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// recursive lambdas
+
+inline constexpr int correct_result = 5 + 4 + 3 + 2 + 1; 
+
+int main()
+{
+  auto cl0 = [](this auto&& self, int n)      -> int { return n ? self(n - 1) + n : 0; };
+  auto cl1 = [](this auto const& self, int n) -> int { return n ? self(n - 1) + n : 0; };
+  auto cl2 = [](this auto self, int n)        -> int { return n ? self(n - 1) + n : 0; };
+  auto cl3 = [](this auto&& self, int n)     { if (!n) return 0; else return self(n - 1) + n; };
+  auto cl4 = [](this auto const& self, int n){ if (!n) return 0; else return self(n - 1) + n; };
+  auto cl5 = [](this auto self, int n)       { if (!n) return 0; else return self(n - 1) + n; };
+  if (cl0(5) != correct_result) __builtin_abort ();
+  if (cl1(5) != correct_result) __builtin_abort ();
+  if (cl2(5) != correct_result) __builtin_abort ();
+  if (cl3(5) != correct_result) __builtin_abort ();
+  if (cl4(5) != correct_result) __builtin_abort ();
+  if (cl5(5) != correct_result) __builtin_abort ();
+}
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..134c7e99a29
--- /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_OPS(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..9b7af676e4f
--- /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_OPS(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..5e0a8d993bd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem.h
@@ -0,0 +1,209 @@
+// tests for ops that must be member functions are seperate
+
+// the name of the class refers to the type of it's member functions xobj parameter
+
+#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, int) { return self; }	\
+  TYPE operator*(this TYPE self) { return self; }		\
+  TYPE operator->*(this TYPE self, int) { 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, 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) { 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) <=> 0;	\
+			\
+  *(OPERAND);		\
+  (OPERAND) ->* 0;	\
+  &(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) <=> 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))));			\
+  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..f9a642ad483
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-mem.C
@@ -0,0 +1,170 @@
+// 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" }
+
+// 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_VALID(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&>; }, "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 const&>; }, "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..9f9c7296157
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C
@@ -0,0 +1,236 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// well-formed and ill-formed uses of non-member capable operators in a requires expression
+
+#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.
+
+template<typename T, typename U>
+concept same_as = __is_same(T, U);
+
+#define TEST_INVALID(OPERAND, CORRECT_TYPE) \
+  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) <=> 0; }, "Unexpected success calling operator <=> with " #OPERAND);	\
+														\
+  static_assert(!requires{ *(OPERAND); }, "Unexpected success calling operator unary* with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) ->* 0; }, "Unexpected success calling operator ->* with " #OPERAND);	\
+  /* We need to check the return type to confirm the built-in operator was not selected.  */			\
+  static_assert(!requires{ {&(OPERAND)} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected success calling operator unary& with " #OPERAND);					\
+  static_assert(!requires{ {(OPERAND), 0} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected success calling operator , with " #OPERAND);
+
+#define TEST_VALID(OPERAND, CORRECT_TYPE) \
+  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) <=> 0; }, "Unexpected failure calling operator <=> with " #OPERAND);	\
+														\
+  static_assert(requires{ *(OPERAND); }, "Unexpected failure calling operator unary* with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) ->* 0; }, "Unexpected failure calling operator ->* with " #OPERAND);	\
+  /* We need to check the return type to confirm we selected our overload, not the built-in operator.  */	\
+  static_assert(requires{ {&(OPERAND)} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected failure calling operator unary& with " #OPERAND);					\
+  static_assert(requires{ {(OPERAND), 0} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected failure calling operator , with " #OPERAND);
+
+// Return types need to be tested for the deduced case
+
+#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) <=> 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)} -> 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, DepValue)
+  TEST_VALID(static_cast<DepValue&&>(value), DepValue)
+  TEST_VALID(static_cast<DepValue const&>(value), DepValue)
+  TEST_VALID(static_cast<DepValue const&&>(value), DepValue)
+}
+
+template<typename DepLRef = LRef>
+void test_l_ref()
+{
+  DepLRef l_ref{};
+  TEST_VALID(l_ref, DepLRef&)
+  TEST_INVALID(static_cast<DepLRef&&>(l_ref), DepLRef&)
+  TEST_INVALID(static_cast<DepLRef const&>(l_ref), DepLRef&)
+  TEST_INVALID(static_cast<DepLRef const&&>(l_ref), DepLRef&)
+}
+
+template<typename DepRRef = RRef>
+void test_r_ref()
+{
+  DepRRef r_ref{};
+  TEST_INVALID(r_ref, DepRRef&&)
+  TEST_VALID(static_cast<DepRRef&&>(r_ref), DepRRef&&)
+  TEST_INVALID(static_cast<DepRRef const&>(r_ref), DepRRef&&)
+  TEST_INVALID(static_cast<DepRRef const&&>(r_ref), DepRRef&&)
+}
+
+template<typename DepConstLRef = ConstLRef>
+void test_const_l_ref()
+{
+  DepConstLRef const_l_ref{};
+  TEST_VALID(const_l_ref, DepConstLRef const&)
+  TEST_VALID(static_cast<DepConstLRef&&>(const_l_ref), DepConstLRef const&)
+  TEST_VALID(static_cast<DepConstLRef const&>(const_l_ref), DepConstLRef const&)
+  TEST_VALID(static_cast<DepConstLRef const&&>(const_l_ref), DepConstLRef const&)
+}
+
+template<typename DepConstRRef = ConstRRef>
+void test_const_r_ref()
+{
+  DepConstRRef const_r_ref{};
+  TEST_INVALID(const_r_ref, DepConstRRef const&&)
+  TEST_VALID(static_cast<DepConstRRef&&>(const_r_ref), DepConstRRef const&&)
+  TEST_INVALID(static_cast<DepConstRRef const&>(const_r_ref), DepConstRRef const&&)
+  TEST_VALID(static_cast<DepConstRRef const&&>(const_r_ref), DepConstRRef const&&)
+}
+
+template<typename DepDeduced = Deduced>
+void test_deduced()
+{
+  DepDeduced 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();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C
new file mode 100644
index 00000000000..a1e49c88750
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C
@@ -0,0 +1,245 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// redeclarations of xobj/iobj member functions where the iobj member function
+// is not ref qualified
+
+// it does not make sense to check for the inverse in this test (7 iobj, 1 xobj)
+// because you are not allowed to overload iobj member functions without ref qualifiers
+// with those that do (and vice versa)
+
+// iobj first
+
+struct S0 {
+  void f0(); // { dg-note "previous declaration" }
+  void f0(this S0 &&); // { dg-error "cannot be overloaded with" }
+  void f0(this S0 const&); // { dg-bogus "" }
+  void f0(this S0 const&&); // { dg-bogus "" }
+  void f0(this S0 volatile&); // { dg-bogus "" }
+  void f0(this S0 volatile&&); // { dg-bogus "" }
+  void f0(this S0 const volatile&); // { dg-bogus "" }
+  void f0(this S0 const volatile&&); // { dg-bogus "" }
+
+  void f1(); // { dg-note "previous declaration" }
+  void f1(this S0 &); // { dg-error "cannot be overloaded with" }
+  void f1(this S0 const&); // { dg-bogus "" }
+  void f1(this S0 const&&); // { dg-bogus "" }
+  void f1(this S0 volatile&); // { dg-bogus "" }
+  void f1(this S0 volatile&&); // { dg-bogus "" }
+  void f1(this S0 const volatile&); // { dg-bogus "" }
+  void f1(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fc0() const; // { dg-note "previous declaration" }
+  void fc0(this S0 &); // { dg-bogus "" }
+  void fc0(this S0 &&); // { dg-bogus "" }
+  void fc0(this S0 const&&); // { dg-error "cannot be overloaded with" }
+  void fc0(this S0 volatile&); // { dg-bogus "" }
+  void fc0(this S0 volatile&&); // { dg-bogus "" }
+  void fc0(this S0 const volatile&); // { dg-bogus "" }
+  void fc0(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fc1() const; // { dg-note "previous declaration" }
+  void fc1(this S0 &); // { dg-bogus "" }
+  void fc1(this S0 &&); // { dg-bogus "" }
+  void fc1(this S0 const&); // { dg-error "cannot be overloaded with" }
+  void fc1(this S0 volatile&); // { dg-bogus "" }
+  void fc1(this S0 volatile&&); // { dg-bogus "" }
+  void fc1(this S0 const volatile&); // { dg-bogus "" }
+  void fc1(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fv0() volatile; // { dg-note "previous declaration" }
+  void fv0(this S0 &); // { dg-bogus "" }
+  void fv0(this S0 &&); // { dg-bogus "" }
+  void fv0(this S0 const&); // { dg-bogus "" }
+  void fv0(this S0 const&&); // { dg-bogus "" }
+  void fv0(this S0 volatile&&); // { dg-error "cannot be overloaded with" }
+  void fv0(this S0 const volatile&); // { dg-bogus "" }
+  void fv0(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fv1() volatile; // { dg-note "previous declaration" }
+  void fv1(this S0 &); // { dg-bogus "" }
+  void fv1(this S0 &&); // { dg-bogus "" }
+  void fv1(this S0 const&); // { dg-bogus "" }
+  void fv1(this S0 const&&); // { dg-bogus "" }
+  void fv1(this S0 volatile&); // { dg-error "cannot be overloaded with" }
+  void fv1(this S0 const volatile&); // { dg-bogus "" }
+  void fv1(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fcv0() const volatile; // { dg-note "previous declaration" }
+  void fcv0(this S0 &); // { dg-bogus "" }
+  void fcv0(this S0 &&); // { dg-bogus "" }
+  void fcv0(this S0 const&); // { dg-bogus "" }
+  void fcv0(this S0 const&&); // { dg-bogus "" }
+  void fcv0(this S0 volatile&); // { dg-bogus "" }
+  void fcv0(this S0 volatile&&); // { dg-bogus "" }
+  void fcv0(this S0 const volatile&&); // { dg-error "cannot be overloaded with" }
+
+  void fcv1() const volatile; // { dg-note "previous declaration" }
+  void fcv1(this S0 &); // { dg-bogus "" }
+  void fcv1(this S0 &&); // { dg-bogus "" }
+  void fcv1(this S0 const&); // { dg-bogus "" }
+  void fcv1(this S0 const&&); // { dg-bogus "" }
+  void fcv1(this S0 volatile&); // { dg-bogus "" }
+  void fcv1(this S0 volatile&&); // { dg-bogus "" }
+  void fcv1(this S0 const volatile&); // { dg-error "cannot be overloaded with" }
+}; // { dg-bogus "" }
+
+// iobj last
+
+struct S1 {
+  void f0(this S1 &&); // { dg-note "previous declaration" }
+  void f0(this S1 const&); // { dg-bogus "" }
+  void f0(this S1 const&&); // { dg-bogus "" }
+  void f0(this S1 volatile&); // { dg-bogus "" }
+  void f0(this S1 volatile&&); // { dg-bogus "" }
+  void f0(this S1 const volatile&); // { dg-bogus "" }
+  void f0(this S1 const volatile&&); // { dg-bogus "" }
+  void f0(); // { dg-error "cannot be overloaded with" }
+
+  void f1(this S1 &); // { dg-note "previous declaration" }
+  void f1(this S1 const&); // { dg-bogus "" }
+  void f1(this S1 const&&); // { dg-bogus "" }
+  void f1(this S1 volatile&); // { dg-bogus "" }
+  void f1(this S1 volatile&&); // { dg-bogus "" }
+  void f1(this S1 const volatile&); // { dg-bogus "" }
+  void f1(this S1 const volatile&&); // { dg-bogus "" }
+  void f1(); // { dg-error "cannot be overloaded with" }
+
+  void fc0(this S1 &); // { dg-bogus "" }
+  void fc0(this S1 &&); // { dg-bogus "" }
+  void fc0(this S1 const&&); // { dg-note "previous declaration" }
+  void fc0(this S1 volatile&); // { dg-bogus "" }
+  void fc0(this S1 volatile&&); // { dg-bogus "" }
+  void fc0(this S1 const volatile&); // { dg-bogus "" }
+  void fc0(this S1 const volatile&&); // { dg-bogus "" }
+  void fc0() const; // { dg-error "cannot be overloaded with" }
+
+  void fc1(this S1 &); // { dg-bogus "" }
+  void fc1(this S1 &&); // { dg-bogus "" }
+  void fc1(this S1 const&); // { dg-note "previous declaration" }
+  void fc1(this S1 volatile&); // { dg-bogus "" }
+  void fc1(this S1 volatile&&); // { dg-bogus "" }
+  void fc1(this S1 const volatile&); // { dg-bogus "" }
+  void fc1(this S1 const volatile&&); // { dg-bogus "" }
+  void fc1() const; // { dg-error "cannot be overloaded with" }
+
+  void fv0(this S1 &); // { dg-bogus "" }
+  void fv0(this S1 &&); // { dg-bogus "" }
+  void fv0(this S1 const&); // { dg-bogus "" }
+  void fv0(this S1 const&&); // { dg-bogus "" }
+  void fv0(this S1 volatile&&); // { dg-note "previous declaration" }
+  void fv0(this S1 const volatile&); // { dg-bogus "" }
+  void fv0(this S1 const volatile&&); // { dg-bogus "" }
+  void fv0() volatile; // { dg-error "cannot be overloaded with" }
+
+  void fv1(this S1 &); // { dg-bogus "" }
+  void fv1(this S1 &&); // { dg-bogus "" }
+  void fv1(this S1 const&); // { dg-bogus "" }
+  void fv1(this S1 const&&); // { dg-bogus "" }
+  void fv1(this S1 volatile&); // { dg-note "previous declaration" }
+  void fv1(this S1 const volatile&); // { dg-bogus "" }
+  void fv1(this S1 const volatile&&); // { dg-bogus "" }
+  void fv1() volatile; // { dg-error "cannot be overloaded with" }
+
+  void fcv0(this S1 &); // { dg-bogus "" }
+  void fcv0(this S1 &&); // { dg-bogus "" }
+  void fcv0(this S1 const&); // { dg-bogus "" }
+  void fcv0(this S1 const&&); // { dg-bogus "" }
+  void fcv0(this S1 volatile&); // { dg-bogus "" }
+  void fcv0(this S1 volatile&&); // { dg-bogus "" }
+  void fcv0(this S1 const volatile&&); // { dg-note "previous declaration" }
+  void fcv0() const volatile; // { dg-error "cannot be overloaded with" }
+
+  void fcv1(this S1 &); // { dg-bogus "" }
+  void fcv1(this S1 &&); // { dg-bogus "" }
+  void fcv1(this S1 const&); // { dg-bogus "" }
+  void fcv1(this S1 const&&); // { dg-bogus "" }
+  void fcv1(this S1 volatile&); // { dg-bogus "" }
+  void fcv1(this S1 volatile&&); // { dg-bogus "" }
+  void fcv1(this S1 const volatile&); // { dg-note "previous declaration" }
+  void fcv1() const volatile; // { dg-error "cannot be overloaded with" }
+};
+
+// in order (iobj replacing one of the following in each group)
+// lvalue ref to S
+// rvalue ref to S
+// lvalue c ref to S
+// rvalue c ref to S
+// lvalue v ref to S
+// rvalue v ref to S
+// lvalue cv ref to S
+// rvalue cv ref to S
+
+struct S2 {
+  void f0(); // { dg-note "previous declaration" }
+  void f0(this S2 &&); // { dg-error "cannot be overloaded with" }
+  void f0(this S2 const&); // { dg-bogus "" }
+  void f0(this S2 const&&); // { dg-bogus "" }
+  void f0(this S2 volatile&); // { dg-bogus "" }
+  void f0(this S2 volatile&&); // { dg-bogus "" }
+  void f0(this S2 const volatile&); // { dg-bogus "" }
+  void f0(this S2 const volatile&&); // { dg-bogus "" }
+
+  void f1(this S2 &); // { dg-note "previous declaration" }
+  void f1(); // { dg-error "cannot be overloaded with" }
+  void f1(this S2 const&); // { dg-bogus "" }
+  void f1(this S2 const&&); // { dg-bogus "" }
+  void f1(this S2 volatile&); // { dg-bogus "" }
+  void f1(this S2 volatile&&); // { dg-bogus "" }
+  void f1(this S2 const volatile&); // { dg-bogus "" }
+  void f1(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fc0(this S2 &); // { dg-bogus "" }
+  void fc0(this S2 &&); // { dg-bogus "" }
+  void fc0() const; // { dg-note "previous declaration" }
+  void fc0(this S2 const&&); // { dg-error "cannot be overloaded with" }
+  void fc0(this S2 volatile&); // { dg-bogus "" }
+  void fc0(this S2 volatile&&); // { dg-bogus "" }
+  void fc0(this S2 const volatile&); // { dg-bogus "" }
+  void fc0(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fc1(this S2 &); // { dg-bogus "" }
+  void fc1(this S2 &&); // { dg-bogus "" }
+  void fc1(this S2 const&); // { dg-note "previous declaration" }
+  void fc1() const; // { dg-error "cannot be overloaded with" }
+  void fc1(this S2 volatile&); // { dg-bogus "" }
+  void fc1(this S2 volatile&&); // { dg-bogus "" }
+  void fc1(this S2 const volatile&); // { dg-bogus "" }
+  void fc1(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fv0(this S2 &); // { dg-bogus "" }
+  void fv0(this S2 &&); // { dg-bogus "" }
+  void fv0(this S2 const&); // { dg-bogus "" }
+  void fv0(this S2 const&&); // { dg-bogus "" }
+  void fv0() volatile; // { dg-note "previous declaration" }
+  void fv0(this S2 volatile&&); // { dg-error "cannot be overloaded with" }
+  void fv0(this S2 const volatile&); // { dg-bogus "" }
+  void fv0(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fv1(this S2 &); // { dg-bogus "" }
+  void fv1(this S2 &&); // { dg-bogus "" }
+  void fv1(this S2 const&); // { dg-bogus "" }
+  void fv1(this S2 const&&); // { dg-bogus "" }
+  void fv1(this S2 volatile&); // { dg-note "previous declaration" }
+  void fv1() volatile; // { dg-error "cannot be overloaded with" }
+  void fv1(this S2 const volatile&); // { dg-bogus "" }
+  void fv1(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fcv0(this S2 &); // { dg-bogus "" }
+  void fcv0(this S2 &&); // { dg-bogus "" }
+  void fcv0(this S2 const&); // { dg-bogus "" }
+  void fcv0(this S2 const&&); // { dg-bogus "" }
+  void fcv0(this S2 volatile&); // { dg-bogus "" }
+  void fcv0(this S2 volatile&&); // { dg-bogus "" }
+  void fcv0() const volatile; // { dg-note "previous declaration" }
+  void fcv0(this S2 const volatile&&); // { dg-error "cannot be overloaded with" }
+
+  void fcv1(this S2 &); // { dg-bogus "" }
+  void fcv1(this S2 &&); // { dg-bogus "" }
+  void fcv1(this S2 const&); // { dg-bogus "" }
+  void fcv1(this S2 const&&); // { dg-bogus "" }
+  void fcv1(this S2 volatile&); // { dg-bogus "" }
+  void fcv1(this S2 volatile&&); // { dg-bogus "" }
+  void fcv1(this S2 const volatile&); // { dg-note "previous declaration" }
+  void fcv1() const volatile; // { dg-error "cannot be overloaded with" }
+};
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C
new file mode 100644
index 00000000000..83c7756f0fd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C
@@ -0,0 +1,160 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+// { dg-options "-pedantic-errors -Wno-volatile" }
+
+// rejecting redeclarations of by-value xobj member functions
+// as iobj member functions that are not ref qualified (and vice-versa)
+// also check that valid overloads are accepted
+
+//  iobj |  xobj  |  MSVC | clang | ISOC++
+//  none |  none  |   X   |   X   |   X
+//  none |     c  |   X   |   O   |   X
+//  none |     v  |   X   |   O   |   X
+//  none |    cv  |   X   |   O   |   X
+//     c |  none  |   O   |   O   |   O
+//     c |     c  |   O   |   X   |   O
+//     c |     v  |   O   |   O   |   O
+//     c |    cv  |   O   |   O   |   O
+//     v |  none  |   O   |   O   |   O
+//     v |     c  |   O   |   O   |   O
+//     v |     v  |   O   |   X   |   O
+//     v |    cv  |   O   |   O   |   O
+//    cv |  none  |   O   |   O   |   O
+//    cv |     c  |   O   |   O   |   O
+//    cv |     v  |   O   |   O   |   O
+//    cv |    cv  |   O   |   X   |   O
+
+/* Top-level cv qualifiers are supposed to be discarded from
+   the parameters of a function declaration.
+     
+   [dcl.fct.5]
+   After producing the list of parameter types, any top-level
+   cv-qualifiers modifying a parameter type are deleted when forming
+   the function type.
+
+   According to the standard, the type of an implicit object parameter
+   is always a reference. This isn't reflected in GCC but we still need
+   to take this rule into account here.
+
+   [over.match.funcs.general.4]
+   For implicit object member functions, the type of the implicit
+   object parameter is
+   -- “lvalue reference to cv X” for functions declared
+      without a ref-qualifier or with the & ref-qualifier
+   -- “rvalue reference to cv X” for functions declared with
+      the && ref-qualifier
+    
+   When comparing an iobj and xobj member function to see if they are
+   redeclarations we treat them differently depending on if the iobj
+   member function has a ref qualifier or not.
+   If the iobj member function does not have a ref qualifier, we need to
+   strip the top-level references before comparing them.
+
+   [basic.scope.scope.3]
+   Two non-static member functions have corresponding object
+   parameters if:
+   -- exactly one is an implicit object member function with no
+      ref-qualifier and the types of their object parameters
+      ([dcl.fct]), after removing top-level references, are the
+      same, or
+   -- their object parameters have the same type. */
+
+struct S {
+  void f0(); // { dg-note "previous declaration" }
+  void f0(this S); // { dg-error "cannot be overloaded with" }
+
+  void f1(); // { dg-note "previous declaration" }
+  void f1(this S const); // { dg-error "cannot be overloaded with" }
+
+  void f2(); // { dg-note "previous declaration" }
+  void f2(this S volatile); // { dg-error "cannot be overloaded with" }
+
+  void f3(); // { dg-note "previous declaration" }
+  void f3(this S const volatile); // { dg-error "cannot be overloaded with" }
+
+  void fc0() const; // { dg-bogus "" }
+  void fc0(this S); // { dg-bogus "" }
+
+  void fc1() const; // { dg-bogus "" }
+  void fc1(this S const); // { dg-bogus "" }
+
+  void fc2() const; // { dg-bogus "" }
+  void fc2(this S volatile); // { dg-bogus "" }
+
+  void fc3() const; // { dg-bogus "" }
+  void fc3(this S const volatile); // { dg-bogus "" }
+
+  void fv0() volatile; // { dg-bogus "" }
+  void fv0(this S); // { dg-bogus "" }
+
+  void fv1() volatile; // { dg-bogus "" }
+  void fv1(this S const); // { dg-bogus "" }
+
+  void fv2() volatile; // { dg-bogus "" }
+  void fv2(this S volatile); // { dg-bogus "" }
+
+  void fv3() volatile; // { dg-bogus "" }
+  void fv3(this S const volatile); // { dg-bogus "" }
+
+  void fcv0() const volatile; // { dg-bogus "" }
+  void fcv0(this S); // { dg-bogus "" }
+
+  void fcv1() const volatile; // { dg-bogus "" }
+  void fcv1(this S const); // { dg-bogus "" }
+
+  void fcv2() const volatile; // { dg-bogus "" }
+  void fcv2(this S volatile); // { dg-bogus "" }
+
+  void fcv3() const volatile; // { dg-bogus "" }
+  void fcv3(this S const volatile); // { dg-bogus "" }
+
+  // same as the above f cases except reversed
+
+  void g0(this S); // { dg-note "previous declaration" }
+  void g0(); // { dg-error "cannot be overloaded with" }
+
+  void g1(this S const); // { dg-note "previous declaration" }
+  void g1(); // { dg-error "cannot be overloaded with" }
+
+  void g2(this S volatile); // { dg-note "previous declaration" }
+  void g2(); // { dg-error "cannot be overloaded with" }
+
+  void g3(this S const volatile); // { dg-note "previous declaration" }
+  void g3(); // { dg-error "cannot be overloaded with" }
+
+  void gc0(this S); // { dg-bogus "" }
+  void gc0() const; // { dg-bogus "" }
+
+  void gc1(this S const); // { dg-bogus "" }
+  void gc1() const; // { dg-bogus "" }
+
+  void gc2(this S volatile); // { dg-bogus "" }
+  void gc2() const; // { dg-bogus "" }
+
+  void gc3(this S const volatile); // { dg-bogus "" }
+  void gc3() const; // { dg-bogus "" }
+
+  void gv0(this S); // { dg-bogus "" }
+  void gv0() volatile; // { dg-bogus "" }
+
+  void gv1(this S const); // { dg-bogus "" }
+  void gv1() volatile; // { dg-bogus "" }
+
+  void gv2(this S volatile); // { dg-bogus "" }
+  void gv2() volatile; // { dg-bogus "" }
+
+  void gv3(this S const volatile); // { dg-bogus "" }
+  void gv3() volatile; // { dg-bogus "" }
+
+  void gcv0(this S); // { dg-bogus "" }
+  void gcv0() const volatile; // { dg-bogus "" }
+
+  void gcv1(this S const); // { dg-bogus "" }
+  void gcv1() const volatile; // { dg-bogus "" }
+
+  void gcv2(this S volatile); // { dg-bogus "" }
+  void gcv2() const volatile; // { dg-bogus "" }
+
+  void gcv3(this S const volatile); // { dg-bogus "" }
+  void gcv3() const volatile; // { dg-bogus "" }
+};
-- 
2.42.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-21 13:04                                                                                   ` [PATCH v5 1/1] " waffl3x
@ 2023-11-22  3:22                                                                                     ` Jason Merrill
  2023-11-22 20:46                                                                                       ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-22  3:22 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/21/23 08:04, waffl3x wrote:
> Bootstrapped and tested on x86_64-linux with no regressions.
> 
> Hopefully this patch is legible enough for reviewing purposes, I've not
> been feeling the greatest so it was a task to get this finished.
> Tomorrow I will look at putting the diagnostics in
> start_preparsed_function and also fixing up anything else.
> 
> To reiterate in case it wasn't abundantly clear by the barren changelog
> and commit message, this version is not intended as the final revision.
> 
> Handling re-declarations was kind of nightmarish, so the comments in
> there are lengthy, but I am fairly certain I implemented them correctly.
> 
> I am going to get some sleep now, hopefully I will feel better tomorrow
> and be ready to polish off the patch. Thanks for the patience.

Great!

> I stared at start_preparsed_function for a long while and couldn't
> figure out where to start off at. So for now the error handling is
> split up between instantiate_body and cp_parser_lambda_declarator_opt.
> The latter is super not correct but I've been stuck on this for a long
> time now though so I wanted to actually get something that works and
> then try to make it better.

I see what you mean, instantiate body isn't prepared for 
start_preparsed_function to fail.  It's ok to handle this in two places.
Though actually, instantiate_body is too late for it to fail; I think 
for the template case it should fail in tsubst_lambda_expr, before we 
even start to consider the body.

Incidentally, I notice this code in tsubst_function_decl seems to need 
adjusting for xobj:

   tree parms = DECL_ARGUMENTS (t);
   if (closure && !DECL_STATIC_FUNCTION_P (t))
     parms = DECL_CHAIN (parms);
   parms = tsubst (parms, args, complain, t);
   for (tree parm = parms; parm; parm = DECL_CHAIN (parm))
     DECL_CONTEXT (parm) = r;
   if (closure && !DECL_STATIC_FUNCTION_P (t))
...

and this in tsubst_lambda_expr that assumes iobj:

       /* Fix the type of 'this'.  */
       fntype = build_memfn_type (fntype, type,
                                  type_memfn_quals (fntype),
                                  type_memfn_rqual (fntype));

This also seems like the place to check for unrelated type.

>  /* Nonzero for FUNCTION_DECL means that this decl is a non-static
> -   member function.  */
> +   member function, use DECL_IOBJ_MEMBER_FUNC_P instead.  */
>  #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
>    (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
>  
> +/* Nonzero for FUNCTION_DECL means that this decl is an implicit object
> +   member function.  */
> +#define DECL_IOBJ_MEMBER_FUNC_P(NODE) \
> +  (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)

I was thinking to rename DECL_NONSTATIC_MEMBER_FUNCTION_P rather than 
add a new, equivalent one.  And then go through all the current uses of 
the old macro to decide whether they mean IOBJ or OBJECT.

> -   (static or non-static).  */
> +   (static or object).  */

Let's leave this comment as it was.

> +  auto handle_arg = [fn, flags, complain](tree type,
> +                                         tree arg,
> +                                         int const param_index,
> +                                         conversion *conv,
> +                                         bool const conversion_warning)

Let's move the conversion_warning logic into the handle_arg lambda 
rather than have it as a parameter.  Yes, we don't need it for the xobj 
parm, but I think it's cleaner to have less in the loop.

Also, let's move handle_arg after the iobj 'this' handling so it's 
closer to the uses.  For which the 'else xobj' needs to drop the 'else', 
or change to 'if (first_arg)'.

> +      /* We currently handle for the case where first_arg is NULL_TREE
> +	 in the future this should be changed and the assert reactivated.  */
> +      #if 0
> +      gcc_assert (first_arg);
> +      #endif

Let's leave this out.

> +      val = handle_arg(TREE_VALUE (parm),

Missing space before (.

> -      if (null_node_p (arg)
> -          && DECL_TEMPLATE_INFO (fn)
> -          && cand->template_decl
> -	  && !cand->explicit_targs)
> -        conversion_warning = false;
> -
> +      auto determine_conversion_warning = [&]()
> +	{
> +	  return !(null_node_p (current_arg)
> +	    && DECL_TEMPLATE_INFO (fn)
> +	    && cand->template_decl
> +	    && !cand->explicit_targs);
> +	};

I don't think this needs to be a lambda.

> +	     declaration, then the type of it's object parameter is still
...
> +		 but due to [basic.scope.scope.3] we need to ignore it's
...
> +	    /* Since a valid xobj parm has it's purpose cleared in find_xobj_parm

"its"

> -  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
> -      && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
> +  if (DECL_OBJECT_MEMBER_FUNC_P (fn)
> +      && !(complain & tf_ptrmem_ok))

The following code (not quoted) could share more code between the cases 
if we check

!(flag_ms_extensions && DECL_IOBJ_MEMBER_FUNC_P (fn))

here?

> +		else if (declarator->declarator->kind == cdk_pointer)
> +		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
> +			 /* "a pointer to function type cannot "?  */
> +			    "a function pointer type cannot "
> +			    "have an explicit object parameter");

"pointer to function type", yes.

> +		/* The locations being used here are probably not correct.  */

Why not?

Let's clear xobj_parm after giving an error in the TYPENAME case

>   if ((!methodp && !DECL_XOBJ_MEMBER_FUNC_P (decl))
>       || DECL_STATIC_FUNCTION_P (decl))

I think this can just be if (DECL_OBJECT_MEMBER_FUNC_P (decl)).

>    if (TREE_CODE (fntype) == METHOD_TYPE)
>      ctype = TYPE_METHOD_BASETYPE (fntype);
> +  else if (DECL_XOBJ_MEMBER_FUNC_P (decl1))
> +    ctype = DECL_CONTEXT (decl1);

All of this can be

if (DECL_CLASS_SCOPE_P (decl1))
   ctype = DECL_CONTEXT (decl1);

I think I'm going to go ahead and clean that up now.

> +  /* 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)))

And this can be

if (DECL_IOBJ_MEMBER_FUNC_P (decl1))

> +  if (INDIRECT_TYPE_P (TREE_TYPE (object)))
> +    /* The proxy variable forwards to the capture field.  */

This comment should stay outside the if.

> +      /* This might not be the most appropriate place for this, but I'm going
> +	 to hold back on changing it for the time being since we are short on
> +	 time. It's not really a parser error, it's more a semantic error.
> +	 But this is where the other errors were so...  */

It's OK to give semantic errors in the parser if they are always 
detectable at parse time (before template instantiation).

Though maybe we could contain the xobj vs mutable and xobj vs static 
diagnostics in this block:

> +  if (xobj_param)
> +    {
> +      quals = TYPE_UNQUALIFIED;
> +      if (TYPE_REF_P (xobj_param)
> +	  && !(cp_type_quals (TREE_TYPE (xobj_param)) & TYPE_QUAL_CONST))
> +        LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
> +    }

rather than separately in the mutable/static blocks?  But the way you 
have it is fine, too.

> +	  inform (DECL_SOURCE_LOCATION(xobj_param),

missing space before (xobj_param), repeated several times in the rest of 
the function.

> +	  /* Error reporting here is a little awkward, if the type of the
> +	     object parameter is deduced, we should tell them the lambda
> +	     is effectively already const, or to make the param const if it is
> +	     not, but if it is deduced and taken by value shouldn't we say
> +	     that it's taken by copy and won't mutate?
> +	     Seems right to me, but it's a little strange.  */

I think just omit the inform if dependent_type_p.

> -	if (!LAMBDA_EXPR_STATIC_P (lambda_expr))
> +	if (!LAMBDA_EXPR_STATIC_P (lambda_expr)
> +	    && !DECL_XOBJ_MEMBER_FUNC_P (fco))

if (DECL_IOBJ_MEMBER_FUNC_P (fco))

> +      /* Since a lambda's type is anonymous, we can assume an explicit object
> +	 parameter is unrelated... at least I believe so. I can think of a
> +	 paradoxical case that almost works, where a class is forward declared
> +	 and then defined deriving from a lambda that has an explicit object
> +	 parameter of that class type, but the only way that can work is with
> +	 decltype... okay yeah I think it can work for a struct defined in
> +	 block scope, but I'm leaving this as is until I can confirm my
> +	 hypothesis.  */

Like this?

template <class T> struct A: T { };
template <class T> T f()
{
   int x = 42;
   auto f1 = [x]<typename U>(this A<U>&& self) { return x; };
   return A<decltype(f1)>{f1}();
}
int main() { return f<int>(); }

When I disable the error this crashes in register_parameter_specializations

> +      if (!TEMPLATE_PARM_P (non_reference (TREE_TYPE (xobj_param))))

...so let's just check dependent_type_p here.

> +      error ("%<this%> is unavailable for explicit object member "
> +	     "functions");
> +      /* I can imagine doing a fixit here, suggesting replacing
> +	 this / *this / this-> with &name / name / "name." but it would be
> +	 very difficult to get it perfect and I've been advised against
> +	 making imperfect fixits.
> +	 Perhaps it would be as simple as the replacements listed,
> +	 even if one is move'ing/forward'ing, if the replacement is just
> +	 done in the same place, it will be exactly what the user wants?
> +	 Even if this is true though, there's still a problem of getting the
> +	 context of the expression to find which tokens to replace.
> +	 I would really like for this to be possible though.
> +	 I will decide whether or not to persue this after review.  */

I still think (&self) will always be a correct replacement for 'this', 
but unlikely to be the way they actually want their code to look.  To 
detect this-> or *this should be doable in the parser (where you can 
look forward and back in the token stream) but we don't have access to 
that here in finish_this_expr.

> +	/* Pull out the function_decl for a single xobj member function,
> +	   similar to the BASELINK case above. If we did the same optimization
> +	   as we do for single static member functions (passing in a BASELINK)
> +	   then it could be handled the same too.  */

This comment seems out of date given that the BASELINK handling just 
gives an error.  We can't use the same handling as static member 
functions because we need the SCOPE_REF to tell us that it was properly 
qualified.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-22  3:22                                                                                     ` Jason Merrill
@ 2023-11-22 20:46                                                                                       ` waffl3x
  2023-11-22 21:38                                                                                         ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-22 20:46 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches






On Tuesday, November 21st, 2023 at 8:22 PM, Jason Merrill <jason@redhat.com> wrote:


>
>
> On 11/21/23 08:04, waffl3x wrote:
>
> > Bootstrapped and tested on x86_64-linux with no regressions.
> >
> > Hopefully this patch is legible enough for reviewing purposes, I've not
> > been feeling the greatest so it was a task to get this finished.
> > Tomorrow I will look at putting the diagnostics in
> > start_preparsed_function and also fixing up anything else.
> >
> > To reiterate in case it wasn't abundantly clear by the barren changelog
> > and commit message, this version is not intended as the final revision.
> >
> > Handling re-declarations was kind of nightmarish, so the comments in
> > there are lengthy, but I am fairly certain I implemented them correctly.
> >
> > I am going to get some sleep now, hopefully I will feel better tomorrow
> > and be ready to polish off the patch. Thanks for the patience.
>
>
> Great!
>
> > I stared at start_preparsed_function for a long while and couldn't
> > figure out where to start off at. So for now the error handling is
> > split up between instantiate_body and cp_parser_lambda_declarator_opt.
> > The latter is super not correct but I've been stuck on this for a long
> > time now though so I wanted to actually get something that works and
> > then try to make it better.
>
>
> I see what you mean, instantiate body isn't prepared for
> start_preparsed_function to fail. It's ok to handle this in two places.
> Though actually, instantiate_body is too late for it to fail; I think
> for the template case it should fail in tsubst_lambda_expr, before we
> even start to consider the body.
>
> Incidentally, I notice this code in tsubst_function_decl seems to need
> adjusting for xobj:
>
> tree parms = DECL_ARGUMENTS (t);
> if (closure && !DECL_STATIC_FUNCTION_P (t))
> parms = DECL_CHAIN (parms);
> parms = tsubst (parms, args, complain, t);
> for (tree parm = parms; parm; parm = DECL_CHAIN (parm))
> DECL_CONTEXT (parm) = r;
> if (closure && !DECL_STATIC_FUNCTION_P (t))
> ...
>
> and this in tsubst_lambda_expr that assumes iobj:
>
> /* Fix the type of 'this'. */
> fntype = build_memfn_type (fntype, type,
> type_memfn_quals (fntype),
> type_memfn_rqual (fntype));

Originally I was going to say this doesn't look like a problem in
tsubst_lambda_expr, but after looking at tsubst_function_decl I'm
thinking it might be the source of some trouble. If it really was
causing problems I would think it would be working much worse than it
currently is, but it does feel like it might be the actual source of
the bug I was chasing yesterday. Assigning to a capture with a deduced
const xobj parameter is not being rejected right now, this might be
causing it. I'll look more thoroughly today.

> This also seems like the place to check for unrelated type.

It does feel that way, I agree.

> > /* Nonzero for FUNCTION_DECL means that this decl is a non-static
> > - member function. */
> > + member function, use DECL_IOBJ_MEMBER_FUNC_P instead. */
> > #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
> > (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
> >
> > +/* Nonzero for FUNCTION_DECL means that this decl is an implicit object
> > + member function. */
> > +#define DECL_IOBJ_MEMBER_FUNC_P(NODE) \
> > + (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
>
>
> I was thinking to rename DECL_NONSTATIC_MEMBER_FUNCTION_P rather than
> add a new, equivalent one. And then go through all the current uses of
> the old macro to decide whether they mean IOBJ or OBJECT.

I figure it would be easier to make that transition if there's a clear
line between old versus new. To be clear, my intention is for the old
macro to be removed once all the uses of it are changed over to the new
macro. I can still remove it for the patch if you like but having both
and removing the old one later seems better to me.

> > - (static or non-static). */
> > + (static or object). */
>
>
> Let's leave this comment as it was.

Okay.

> > + auto handle_arg = [fn, flags, complain](tree type,
> > + tree arg,
> > + int const param_index,
> > + conversion *conv,
> > + bool const conversion_warning)
>
>
> Let's move the conversion_warning logic into the handle_arg lambda
> rather than have it as a parameter. Yes, we don't need it for the xobj
> parm, but I think it's cleaner to have less in the loop.

I would argue that it's cleaner to have the lambda be concise, but I'll
make this change.

> Also, let's move handle_arg after the iobj 'this' handling so it's
> closer to the uses. For which the 'else xobj' needs to drop the 'else',
> or change to 'if (first_arg)'.

Agreed, I didn't like how far away it was.

> > + /* We currently handle for the case where first_arg is NULL_TREE
> > + in the future this should be changed and the assert reactivated. */
> > + #if 0
> > + gcc_assert (first_arg);
> > + #endif
>
>
> Let's leave this out.

Alright.

> > + val = handle_arg(TREE_VALUE (parm),
>
>
> Missing space before (.
>
> > - if (null_node_p (arg)
> > - && DECL_TEMPLATE_INFO (fn)
> > - && cand->template_decl
> > - && !cand->explicit_targs)
> > - conversion_warning = false;
> > -
> > + auto determine_conversion_warning = &
> > + {
> > + return !(null_node_p (current_arg)
> > + && DECL_TEMPLATE_INFO (fn)
> > + && cand->template_decl
> > + && !cand->explicit_targs);
> > + };
>
>
> I don't think this needs to be a lambda.

Yeah probably a little unnecessary, I'll switch it back.

> > + declaration, then the type of it's object parameter is still
>
> ...
>
> > + but due to [basic.scope.scope.3] we need to ignore it's
>
> ...
>
> > + /* Since a valid xobj parm has it's purpose cleared in find_xobj_parm
>
>
> "its"

TIL while we use an apostrophe for possessive nouns, we don't use one
for the posessive its. English!

> > - if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
> > - && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
> > + if (DECL_OBJECT_MEMBER_FUNC_P (fn)
> > + && !(complain & tf_ptrmem_ok))
>
>
> The following code (not quoted) could share more code between the cases
> if we check
>
> !(flag_ms_extensions && DECL_IOBJ_MEMBER_FUNC_P (fn))
>
> here?

Yeah I didn't like it very much when I wrote it, but I was too tired to
reliably merge things together so I just wanted it to work. I will
screw around with it more today.

> > + else if (declarator->declarator->kind == cdk_pointer)
> > + error_at (DECL_SOURCE_LOCATION (xobj_parm),
> > + /* "a pointer to function type cannot "? */
> > + "a function pointer type cannot "
> > + "have an explicit object parameter");
>
>
> "pointer to function type", yes.
>
> > + /* The locations being used here are probably not correct. */
>
>
> Why not?

I threw them in just so I could call inform, but it doesn't feel like
the messages should be pointing at the parameter, but rather at the
full type declaration. When I put those locations in I wasn't sure how
to get the full declaration location, and I'm still not 100% confident
in how to do it, so I just threw them in and moved on.

> Let's clear xobj_parm after giving an error in the TYPENAME case

I don't like the spirit of this very much, whats your reasoning for
this? We're nearly at the end of the scope where it is last used, I
think it would be more unclear if we suddenly set it to NULL_TREE near
the end. It raises the question of whether that assignment actually
does anything, or if we are just trying to indicate that it isn't being
used anymore, but I already made sure to declare it in the deepest
scope possible. That much should be sufficient for indicating it's
usage, no?

> > if ((!methodp && !DECL_XOBJ_MEMBER_FUNC_P (decl))
> > || DECL_STATIC_FUNCTION_P (decl))
>
>
> I think this can just be if (DECL_OBJECT_MEMBER_FUNC_P (decl)).

Alright, and going forward I'll try to make more changes that are
consistent with this one. With that said I'm not sure it can, but I'll
take a close look and if you're right I'll make that change.

> > if (TREE_CODE (fntype) == METHOD_TYPE)
> > ctype = TYPE_METHOD_BASETYPE (fntype);
> > + else if (DECL_XOBJ_MEMBER_FUNC_P (decl1))
> > + ctype = DECL_CONTEXT (decl1);
>
>
> All of this can be
>
> if (DECL_CLASS_SCOPE_P (decl1))
> ctype = DECL_CONTEXT (decl1);
>
> I think I'm going to go ahead and clean that up now.

Sounds good to me, a lot of this stuff needs small cleanups and I'm
just concerned about making them too much.

> > + /* 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)))
>
>
> And this can be
>
> if (DECL_IOBJ_MEMBER_FUNC_P (decl1))
>
> > + if (INDIRECT_TYPE_P (TREE_TYPE (object)))
> > + /* The proxy variable forwards to the capture field. */
>
>
> This comment should stay outside the if.
>
> > + /* This might not be the most appropriate place for this, but I'm going
> > + to hold back on changing it for the time being since we are short on
> > + time. It's not really a parser error, it's more a semantic error.
> > + But this is where the other errors were so... */
>
>
> It's OK to give semantic errors in the parser if they are always
> detectable at parse time (before template instantiation).

Okay, it's good to hear a definite answer to that, I'll keep it in mind.

> Though maybe we could contain the xobj vs mutable and xobj vs static
> diagnostics in this block:
>
> > + if (xobj_param)
> > + {
> > + quals = TYPE_UNQUALIFIED;
> > + if (TYPE_REF_P (xobj_param)
> > + && !(cp_type_quals (TREE_TYPE (xobj_param)) & TYPE_QUAL_CONST))
> > + LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
> > + }
>
>
> rather than separately in the mutable/static blocks? But the way you
> have it is fine, too.

Yeah I don't like the code very much, I am happy to refactor it, it's
just another case of tired coding so I didn't move things around too
much.

> > + inform (DECL_SOURCE_LOCATION(xobj_param),
>
>
> missing space before (xobj_param), repeated several times in the rest of
> the function.

Yeah tired coding. :)
I'll be sure to go over everything with a fine tooth comb again before
the final version of the patch.

> > + /* Error reporting here is a little awkward, if the type of the
> > + object parameter is deduced, we should tell them the lambda
> > + is effectively already const, or to make the param const if it is
> > + not, but if it is deduced and taken by value shouldn't we say
> > + that it's taken by copy and won't mutate?
> > + Seems right to me, but it's a little strange. */
>
>
> I think just omit the inform if dependent_type_p.

Maybe I don't understand what a dependent type is as well as I thought,
but doesn't this defeat every useful case? The most common being an
xobj parameter of lambda type, which will always be deduced. Unless a
template parameter does not count as a dependent type, which is not
something I've ever thought about before.

It does need to be better though, so I'll work on it and I'll keep what
you said in mind.

> > - if (!LAMBDA_EXPR_STATIC_P (lambda_expr))
> > + if (!LAMBDA_EXPR_STATIC_P (lambda_expr)
> > + && !DECL_XOBJ_MEMBER_FUNC_P (fco))
>
>
> if (DECL_IOBJ_MEMBER_FUNC_P (fco))
>
> > + /* Since a lambda's type is anonymous, we can assume an explicit object
> > + parameter is unrelated... at least I believe so. I can think of a
> > + paradoxical case that almost works, where a class is forward declared
> > + and then defined deriving from a lambda that has an explicit object
> > + parameter of that class type, but the only way that can work is with
> > + decltype... okay yeah I think it can work for a struct defined in
> > + block scope, but I'm leaving this as is until I can confirm my
> > + hypothesis. */
>
>
> Like this?
>
> template <class T> struct A: T { };
>
> template <class T> T f()
>
> {
> int x = 42;
> auto f1 = [x]<typename U>(this A<U>&& self) { return x; };
>
> return A<decltype(f1)>{f1}();
>
> }
> int main() { return f<int>(); }

Oh boy, I had a feeling I was wrong. I was really hoping I was right
but here it is, that definitely counts. I had tried something like this
but I decided that it wasn't possible, I completely forgot about
template argument deduction and passing it in like this.

>
> When I disable the error this crashes in register_parameter_specializations
>
> > + if (!TEMPLATE_PARM_P (non_reference (TREE_TYPE (xobj_param))))
>
>
> ...so let's just check dependent_type_p here.

I think this jogs my memory on what a dependent type is. I believe I
see how this would work now.

Mildly related, a lot of the stuff I hacked together with multiple
levels of accessing macros and predicates was due to not being able to
find a solution for what I needed. I think we would highly benefit from
better documentation of the accessors and predicates. I believe I've
seen some that appear to be duplicates, and some where they don't
appear to be implemented properly or match their description. If there
is such a document please direct me to it as I have spent an hour or so
each time I stumble on one of these problems.

In the section I wrote in add_method I spent I think about 3 hours
trying to figure out what combination of predicates and accessing
macros was correct for what I was trying to do. It gets pretty
convoluted to traverse, especially when the implementations of them are
rather involved. Finding non_reference was a big help for example, and
I stumbled across it by pure luck.

If we don't have this kind of documentation anywhere yet, I'm happy to
work on it next. I've spent a lot of time looking at it all so I feel
like I have a DECENT grasp on some of it. I think it would greatly
enhance the status quo, and might even help us figure out what needs to
be cleaned up, removed, replaced, etc.

Side side note, if said document does exist already, I either need to
learn how to search the wiki more effectively or it needs some sort of
improvement.

> > + error ("%<this%> is unavailable for explicit object member "
> > + "functions");
> > + /* I can imagine doing a fixit here, suggesting replacing
> > + this / *this / this-> with &name / name / "name." but it would be
> > + very difficult to get it perfect and I've been advised against
> > + making imperfect fixits.
> > + Perhaps it would be as simple as the replacements listed,
> > + even if one is move'ing/forward'ing, if the replacement is just
> > + done in the same place, it will be exactly what the user wants?
> > + Even if this is true though, there's still a problem of getting the
> > + context of the expression to find which tokens to replace.
> > + I would really like for this to be possible though.
> > + I will decide whether or not to persue this after review. */
>
>
> I still think (&self) will always be a correct replacement for 'this',
> but unlikely to be the way they actually want their code to look. To
> detect this-> or *this should be doable in the parser (where you can
> look forward and back in the token stream) but we don't have access to
> that here in finish_this_expr.

Yeah, that is the issue. At the moment I'm putting any fixit stuff that
I want to do on the backburner. I've come across a few more I wanted to
do but, I've just come to the conclusion that learning how to do fixits
properly will be something I'll need to sit down and study on its own.
They are really finicky, the last one I tried to do just doesn't seem
to show up at all and I don't really know why.

Regarding (&self), yeah it will always be valid, it's just like you
said it's unlikely to be what the user means. I don't want to put a
half solution in here, I'll just delay the full solution.

> > + /* Pull out the function_decl for a single xobj member function,
> > + similar to the BASELINK case above. If we did the same optimization
> > + as we do for single static member functions (passing in a BASELINK)
> > + then it could be handled the same too. */
>
>
> This comment seems out of date given that the BASELINK handling just
> gives an error. We can't use the same handling as static member
> functions because we need the SCOPE_REF to tell us that it was properly
> qualified.
>
> Jason

I can revise how the comment reads, especially since as you've pointed
out introducing the baselink optimization to non-overloaded xobj member
functions would cause more problems than benefits. What I meant by the
comment was that it just simply pulls the function_decl out and puts it
in arg, then lets everything else after the switch case handle it. It
definitely isn't clear the way it's written right now, I will revise it.

I mostly got tests written yesterday, not a whole lot else done. I
found that one of my comments was false specifically regarding the
change in build_capture_proxy.

+  /* Making this conditional prevents the ICE, but does not actually work
+     correctly.  */

So funny enough, the comment seems to be false for the version of the
patch I submitted. I removed the change I made in
finish_non_static_data_member before submitting the patch, but didn't
realize that the change I made in there was what actually caused the
problem I was referencing. It wasn't until yesterday morning that I
realized that the behavior was actually correct. I also blame GDB for
acting funky, for some reason even in a build with optimizations off,
assignments to variables were appearing late. I blame GDB but it really
might not be its fault. Regardless, it threw me off quite a bit a few
times I think, I only noticed it when I started comparing return values
from functions (with the finish command) and what they were being
assigned to. It seems to happen when assigning to a variable that was
declared but not initialized.

Rambling aside, the point is, that simple change you suggested in
build_capture_proxy does appear to have been sufficient for fixing the
by-value xobj parameter case. The next patch will have tests to
demonstrate these cases. Realistically I should have had the tests done
for this version but, I just really couldn't manage it. With how much
feedback you gave I think I made the right call getting it sent faster
rather than waiting for all these things.

I will attempt to get another version finished today. I have a feeling
you were right about tsubst_lambda_expr and tsubst_function_decl so
hopefully that will expedite things. As noted, the next version will
include more tests so there will be less mystery in what works and what
doesn't.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-22 20:46                                                                                       ` waffl3x
@ 2023-11-22 21:38                                                                                         ` Jason Merrill
       [not found]                                                                                           ` <kltTuyDDwoyOmhBWostMKm5zF3sQCGz3HjMBrBUK6LOZp1-AbGMl5ijKKMlOncwR2yiWippyp89sFPZykNF3OVyz4yknnCVwn=5FiHJPUl25k=3D@protonmail.com>
                                                                                                             ` (2 more replies)
  0 siblings, 3 replies; 100+ messages in thread
From: Jason Merrill @ 2023-11-22 21:38 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/22/23 15:46, waffl3x wrote:
> On Tuesday, November 21st, 2023 at 8:22 PM, Jason Merrill <jason@redhat.com> wrote:
>> On 11/21/23 08:04, waffl3x wrote:
>>
>>> /* Nonzero for FUNCTION_DECL means that this decl is a non-static
>>> - member function. */
>>> + member function, use DECL_IOBJ_MEMBER_FUNC_P instead. */
>>> #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
>>> (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
>>>
>>> +/* Nonzero for FUNCTION_DECL means that this decl is an implicit object
>>> + member function. */
>>> +#define DECL_IOBJ_MEMBER_FUNC_P(NODE) \
>>> + (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
>>
>> I was thinking to rename DECL_NONSTATIC_MEMBER_FUNCTION_P rather than
>> add a new, equivalent one. And then go through all the current uses of
>> the old macro to decide whether they mean IOBJ or OBJECT.
> 
> I figure it would be easier to make that transition if there's a clear
> line between old versus new. To be clear, my intention is for the old
> macro to be removed once all the uses of it are changed over to the new
> macro. I can still remove it for the patch if you like but having both
> and removing the old one later seems better to me.

Hmm, I think changing all the uses is a necessary part of this change. 
I suppose it could happen before the main patch, if you'd prefer, but it 
seems more straightforward to include it.

>>> + else if (declarator->declarator->kind == cdk_pointer)
>>> + error_at (DECL_SOURCE_LOCATION (xobj_parm),
>>> + /* "a pointer to function type cannot "? */
>>> + "a function pointer type cannot "
>>> + "have an explicit object parameter");
>>
>> "pointer to function type", yes.
>>
>>> + /* The locations being used here are probably not correct. */
>>
>> Why not?
> 
> I threw them in just so I could call inform, but it doesn't feel like
> the messages should be pointing at the parameter, but rather at the
> full type declaration. When I put those locations in I wasn't sure how
> to get the full declaration location, and I'm still not 100% confident
> in how to do it, so I just threw them in and moved on.

That would be more precise, but I think it's actually preferable for the 
inform to have the same location as the previous error to avoid 
redundant quoting of the source.

>> Let's clear xobj_parm after giving an error in the TYPENAME case
> 
> I don't like the spirit of this very much, whats your reasoning for
> this? We're nearly at the end of the scope where it is last used, I
> think it would be more unclear if we suddenly set it to NULL_TREE near
> the end. It raises the question of whether that assignment actually
> does anything, or if we are just trying to indicate that it isn't being
> used anymore, but I already made sure to declare it in the deepest
> scope possible. That much should be sufficient for indicating it's
> usage, no?

Hmm, I think I poked at that and changed my mind, but forgot to delete 
the suggestion.  Never mind.

>>> if ((!methodp && !DECL_XOBJ_MEMBER_FUNC_P (decl))
>>> || DECL_STATIC_FUNCTION_P (decl))
>>
>> I think this can just be if (DECL_OBJECT_MEMBER_FUNC_P (decl)).
> 
> Alright, and going forward I'll try to make more changes that are
> consistent with this one. With that said I'm not sure it can, but I'll
> take a close look and if you're right I'll make that change.
> 
>>> if (TREE_CODE (fntype) == METHOD_TYPE)
>>> ctype = TYPE_METHOD_BASETYPE (fntype);
>>> + else if (DECL_XOBJ_MEMBER_FUNC_P (decl1))
>>> + ctype = DECL_CONTEXT (decl1);
>>
>> All of this can be
>>
>> if (DECL_CLASS_SCOPE_P (decl1))
>> ctype = DECL_CONTEXT (decl1);
>>
>> I think I'm going to go ahead and clean that up now.
> 
> Sounds good to me, a lot of this stuff needs small cleanups and I'm
> just concerned about making them too much.

My cleanup of the ctype logic is in now.

>>> + /* Error reporting here is a little awkward, if the type of the
>>> + object parameter is deduced, we should tell them the lambda
>>> + is effectively already const, or to make the param const if it is
>>> + not, but if it is deduced and taken by value shouldn't we say
>>> + that it's taken by copy and won't mutate?
>>> + Seems right to me, but it's a little strange. */
>>
>> I think just omit the inform if dependent_type_p.
> 
> Maybe I don't understand what a dependent type is as well as I thought,
> but doesn't this defeat every useful case? The most common being an
> xobj parameter of lambda type, which will always be deduced. Unless a
> template parameter does not count as a dependent type, which is not
> something I've ever thought about before.

No, you're right.  A template parameter is certainly dependent.  I think 
the informs are fine as they are.

> Mildly related, a lot of the stuff I hacked together with multiple
> levels of accessing macros and predicates was due to not being able to
> find a solution for what I needed. I think we would highly benefit from
> better documentation of the accessors and predicates. I believe I've
> seen some that appear to be duplicates, and some where they don't
> appear to be implemented properly or match their description. If there
> is such a document please direct me to it as I have spent an hour or so
> each time I stumble on one of these problems.
> 
> In the section I wrote in add_method I spent I think about 3 hours
> trying to figure out what combination of predicates and accessing
> macros was correct for what I was trying to do. It gets pretty
> convoluted to traverse, especially when the implementations of them are
> rather involved. Finding non_reference was a big help for example, and
> I stumbled across it by pure luck.
> 
> If we don't have this kind of documentation anywhere yet, I'm happy to
> work on it next. I've spent a lot of time looking at it all so I feel
> like I have a DECENT grasp on some of it. I think it would greatly
> enhance the status quo, and might even help us figure out what needs to
> be cleaned up, removed, replaced, etc.
> 
> Side side note, if said document does exist already, I either need to
> learn how to search the wiki more effectively or it needs some sort of
> improvement.

There isn't really such a document; the comments are the main internals 
documentation, such as it is.  I wonder about reorganizing cp-tree.h 
rather than creating a separate document that is likely to go out of 
date more quickly?

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-22 21:38                                                                                         ` Jason Merrill
       [not found]                                                                                           ` <kltTuyDDwoyOmhBWostMKm5zF3sQCGz3HjMBrBUK6LOZp1-AbGMl5ijKKMlOncwR2yiWippyp89sFPZykNF3OVyz4yknnCVwn=5FiHJPUl25k=3D@protonmail.com>
@ 2023-11-22 21:56                                                                                           ` waffl3x
  2023-11-22 22:44                                                                                           ` waffl3x
  2 siblings, 0 replies; 100+ messages in thread
From: waffl3x @ 2023-11-22 21:56 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches






On Wednesday, November 22nd, 2023 at 2:38 PM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 11/22/23 15:46, waffl3x wrote:
> 
> > On Tuesday, November 21st, 2023 at 8:22 PM, Jason Merrill jason@redhat.com wrote:
> > 
> > > On 11/21/23 08:04, waffl3x wrote:
> > > 
> > > > /* Nonzero for FUNCTION_DECL means that this decl is a non-static
> > > > - member function. */
> > > > + member function, use DECL_IOBJ_MEMBER_FUNC_P instead. */
> > > > #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
> > > > (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
> > > > 
> > > > +/* Nonzero for FUNCTION_DECL means that this decl is an implicit object
> > > > + member function. */
> > > > +#define DECL_IOBJ_MEMBER_FUNC_P(NODE) \
> > > > + (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
> > > 
> > > I was thinking to rename DECL_NONSTATIC_MEMBER_FUNCTION_P rather than
> > > add a new, equivalent one. And then go through all the current uses of
> > > the old macro to decide whether they mean IOBJ or OBJECT.
> > 
> > I figure it would be easier to make that transition if there's a clear
> > line between old versus new. To be clear, my intention is for the old
> > macro to be removed once all the uses of it are changed over to the new
> > macro. I can still remove it for the patch if you like but having both
> > and removing the old one later seems better to me.
> 
> 
> Hmm, I think changing all the uses is a necessary part of this change.
> I suppose it could happen before the main patch, if you'd prefer, but it
> seems more straightforward to include it.
> 
> > > > + else if (declarator->declarator->kind == cdk_pointer)
> > > > + error_at (DECL_SOURCE_LOCATION (xobj_parm),
> > > > + /* "a pointer to function type cannot "? */
> > > > + "a function pointer type cannot "
> > > > + "have an explicit object parameter");
> > > 
> > > "pointer to function type", yes.
> > > 
> > > > + /* The locations being used here are probably not correct. */
> > > 
> > > Why not?
> > 
> > I threw them in just so I could call inform, but it doesn't feel like
> > the messages should be pointing at the parameter, but rather at the
> > full type declaration. When I put those locations in I wasn't sure how
> > to get the full declaration location, and I'm still not 100% confident
> > in how to do it, so I just threw them in and moved on.
> 
> 
> That would be more precise, but I think it's actually preferable for the
> inform to have the same location as the previous error to avoid
> redundant quoting of the source.

Yeah that makes sense, I'll revise the comment with that rationale and
we can maybe revisit it later.

> > > Let's clear xobj_parm after giving an error in the TYPENAME case
> > 
> > I don't like the spirit of this very much, whats your reasoning for
> > this? We're nearly at the end of the scope where it is last used, I
> > think it would be more unclear if we suddenly set it to NULL_TREE near
> > the end. It raises the question of whether that assignment actually
> > does anything, or if we are just trying to indicate that it isn't being
> > used anymore, but I already made sure to declare it in the deepest
> > scope possible. That much should be sufficient for indicating it's
> > usage, no?
> 
> 
> Hmm, I think I poked at that and changed my mind, but forgot to delete
> the suggestion. Never mind.

Perfect.

> > > > if ((!methodp && !DECL_XOBJ_MEMBER_FUNC_P (decl))
> > > > || DECL_STATIC_FUNCTION_P (decl))
> > > 
> > > I think this can just be if (DECL_OBJECT_MEMBER_FUNC_P (decl)).
> > 
> > Alright, and going forward I'll try to make more changes that are
> > consistent with this one. With that said I'm not sure it can, but I'll
> > take a close look and if you're right I'll make that change.
> > 
> > > > if (TREE_CODE (fntype) == METHOD_TYPE)
> > > > ctype = TYPE_METHOD_BASETYPE (fntype);
> > > > + else if (DECL_XOBJ_MEMBER_FUNC_P (decl1))
> > > > + ctype = DECL_CONTEXT (decl1);
> > > 
> > > All of this can be
> > > 
> > > if (DECL_CLASS_SCOPE_P (decl1))
> > > ctype = DECL_CONTEXT (decl1);
> > > 
> > > I think I'm going to go ahead and clean that up now.
> > 
> > Sounds good to me, a lot of this stuff needs small cleanups and I'm
> > just concerned about making them too much.
> 
> 
> My cleanup of the ctype logic is in now.

I'll make sure to base the final patch off a newer commit then, I don't
think I'll do that right now because it takes a lot of time for me. I
still haven't gotten used to all the workflows with git so doing
anything with it takes a lot out of me. Also, I would have to rerun the
testsuite on the newer commit so I can get an accurate baseline which
takes a few hours.

> > > > + /* Error reporting here is a little awkward, if the type of the
> > > > + object parameter is deduced, we should tell them the lambda
> > > > + is effectively already const, or to make the param const if it is
> > > > + not, but if it is deduced and taken by value shouldn't we say
> > > > + that it's taken by copy and won't mutate?
> > > > + Seems right to me, but it's a little strange. */
> > > 
> > > I think just omit the inform if dependent_type_p.
> > 
> > Maybe I don't understand what a dependent type is as well as I thought,
> > but doesn't this defeat every useful case? The most common being an
> > xobj parameter of lambda type, which will always be deduced. Unless a
> > template parameter does not count as a dependent type, which is not
> > something I've ever thought about before.
> 
> 
> No, you're right. A template parameter is certainly dependent. I think
> the informs are fine as they are.

Okay sounds good, I still have to clean up in there for the other
reasons you noted, and because it just doesn't behave the way I want in
some cases still.

I think the logic I want is probably something like, is dependent,
unless it's a type template parameter. I'll figure it out, probably.

> > Mildly related, a lot of the stuff I hacked together with multiple
> > levels of accessing macros and predicates was due to not being able to
> > find a solution for what I needed. I think we would highly benefit from
> > better documentation of the accessors and predicates. I believe I've
> > seen some that appear to be duplicates, and some where they don't
> > appear to be implemented properly or match their description. If there
> > is such a document please direct me to it as I have spent an hour or so
> > each time I stumble on one of these problems.
> > 
> > In the section I wrote in add_method I spent I think about 3 hours
> > trying to figure out what combination of predicates and accessing
> > macros was correct for what I was trying to do. It gets pretty
> > convoluted to traverse, especially when the implementations of them are
> > rather involved. Finding non_reference was a big help for example, and
> > I stumbled across it by pure luck.
> > 
> > If we don't have this kind of documentation anywhere yet, I'm happy to
> > work on it next. I've spent a lot of time looking at it all so I feel
> > like I have a DECENT grasp on some of it. I think it would greatly
> > enhance the status quo, and might even help us figure out what needs to
> > be cleaned up, removed, replaced, etc.
> > 
> > Side side note, if said document does exist already, I either need to
> > learn how to search the wiki more effectively or it needs some sort of
> > improvement.
> 
> 
> There isn't really such a document; the comments are the main internals
> documentation, such as it is. I wonder about reorganizing cp-tree.h
> rather than creating a separate document that is likely to go out of
> date more quickly?
> 
> Jason

Yeah if it gets out of sync it's a problem, I'm not a big fan of
generating documentation from source usually, but perhaps this is a
good case for it. Annotating the macros and predicates with tags to
make it more searchable. The tags being the actual C++ keywords or
whatnot.

Obviously there's not too much time to think about it right now so I'll
just leave it at that for the moment, but some sort of mechanism like
this to aide in organization, de-duplication, and search-ability would
be the right way forward IMO. It will be less likely to get to a state
where it's in a dire need of reorganization that way.

One last thing on that, I've noticed there appears to still be some
pollution of gcc/tree.h with C++ constructs. Indeed, I checked really
quick just now and spotted a predicate for relating to virtual
functions/inheritance. I recognize that's a huge task to fully clean up
so I understand why it's still the case, but that should probably be
tackled at the same time.

/* Nonzero means that the derivation chain is via a `virtual' declaration.  */
#define BINFO_VIRTUAL_P(NODE) (TREE_BINFO_CHECK (NODE)->base.static_flag)

Back to work, thanks for the quick reply.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-22 21:38                                                                                         ` Jason Merrill
       [not found]                                                                                           ` <kltTuyDDwoyOmhBWostMKm5zF3sQCGz3HjMBrBUK6LOZp1-AbGMl5ijKKMlOncwR2yiWippyp89sFPZykNF3OVyz4yknnCVwn=5FiHJPUl25k=3D@protonmail.com>
  2023-11-22 21:56                                                                                           ` waffl3x
@ 2023-11-22 22:44                                                                                           ` waffl3x
  2023-11-24  6:49                                                                                             ` waffl3x
  2 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-22 22:44 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

> > > > /* Nonzero for FUNCTION_DECL means that this decl is a non-static
> > > > - member function. */
> > > > + member function, use DECL_IOBJ_MEMBER_FUNC_P instead. */
> > > > #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
> > > > (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
> > > > 
> > > > +/* Nonzero for FUNCTION_DECL means that this decl is an implicit object
> > > > + member function. */
> > > > +#define DECL_IOBJ_MEMBER_FUNC_P(NODE) \
> > > > + (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
> > > 
> > > I was thinking to rename DECL_NONSTATIC_MEMBER_FUNCTION_P rather than
> > > add a new, equivalent one. And then go through all the current uses of
> > > the old macro to decide whether they mean IOBJ or OBJECT.
> > 
> > I figure it would be easier to make that transition if there's a clear
> > line between old versus new. To be clear, my intention is for the old
> > macro to be removed once all the uses of it are changed over to the new
> > macro. I can still remove it for the patch if you like but having both
> > and removing the old one later seems better to me.
> 
> 
> Hmm, I think changing all the uses is a necessary part of this change.
> I suppose it could happen before the main patch, if you'd prefer, but it
> seems more straightforward to include it.
> 

I had meant to reply to this as well but forgot, I agree that it's
likely necessary but I've only been changing them as I come across
things that don't work right rather than trying to evaluate them
through the code. Making changes to them without having a test case
that demonstrates that the case is definitely being handled incorrectly
is risky, especially for me since I don't have a full understanding of
the code base. I would rather only change ones that are evidently
wrong, and defer the rest to someone else who knows the code base
better.

With that said, I have been neglecting replacing uses of the old macro,
but I now realize that's just creating more work for whoever is
evaluating the rest of them. Going forward I will make sure I replace
the old macro when I am fairly certain it should be.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-22 22:44                                                                                           ` waffl3x
@ 2023-11-24  6:49                                                                                             ` waffl3x
  2023-11-24 23:26                                                                                               ` waffl3x
  2023-11-25 17:32                                                                                               ` [PATCH v5 " Jason Merrill
  0 siblings, 2 replies; 100+ messages in thread
From: waffl3x @ 2023-11-24  6:49 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

> and this in tsubst_lambda_expr that assumes iobj:
> 
> /* Fix the type of 'this'. */
> fntype = build_memfn_type (fntype, type,
> type_memfn_quals (fntype),
> type_memfn_rqual (fntype));

Unfortunately, putting a condition on this had some unforeseen
consequences. I've been working on this about 8 hours today and I'm a
little defeated after discovering this.

commit 39ade88fa1632c659c5c4ed065fa2b62d16a8670
Author: Jason Merrill <jason@redhat.com>
Date:   Tue Jan 24 15:29:35 2023 -0500

    c++: static lambda in template [PR108526]
    
    tsubst_lambda_expr uses build_memfn_type to build a METHOD_TYPE for the new
    lamba op().  This is not what we want for a C++23 static op(), but since we
    also use that METHOD_TYPE to communicate the closure type down to
    tsubst_function_decl, let's wait and turn it back at that point.
    
            PR c++/108526
    
    gcc/cp/ChangeLog:
    
    gcc/cp/ChangeLog:
    
            * pt.cc (tsubst_function_decl): Handle static lambda.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp23/static-operator-call5.C: New test.

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 2a4d03c5e47..51fc246ed71 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -14306,6 +14306,11 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
   tree ctx = closure ? closure : DECL_CONTEXT (t);
   bool member = ctx && TYPE_P (ctx);
 
+  /* If this is a static lambda, remove the 'this' pointer added in
+     tsubst_lambda_expr now that we know the closure type.  */
+  if (lambda_fntype && DECL_STATIC_FUNCTION_P (t))
+    lambda_fntype = static_fn_type (lambda_fntype);
+

I discovered this when I decided to try a static lambda to see if that
would help me narrow down my current problem. I was shocked to find out
it exhibited the exact same ICE I was trying to fix. So I was going to
go undo my changes one by one to see what it was, thankfully this was
the first one I tried, I undid the condition I put on it, and the crash
was gone.

Anyway, this has been my whole day so far, I am going to have to look
deeper to decide how exactly I fix this because I don't think this hack
that is in place at the moment is the right way to do it. The first
idea that comes to mind is modifying the decl_context of the call
operator, but I'm really not sure. I'm just going to take a break, eat
some pizza, and come back to this hopefully less defeated.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-24  6:49                                                                                             ` waffl3x
@ 2023-11-24 23:26                                                                                               ` waffl3x
  2023-11-25  1:14                                                                                                 ` waffl3x
  2023-11-25 17:32                                                                                               ` [PATCH v5 " Jason Merrill
  1 sibling, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-24 23:26 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

Okay within about half an hour today I think I've figured out what the
main issue is for my problem. For the static lambda case, while I don't
like the hack that's currently present, I reckon we just leave it as it
is, as it works just fine right now. For my issue, I'm not totally sure
how to tackle it, but here's what I've identified.

build_memfn_type calls into build_method_type_directly and that takes
the main variant of the passed in basetype and sets it as the
TYPE_METHOD_BASETYPE.

TYPE_METHOD_BASETYPE (t) = TYPE_MAIN_VARIANT (basetype);

So naively just bypassing build_memfn_type is not going to work, I'm
not totally sure how to solve this though. (Below)

/* Fix the type of 'this'.  */
      fntype = build_memfn_type (fntype, type,
                                   type_memfn_quals (fntype),
                                   type_memfn_rqual (fntype));

Making tree nodes in general is something I'm not very familiar with,
and struggled with every time I tried to read any of the code. So for
now I'm going to hope that the next block (below) handles everything
properly as far as creating a new node and substitution goes.

      tree inst = (oldtmpl
                   ? tsubst_template_decl (oldtmpl, args, complain,
                                           fntype, tparms)
                   : tsubst_function_decl (oldfn, args, complain, fntype));

And I'll just see if setting the decl_context of inst to type does the
trick. I guess I forgot to elaborate that tsubst_function_decl uses the
decl_context of t on the second, which ends up having template-info
attached to it. As you might have been able to deduce from what I've
said so far, I suspect that it's because I'm not setting it properly
when the function_decl is substituted into. I don't really know where
the right place to do that is though, or really why it's not happening
already, so I'm just gonna poke around, try some things, and clean it
up later if it's messy.

That's my quick update, hopefully this gets me somewhere finally.

Alex

On Thursday, November 23rd, 2023 at 11:49 PM, waffl3x <waffl3x@protonmail.com> wrote:


> 
> 
> > and this in tsubst_lambda_expr that assumes iobj:
> 
> > /* Fix the type of 'this'. */
> > fntype = build_memfn_type (fntype, type,
> > type_memfn_quals (fntype),
> > type_memfn_rqual (fntype));
> 
> 
> Unfortunately, putting a condition on this had some unforeseen
> consequences. I've been working on this about 8 hours today and I'm a
> little defeated after discovering this.
> 
> commit 39ade88fa1632c659c5c4ed065fa2b62d16a8670
> Author: Jason Merrill jason@redhat.com
> 
> Date: Tue Jan 24 15:29:35 2023 -0500
> 
> c++: static lambda in template [PR108526]
> 
> tsubst_lambda_expr uses build_memfn_type to build a METHOD_TYPE for the new
> lamba op(). This is not what we want for a C++23 static op(), but since we
> also use that METHOD_TYPE to communicate the closure type down to
> tsubst_function_decl, let's wait and turn it back at that point.
> 
> PR c++/108526
> 
> gcc/cp/ChangeLog:
> 
> gcc/cp/ChangeLog:
> 
> * pt.cc (tsubst_function_decl): Handle static lambda.
> 
> gcc/testsuite/ChangeLog:
> 
> * g++.dg/cpp23/static-operator-call5.C: New test.
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 2a4d03c5e47..51fc246ed71 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -14306,6 +14306,11 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
> tree ctx = closure ? closure : DECL_CONTEXT (t);
> bool member = ctx && TYPE_P (ctx);
> 
> + /* If this is a static lambda, remove the 'this' pointer added in
> + tsubst_lambda_expr now that we know the closure type. */
> + if (lambda_fntype && DECL_STATIC_FUNCTION_P (t))
> + lambda_fntype = static_fn_type (lambda_fntype);
> +
> 
> I discovered this when I decided to try a static lambda to see if that
> would help me narrow down my current problem. I was shocked to find out
> it exhibited the exact same ICE I was trying to fix. So I was going to
> go undo my changes one by one to see what it was, thankfully this was
> the first one I tried, I undid the condition I put on it, and the crash
> was gone.
> 
> Anyway, this has been my whole day so far, I am going to have to look
> deeper to decide how exactly I fix this because I don't think this hack
> that is in place at the moment is the right way to do it. The first
> idea that comes to mind is modifying the decl_context of the call
> operator, but I'm really not sure. I'm just going to take a break, eat
> some pizza, and come back to this hopefully less defeated.
> 
> Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-24 23:26                                                                                               ` waffl3x
@ 2023-11-25  1:14                                                                                                 ` waffl3x
  2023-11-26 21:30                                                                                                   ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-25  1:14 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

OKAY, I figured out SOMETHING, I think this should be fine. As noted in
the comments, this might be a better way of handling the static lambda
case too. This is still a nasty hack so it should probably be done
differently, but I question if making a whole new fntype node in
tsubst_lambda_expr makes sense. On the other hand, maybe there will be
problems if a lambda is already instantiated? I'm not sure, mutating
things this way makes me uneasy though.

I don't like that pt.cc feels like it has a ton of hidden mutations,
it's really hard to follow through it. Would you agree it's in need for
cleanup or am I just not experienced enough in this area yet?

I still have to write good tests for this case and move the error
handling for unrelated object types into here, but my gut tells me this
issue should be fixed now.

Regarding the error handling, I just had a thought about it, I have a
hunch it definitely needs to go in tsubst_template_decl or
tsubst_function_decl. There might need to be more changes to determine
the actual type of the lambda in there, but everything else I've done
changes the implicit object argument to be treated more like a regular
argument, doing error handling for the object type in
tsubst_lambda_expr would be inconsistent with that.

Also, before I forget, you were totally right about just using
dependent_type_p in cp_parser_lambda_declarator_opt. It really is just
that simple.

The code below is not necessarily final, but I wanted to get your
thoughts about doing it this way given my concerns written above. Now
that this problem is out of the way, or at least almost out of the way,
I hopefully can get a version pushed out today.

Alex

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 86c95b278ba..ea2db5c0c9d 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -14407,14 +14407,20 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
       gen_tmpl = NULL_TREE;
       argvec = NULL_TREE;
     }
-
+  /* We hack TYPE_METHOD_BASETYPE onto xobj member functions in
+     tsubst_lambda_expr to get the proper closure type here.  */
   tree closure = (lambda_fntype ? TYPE_METHOD_BASETYPE (lambda_fntype)
                   : NULL_TREE);
   tree ctx = closure ? closure : DECL_CONTEXT (t);
   bool member = ctx && TYPE_P (ctx);
 
   /* If this is a static lambda, remove the 'this' pointer added in
-     tsubst_lambda_expr now that we know the closure type.  */
+     tsubst_lambda_expr now that we know the closure type.
+     I suspect that we can just carry this information down in
+     TYPE_METHOD_BASETYPE without building the full method type in
+     tsubst_lambda_expr.  This wouldn't be ideal but neither is this.
+     Since that field isn't used for anything for static or xobj member
+     functions, it should be fine to do that.  */
   if (lambda_fntype && DECL_STATIC_FUNCTION_P (t))
     lambda_fntype = static_fn_type (lambda_fntype);
 
@@ -14490,12 +14496,12 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
     DECL_NAME (r) = make_conv_op_name (TREE_TYPE (type));
 
   tree parms = DECL_ARGUMENTS (t);
-  if (closure && !DECL_STATIC_FUNCTION_P (t))
+  if (closure && DECL_IOBJ_MEMBER_FUNC_P (t))
     parms = DECL_CHAIN (parms);
   parms = tsubst (parms, args, complain, t);
   for (tree parm = parms; parm; parm = DECL_CHAIN (parm))
     DECL_CONTEXT (parm) = r;
-  if (closure && !DECL_STATIC_FUNCTION_P (t))
+  if (closure && DECL_IOBJ_MEMBER_FUNC_P (t))
     {
       tree tparm = build_this_parm (r, closure, type_memfn_quals (type));
       DECL_NAME (tparm) = closure_identifier;
@@ -19491,9 +19497,16 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
       cp_evaluated ev;
 
       /* Fix the type of 'this'.  */
-      fntype = build_memfn_type (fntype, type,
-				 type_memfn_quals (fntype),
-				 type_memfn_rqual (fntype));
+      if (DECL_IOBJ_MEMBER_FUNC_P (oldfn))
+	fntype = build_memfn_type (fntype, type,
+				   type_memfn_quals (fntype),
+				   type_memfn_rqual (fntype));
+      /* We don't use this field anywhere else for xobj member functions, and
+	 we need a way to pass this information down.  Theres some really
+	 convoluted stuff going on unfortunately, and I can only begin to
+	 unravel it all.   */
+      if (DECL_XOBJ_MEMBER_FUNC_P (oldfn))
+	TYPE_METHOD_BASETYPE (fntype) = type;
       tree inst = (oldtmpl
                    ? tsubst_template_decl (oldtmpl, args, complain,
                                            fntype, tparms)

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-24  6:49                                                                                             ` waffl3x
  2023-11-24 23:26                                                                                               ` waffl3x
@ 2023-11-25 17:32                                                                                               ` Jason Merrill
  2023-11-25 22:59                                                                                                 ` waffl3x
  1 sibling, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-25 17:32 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/24/23 01:49, waffl3x wrote:
>> and this in tsubst_lambda_expr that assumes iobj:
>>
>> /* Fix the type of 'this'. */
>> fntype = build_memfn_type (fntype, type,
>> type_memfn_quals (fntype),
>> type_memfn_rqual (fntype));
> 
> Unfortunately, putting a condition on this had some unforeseen
> consequences. I've been working on this about 8 hours today and I'm a
> little defeated after discovering this.
> 
> commit 39ade88fa1632c659c5c4ed065fa2b62d16a8670
> Author: Jason Merrill <jason@redhat.com>
> Date:   Tue Jan 24 15:29:35 2023 -0500
> 
>      c++: static lambda in template [PR108526]
>      
>      tsubst_lambda_expr uses build_memfn_type to build a METHOD_TYPE for the new
>      lamba op().  This is not what we want for a C++23 static op(), but since we
>      also use that METHOD_TYPE to communicate the closure type down to
>      tsubst_function_decl, let's wait and turn it back at that point.
>      
>              PR c++/108526
>      
>      gcc/cp/ChangeLog:
>      
>      gcc/cp/ChangeLog:
>      
>              * pt.cc (tsubst_function_decl): Handle static lambda.
>      
>      gcc/testsuite/ChangeLog:
>      
>              * g++.dg/cpp23/static-operator-call5.C: New test.
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 2a4d03c5e47..51fc246ed71 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -14306,6 +14306,11 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
>     tree ctx = closure ? closure : DECL_CONTEXT (t);
>     bool member = ctx && TYPE_P (ctx);
>   
> +  /* If this is a static lambda, remove the 'this' pointer added in
> +     tsubst_lambda_expr now that we know the closure type.  */
> +  if (lambda_fntype && DECL_STATIC_FUNCTION_P (t))
> +    lambda_fntype = static_fn_type (lambda_fntype);

Ah, right.  So the simplest thing would be to do the same thing here for 
xob lambdas.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-25 17:32                                                                                               ` [PATCH v5 " Jason Merrill
@ 2023-11-25 22:59                                                                                                 ` waffl3x
  0 siblings, 0 replies; 100+ messages in thread
From: waffl3x @ 2023-11-25 22:59 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

I don't think so, I'm concerned that it might eat the xobj parameter if
we do it that way. Besides, all we want is the lambda type anyway, I
don't want to build a whole new node just to do that. Upon further
consideration I think my solution does work and shouldn't cause
problems. The field that I'm using just doesn't get used anywhere else
for function_type nodes, and we create a new node in
tsubst_function_decl so we don't rely on that field anywhere after it's
used in tsubst_function_decl. I think I will null out the member,
consuming it, so that is clearly communicated.

I am still having issues trying to figure out where exactly to emit
errors from, it's really unclear how this stuff works during template
instantiation. I tried emitting conditionally based on the complain
parameter, but that seemed to suppress errors at all times. While at
the same time, I seemed to observe emitting errors unconditionally
(correctly) not not displaying the error when another candidate was
selected. This leads me to believe that there's already a mechanism for
discarding errors that occur during substitution. I don't really know
what to do for it, but surely I will figure it out now that I'm asking
for help, as tradition dictates.

The other problem I'm having is

auto f0 = [n = 5, &m](this auto const&){ n = 10; };
This errors just fine, the lambda is unconditionally const so
LAMBDA_EXPR_MUTABLE_P flag is set for the closure.

This on the other hand does not. The constness of the captures depends
on (I assume) LAMBDA_EXPR_MUTABLE_P so all the captures are non-const
here.
auto f1 = [n = 5](this auto&& self){ n = 10; };
as_const(f1)();

I don't know the right way to solve this, I haven't even really tracked
down where the tree is built up. At the moment I think that hacking
something into instantiate_body to make them const is the right
decision, as the type of the closure shouldn't be changing when
instantiating it's call operator template. I'm not sure this is really
actually the right call though because it's another thing that feels
very much like a hack. As I near completion of the patch it feels like
more and more of the solutions I'm finding are hacks, and I don't know
if it's just a consequence of old assumptions or if I'm really just
reaching for the easy(ier) solutions way too quickly now.

These problems are the last few that I have and they are taking me way
more time than I am comfortable with.

I was going to close out this message there but I just realized why
exactly you think erroring in instantiate_body is too late, it's
because at that point an error is an error, while if we error a little
bit earlier during substitution it's not a hard error. I'm glad I
realized this now, because I am much more confident in how to implement
the errors for unrelated type now. I'm still a little confused on what
the exact protocol for emitting errors at this stage is, as there
aren't many explicit errors written in. It seems to me like the errors
are supposed to be emitted when calling back into non-template stages
of the compiler with substituted types. It also seems like that hasn't
always been strictly followed, and I hate to contribute to code debt
but I'm not sure how I would do it in this case, nor if I actually have
a valid understanding of how this all works.

Given my above realization, I'm now more confident in figuring out how
exactly to implement the errors for unrelated types. I guess I'm rubber
ducking a little bit in these e-mails. On the other hand, if the
conclusions I'm forming are incorrect, it's probably still helpful to
know the journey I took to get to them. I'm thinking it's probably
better I still include my initial concerns even though I feel like I
know the answer now.

One side note before I close up here, I don't like when *_P macros are
used to set flags. Is this
something else we should clean up in the future? I'm beginning to
wonder if an overhaul that gets rid of the macros from the public
interface is a good idea. I'm reluctant to suggest that as I've really
warmed up to the macros a lot. They are used in a consistent and easy
to understand way which is highly unlike the bad uses of macros that
I've seen before that really obscure what's actually going on. But they
are still macros, so maybe moving away from them is the right call,
especially since there has started to be a mix-up of macros and
functions for the same purposes. I'm mildly of the opinion that only
one style should be used (in the public interface) because mixing them
causes confusion, it did for me anyway. Perhaps I should open a thread
on the general mail list and see what others think, get some input
before I decide which direction to go with it. To be clear, when I say
getting rid of macros, I want to emphasize I mean only in the public
interface, I don't see any value in getting rid of macros under the
hood as the way the checking macros are implemented is already really
good and works. It would only cause problems to try to move away from
that. I think I'll probably start to mess around with this idea as soon
as this patch is done.

That unrelated rambling aside, I will get back to work and try my best
to finish the errors for unrelated types today. It feels kind of bad to
work so hard on this since I've noticed that it's almost impossible to
coerce the case other than overload resolution when taking the address
of a lambdas operator. But that's not an excuse to leave a hole in the
compiler so, it's still gotta get done. I don't think I will get
rejection of mutation of captures done, especially since I'm uncertain
of what the right way to do it is, but maybe I'll work fast.


On Saturday, November 25th, 2023 at 10:32 AM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 11/24/23 01:49, waffl3x wrote:
> 
> > > and this in tsubst_lambda_expr that assumes iobj:
> > > 
> > > /* Fix the type of 'this'. */
> > > fntype = build_memfn_type (fntype, type,
> > > type_memfn_quals (fntype),
> > > type_memfn_rqual (fntype));
> > 
> > Unfortunately, putting a condition on this had some unforeseen
> > consequences. I've been working on this about 8 hours today and I'm a
> > little defeated after discovering this.
> > 
> > commit 39ade88fa1632c659c5c4ed065fa2b62d16a8670
> > Author: Jason Merrill jason@redhat.com
> > Date: Tue Jan 24 15:29:35 2023 -0500
> > 
> > c++: static lambda in template [PR108526]
> > 
> > tsubst_lambda_expr uses build_memfn_type to build a METHOD_TYPE for the new
> > lamba op(). This is not what we want for a C++23 static op(), but since we
> > also use that METHOD_TYPE to communicate the closure type down to
> > tsubst_function_decl, let's wait and turn it back at that point.
> > 
> > PR c++/108526
> > 
> > gcc/cp/ChangeLog:
> > 
> > gcc/cp/ChangeLog:
> > 
> > * pt.cc (tsubst_function_decl): Handle static lambda.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > * g++.dg/cpp23/static-operator-call5.C: New test.
> > 
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 2a4d03c5e47..51fc246ed71 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -14306,6 +14306,11 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
> > tree ctx = closure ? closure : DECL_CONTEXT (t);
> > bool member = ctx && TYPE_P (ctx);
> > 
> > + /* If this is a static lambda, remove the 'this' pointer added in
> > + tsubst_lambda_expr now that we know the closure type. */
> > + if (lambda_fntype && DECL_STATIC_FUNCTION_P (t))
> > + lambda_fntype = static_fn_type (lambda_fntype);
> 
> 
> Ah, right. So the simplest thing would be to do the same thing here for
> xob lambdas.
> 
> Jason

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-25  1:14                                                                                                 ` waffl3x
@ 2023-11-26 21:30                                                                                                   ` Jason Merrill
  2023-11-26 23:10                                                                                                     ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-26 21:30 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/24/23 20:14, waffl3x wrote:
> OKAY, I figured out SOMETHING, I think this should be fine. As noted in
> the comments, this might be a better way of handling the static lambda
> case too. This is still a nasty hack so it should probably be done
> differently, but I question if making a whole new fntype node in
> tsubst_lambda_expr makes sense. On the other hand, maybe there will be
> problems if a lambda is already instantiated? I'm not sure, mutating
> things this way makes me uneasy though.

A problem with changing TYPE_METHOD_BASETYPE for a FUNCTION_TYPE is that 
all equivalent FUNCTION_TYPE share the same tree node (through 
type_hash_canon), so you're messing with the type of unrelated functions 
at the same time.  I think it's better to stick with the way static 
lambdas are handled.

> I don't like that pt.cc feels like it has a ton of hidden mutations,
> it's really hard to follow through it. Would you agree it's in need for
> cleanup or am I just not experienced enough in this area yet?

I'm sure there are things that could use cleaning up, but I'm not 
thinking of anything specific offhand.  Any particular examples?

> Regarding the error handling, I just had a thought about it, I have a
> hunch it definitely needs to go in tsubst_template_decl or
> tsubst_function_decl. There might need to be more changes to determine
> the actual type of the lambda in there, but everything else I've done
> changes the implicit object argument to be treated more like a regular
> argument, doing error handling for the object type in
> tsubst_lambda_expr would be inconsistent with that.

But the rule about unrelated type is specific to lambdas, so doing it in 
tsubst_lambda_expr seems preferable to adding a lambda special case to 
generic code.

> The other problem I'm having is
> 
> auto f0 = [n = 5, &m](this auto const&){ n = 10; };
> This errors just fine, the lambda is unconditionally const so
> LAMBDA_EXPR_MUTABLE_P flag is set for the closure.
> 
> This on the other hand does not. The constness of the captures depends
> on (I assume) LAMBDA_EXPR_MUTABLE_P so all the captures are non-const
> here.
> auto f1 = [n = 5](this auto&& self){ n = 10; };
> as_const(f1)();

That sounds correct, why would this be an error?

The constness of the captures doesn't depend on LAMBDA_EXPR_MUTABLE_P, 
it depends on the type of the object parameter, which in this case is 
non-const, so so are the captures.

> I was going to close out this message there but I just realized why
> exactly you think erroring in instantiate_body is too late, it's
> because at that point an error is an error, while if we error a little
> bit earlier during substitution it's not a hard error. I'm glad I
> realized this now, because I am much more confident in how to implement
> the errors for unrelated type now. I'm still a little confused on what
> the exact protocol for emitting errors at this stage is, as there
> aren't many explicit errors written in. It seems to me like the errors
> are supposed to be emitted when calling back into non-template stages
> of the compiler with substituted types. It also seems like that hasn't
> always been strictly followed, and I hate to contribute to code debt
> but I'm not sure how I would do it in this case, nor if I actually have
> a valid understanding of how this all works.

Most errors that could occur in template substitution are guarded by if 
(complain & tf_error) so that they aren't emitted during deduction 
substitution.  It's better if those are in code that's shared with the 
non-template case, but sometimes that isn't feasible.

> One side note before I close up here, I don't like when *_P macros are
> used to set flags. Is this something else we should clean up in the future?

I don't think so, a wholesale renaming would just confuse existing 
developers.

> I'm beginning to
> wonder if an overhaul that gets rid of the macros from the public
> interface is a good idea. I'm reluctant to suggest that as I've really
> warmed up to the macros a lot. They are used in a consistent and easy
> to understand way which is highly unlike the bad uses of macros that
> I've seen before that really obscure what's actually going on. But they
> are still macros, so maybe moving away from them is the right call,
> especially since there has started to be a mix-up of macros and
> functions for the same purposes. I'm mildly of the opinion that only
> one style should be used (in the public interface) because mixing them
> causes confusion, it did for me anyway. Perhaps I should open a thread
> on the general mail list and see what others think, get some input
> before I decide which direction to go with it. To be clear, when I say
> getting rid of macros, I want to emphasize I mean only in the public
> interface, I don't see any value in getting rid of macros under the
> hood as the way the checking macros are implemented is already really
> good and works. It would only cause problems to try to move away from
> that. I think I'll probably start to mess around with this idea as soon
> as this patch is done.

There has been some movement from macros toward inline functions since 
the switch to C++, but that also has complications with 
const-correctness, declaration order, and bit-fields.  It's probably 
good to use inlines instead of larger macros when feasible, but the 
accessors should probably stay macros.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-26 21:30                                                                                                   ` Jason Merrill
@ 2023-11-26 23:10                                                                                                     ` waffl3x
  2023-11-27  1:16                                                                                                       ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-26 23:10 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches






On Sunday, November 26th, 2023 at 2:30 PM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 11/24/23 20:14, waffl3x wrote:
> 
> > OKAY, I figured out SOMETHING, I think this should be fine. As noted in
> > the comments, this might be a better way of handling the static lambda
> > case too. This is still a nasty hack so it should probably be done
> > differently, but I question if making a whole new fntype node in
> > tsubst_lambda_expr makes sense. On the other hand, maybe there will be
> > problems if a lambda is already instantiated? I'm not sure, mutating
> > things this way makes me uneasy though.
> 
> 
> A problem with changing TYPE_METHOD_BASETYPE for a FUNCTION_TYPE is that
> all equivalent FUNCTION_TYPE share the same tree node (through
> type_hash_canon), so you're messing with the type of unrelated functions
> at the same time. I think it's better to stick with the way static
> lambdas are handled.

Yes, that was my concern as well, without even thinking about hashing.
I will investigate it deeper but I'm pretty sure TYPE_METHOD_BASETYPE,
while a valid field, is not used at all for function_type nodes. I had
to look into this when looking into the DECL_CONTEXT stuff previously,
so I'm pretty confident in this. Come to think of it, does hashing even
take fields that aren't "supposed" to be set for a node into account?
Well, even so, I can just clear it after it gets used as we just need
it to pass the closure type down. Perhaps I should have led with this,
but as it stands the version that uses TYPE_METHOD_BASETYPE bootstraps
with no regressions. I'll still look deeper but I'm pretty confident in
my decision here, I really don't want to try to unravel what
build_memfn_type does, I would rather find a whole different way of
passing that information down.

> > I don't like that pt.cc feels like it has a ton of hidden mutations,
> > it's really hard to follow through it. Would you agree it's in need for
> > cleanup or am I just not experienced enough in this area yet?
> 
> 
> I'm sure there are things that could use cleaning up, but I'm not
> thinking of anything specific offhand. Any particular examples?

Not at the moment, just after 8 hours of following the execution path
you start to feel like something could be done better. I'm sorry if I
sound bitter but to be honest I kind of am. :^)

I don't think it's going to be my first goal to look at pt.cc for
things to clean up, I honestly didn't want to go through here at all.
Maybe if I look at it for longer I will feel better about it but I want
to make that a task for the future.

> > Regarding the error handling, I just had a thought about it, I have a
> > hunch it definitely needs to go in tsubst_template_decl or
> > tsubst_function_decl. There might need to be more changes to determine
> > the actual type of the lambda in there, but everything else I've done
> > changes the implicit object argument to be treated more like a regular
> > argument, doing error handling for the object type in
> > tsubst_lambda_expr would be inconsistent with that.
> 
> 
> But the rule about unrelated type is specific to lambdas, so doing it in
> tsubst_lambda_expr seems preferable to adding a lambda special case to
> generic code.

No, while that might seem intuitive, the error is not during
instantiation of the closure object's type, it's during instantiation
of the function template. The type of the closure will always be the
type of the closure, but the type of the explicit object parameter can
be different. In the following example we don't go into
tsubst_lambda_expr at all.

template<typename T>
struct S : T {
  using T::operator();
  using s_tag = void;
};

int main()
{
  auto f = [](this auto self){
    if constexpr ( requires{typename decltype(self)::s_tag;} ) {
      return 5;
    }
    else {
      return 10;
    }
  };
  auto s = S{f};

  if (f () != 10)
    __builtin_abort ();
  if (s () != 5)
    __builtin_abort ();
}

I hope this demonstrates that placing the check in tsubst_function_decl
is the correct way to do this.

Now with that out of the way, I do still kind of feel icky about it.
This really is a MASSIVE edge case that will almost never matter. As
far as I know, instantiating explicitly like so... 

auto f = [x = 42](this auto&&) -> int { return x; };
int (*fp)(int&) = &decltype(f)::operator();

...is the only way to coerce the explicit object parameter of the
lambda's call operator into deducing as an unrelated type. Cases that
are not deduced can be caught trivially while parsing the lambda and
are the only reasonable cases where you might have an unrelated type.
Perhaps it might become relevant in the future if a proposal like
https://atomgalaxy.github.io/isocpp-1107/D1107.html ever made it in,
but we aren't there yet. So as it stands, I'm pretty certain that it's
just impossible to instantiate a lambda's call operator with an
unrelated xobj parameter type except for the above case with
address-of. If you can think of another, please do share, but I've
spent a fair amount of time on it and came up with nothing.

Anyway, due to this, I am not currently concerned about the lack of a
diagnostic, and I believe my implementation is the most correct way to
do it. The problem with resolve_address_of_overloaded_function that I
go into below (depending on if you agree that it's a problem) is what
needs to be fixed to allow the current diagnostic to be properly
emitted. As of right now, there is no execution path that I know of
where the diagnostic will be properly emitted.

I go into more detail about this in the comment in
tsubst_function_decl, although I do have to revise it a bit as I wrote
it before I had a discussion with another developer on the correct
behavior of the following case. I previously wondered if the overload
resolution from initializing p1 should result in a hard fault.

template<typename T>
struct S : T {
  using T::operator();
  void operator()(this int&, auto) {}
};

int main()
{
  auto s0 = S{[](this auto&&, auto){}};
  // error, ambiguous
  void (*p0)(int&, int) = &decltype(s0)::operator();

  auto s1 = S{[x = 42](this auto&&, auto){}};
  // SFINAE's, calls S::operator()
  void (*p1)(int&, int) = &decltype(s1)::operator();
}

The wording in [expr.prim.lambda.closure-5] is similar to that in
[dcl.fct-15], and we harness SFINAE in that case, so he concluded that
this case (initializing p1) should also harness SFINAE. Luckily for me,
that is what my implementation already did. The big comment in
tsubst_function_decl does not reflect this conclusion yet so I will
have to revise it a little bit before I can submit v6, but once I do
that I will follow up this e-mail with it.
https://eel.is/c++draft/dcl.fct#15
https://eel.is/c++draft/expr.prim#lambda.closure-5

> > The other problem I'm having is
> > 
> > auto f0 = [n = 5, &m](this auto const&){ n = 10; };
> > This errors just fine, the lambda is unconditionally const so
> > LAMBDA_EXPR_MUTABLE_P flag is set for the closure.
> > 
> > This on the other hand does not. The constness of the captures depends
> > on (I assume) LAMBDA_EXPR_MUTABLE_P so all the captures are non-const
> > here.
> > auto f1 = [n = 5](this auto&& self){ n = 10; };
> > as_const(f1)();
> 
> 
> That sounds correct, why would this be an error?
> 
> The constness of the captures doesn't depend on LAMBDA_EXPR_MUTABLE_P,
> it depends on the type of the object parameter, which in this case is
> non-const, so so are the captures.

Oops, I should have just made a less terse example, you missed the
"as_const" in the call to the lambda. The object parameter should be
deduced as const here. I definitely should have made that example
better, my bad.

Anyway, yes I agree that it's -supposed- to depend on the object
parameter, but that isn't the behavior I'm observing right now. Perhaps
you're right that it's not based on the LAMBDA_EXPR_MUTABLE_P flag, I
mostly just assumed that was the case, but it does seem to be based on
the closure object. Now that I'm clear of the previous problem today
will be spent focusing on this bug.

> > I was going to close out this message there but I just realized why
> > exactly you think erroring in instantiate_body is too late, it's
> > because at that point an error is an error, while if we error a little
> > bit earlier during substitution it's not a hard error. I'm glad I
> > realized this now, because I am much more confident in how to implement
> > the errors for unrelated type now. I'm still a little confused on what
> > the exact protocol for emitting errors at this stage is, as there
> > aren't many explicit errors written in. It seems to me like the errors
> > are supposed to be emitted when calling back into non-template stages
> > of the compiler with substituted types. It also seems like that hasn't
> > always been strictly followed, and I hate to contribute to code debt
> > but I'm not sure how I would do it in this case, nor if I actually have
> > a valid understanding of how this all works.
> 
> 
> Most errors that could occur in template substitution are guarded by if
> (complain & tf_error) so that they aren't emitted during deduction
> substitution. It's better if those are in code that's shared with the
> non-template case, but sometimes that isn't feasible.

Yeah, makes sense, but the case going through
resolve_address_of_overloaded_function -> fn_type_unification ->
instantiate_template -> tsubst_decl -> tsubst_function_decl does not
behave super nicely. I tried adding (complain & tf_error) to the
explain_p parameter of fn_type_unification in
resolve_address_of_overloaded_function, but I immediately learned why
that wasn't being handled that way. I think
resolve_address_of_overloaded_function has a number of problems and
should probably use print_z_candidates instead of print_candidates in
the long term. This would actually solve the problem in
tsubst_function_decl where it never emits an error for an unrelated
type, print_z_candidates does call fn_type_unification with true passed
to explain_p. I go into this in detail in the large comment in
tsubst_function_decl (that I have to revise) so I won't waste time
rehashing it here.

Thank you for explaining though, some of the code doesn't reflect the
method you've relayed here (such as an unguarded error_at in
tsubst_function_decl) so I was starting to get confused on what exactly
is supposed to be going on. This clarifies things for me.

> > One side note before I close up here, I don't like when *_P macros are
> > used to set flags. Is this something else we should clean up in the future?
> 
> 
> I don't think so, a wholesale renaming would just confuse existing
> developers.

I don't think changing *_P macros to be immutable, and adding
corresponding *_SET or *_FLAG macros (or functions) would be confusing.
I think it's much more confusing to have the inconsistent behavior.

In case it's not clear, I'm not proposing we change any of the names
here. I disliked the *_P name scheme at first, but as I became familiar
with it I think it's good, especially once I realized that it stands
for predicate. Even at that point though I was pretty sure that
convention should stick as it's not worth the confusion just because a
single developer, let alone a new one (me), doesn't like it. And I like
it now so I absolutely wouldn't propose making that change.

But I do think they should be immutable, this has other benefits anyway
since it will be more clear where we observe state, and where we mutate
it.

> > I'm beginning to
> > wonder if an overhaul that gets rid of the macros from the public
> > interface is a good idea. I'm reluctant to suggest that as I've really
> > warmed up to the macros a lot. They are used in a consistent and easy
> > to understand way which is highly unlike the bad uses of macros that
> > I've seen before that really obscure what's actually going on. But they
> > are still macros, so maybe moving away from them is the right call,
> > especially since there has started to be a mix-up of macros and
> > functions for the same purposes. I'm mildly of the opinion that only
> > one style should be used (in the public interface) because mixing them
> > causes confusion, it did for me anyway. Perhaps I should open a thread
> > on the general mail list and see what others think, get some input
> > before I decide which direction to go with it. To be clear, when I say
> > getting rid of macros, I want to emphasize I mean only in the public
> > interface, I don't see any value in getting rid of macros under the
> > hood as the way the checking macros are implemented is already really
> > good and works. It would only cause problems to try to move away from
> > that. I think I'll probably start to mess around with this idea as soon
> > as this patch is done.
> 
> 
> There has been some movement from macros toward inline functions since
> the switch to C++, but that also has complications with
> const-correctness, declaration order, and bit-fields. It's probably
> good to use inlines instead of larger macros when feasible, but the
> accessors should probably stay macros.
> 
> Jason

Yeah I've noticed the pains with const-correctness, it seems like a
nightmare. However, thats one of the reasons I want to tackle it, I
think fixing and propagating const correctness throughout the code base
would be the biggest thing that could be done to improve it. I have
really poor short term memory so once you start piling on mutations I
have a hard time reasoning about code really quickly.

I see what you mean regarding accessors, trying to access or mutate
bitfields through functions might be way more trouble than it's worth,
especially if we wanted to keep the `SOME_FIELD (some_node) =
new_value` pattern. We would need to return proxy objects and those
just don't always optimize properly in my (limited) experience. I plan
on playing around with some solutions once this patch is done, I'll see
what feels good, what feels bad, whether it's actually viable to change
the accessors into functions, etc.

One developer in IRC expressed that he wished that accessors were
member functions, which I also have kind of wished before. I feel like
it shouldn't be the first refactor though.

I will follow up this e-mail shortly with v6, I just have to clean up
that comment as I said. I don't want to spend too much time on it
because I want to get a proof-of-concept fix for the "lambda capture's
not const" bug today but I'll try to throw it together with a little
more effort than v5.

I do want to note though, despite my claims above that the patch is
bootstrapped and tested without regressions, I did have to make a small
change in tsubst_lambda_expr. When I refactored the changes there I
made a thinko and checked for iobj member function in the condition
before build_memfn_type, completely forgetting about the static lambda
case. I changed it back to !DECL_XOBJ_MEMBER_FUNC_P and ran the 1 test
that failed (static-operator-call5.C) and confirmed that it worked
again, but running the full test suite takes me about an hour and a
half. SO, point is, I'm confident that change broke nothing, but due to
the change technically this version is not regression tested. I am
running a bootstrap since that will finish before I'm done revising the
comment though. Hope this isn't a problem.

I will try to finish up quickly so you can get your eyes on the patch
ASAP.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-26 23:10                                                                                                     ` waffl3x
@ 2023-11-27  1:16                                                                                                       ` Jason Merrill
       [not found]                                                                                                         ` <BEI8PD7nktTuX7dimb22uDnR0b8Bc8ozi4xx9KbiEFj8TjgUCxMfEPpcIPL0bkdThBBab97T1uEJ9rUM3va1eiE1TyRw=5FiLrxwKgg30ZaW0=3D@protonmail.com>
                                                                                                                           ` (2 more replies)
  0 siblings, 3 replies; 100+ messages in thread
From: Jason Merrill @ 2023-11-27  1:16 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/26/23 18:10, waffl3x wrote:
> On Sunday, November 26th, 2023 at 2:30 PM, Jason Merrill <jason@redhat.com> wrote:
>> On 11/24/23 20:14, waffl3x wrote:
>>
>>> OKAY, I figured out SOMETHING, I think this should be fine. As noted in
>>> the comments, this might be a better way of handling the static lambda
>>> case too. This is still a nasty hack so it should probably be done
>>> differently, but I question if making a whole new fntype node in
>>> tsubst_lambda_expr makes sense. On the other hand, maybe there will be
>>> problems if a lambda is already instantiated? I'm not sure, mutating
>>> things this way makes me uneasy though.
>>
>> A problem with changing TYPE_METHOD_BASETYPE for a FUNCTION_TYPE is that
>> all equivalent FUNCTION_TYPE share the same tree node (through
>> type_hash_canon), so you're messing with the type of unrelated functions
>> at the same time. I think it's better to stick with the way static
>> lambdas are handled.
> 
> Yes, that was my concern as well, without even thinking about hashing.
> I will investigate it deeper but I'm pretty sure TYPE_METHOD_BASETYPE,
> while a valid field, is not used at all for function_type nodes. I had
> to look into this when looking into the DECL_CONTEXT stuff previously,
> so I'm pretty confident in this. Come to think of it, does hashing even
> take fields that aren't "supposed" to be set for a node into account?

It doesn't.  The issue is messing with the type of (potentially) a lot 
of different functions.  Even if it doesn't actually break anything, it 
seems like the kind of hidden mutation that you were objecting to.

> Well, even so, I can just clear it after it gets used as we just need
> it to pass the closure type down. Perhaps I should have led with this,
> but as it stands the version that uses TYPE_METHOD_BASETYPE bootstraps
> with no regressions. I'll still look deeper but I'm pretty confident in
> my decision here, I really don't want to try to unravel what
> build_memfn_type does, I would rather find a whole different way of
> passing that information down.

But the existing code already works fine, it's just a question of 
changing the conditions so we handle xob lambdas the same way as static.

>>> Regarding the error handling, I just had a thought about it, I have a
>>> hunch it definitely needs to go in tsubst_template_decl or
>>> tsubst_function_decl. There might need to be more changes to determine
>>> the actual type of the lambda in there, but everything else I've done
>>> changes the implicit object argument to be treated more like a regular
>>> argument, doing error handling for the object type in
>>> tsubst_lambda_expr would be inconsistent with that.
>>
>> But the rule about unrelated type is specific to lambdas, so doing it in
>> tsubst_lambda_expr seems preferable to adding a lambda special case to
>> generic code.
> 
> No, while that might seem intuitive, the error is not during
> instantiation of the closure object's type, it's during instantiation
> of the function template. The type of the closure will always be the
> type of the closure, but the type of the explicit object parameter can
> be different. In the following example we don't go into
> tsubst_lambda_expr at all.

Ah, good point.

> I hope this demonstrates that placing the check in tsubst_function_decl
> is the correct way to do this.

Sounds right.

> Now with that out of the way, I do still kind of feel icky about it.
> This really is a MASSIVE edge case that will almost never matter. As
> far as I know, instantiating explicitly like so...
> 
> auto f = [x = 42](this auto&&) -> int { return x; };
> int (*fp)(int&) = &decltype(f)::operator();
> 
> ...is the only way to coerce the explicit object parameter of the
> lambda's call operator into deducing as an unrelated type. Cases that
> are not deduced can be caught trivially while parsing the lambda and
> are the only reasonable cases where you might have an unrelated type.
> Perhaps it might become relevant in the future if a proposal like
> https://atomgalaxy.github.io/isocpp-1107/D1107.html ever made it in,
> but we aren't there yet. So as it stands, I'm pretty certain that it's
> just impossible to instantiate a lambda's call operator with an
> unrelated xobj parameter type except for the above case with
> address-of. If you can think of another, please do share, but I've
> spent a fair amount of time on it and came up with nothing.

I think you're right.

> Anyway, due to this, I am not currently concerned about the lack of a
> diagnostic, and I believe my implementation is the most correct way to
> do it. The problem with resolve_address_of_overloaded_function that I
> go into below (depending on if you agree that it's a problem) is what
> needs to be fixed to allow the current diagnostic to be properly
> emitted. As of right now, there is no execution path that I know of
> where the diagnostic will be properly emitted.
> 
> I go into more detail about this in the comment in
> tsubst_function_decl, although I do have to revise it a bit as I wrote
> it before I had a discussion with another developer on the correct
> behavior of the following case. I previously wondered if the overload
> resolution from initializing p1 should result in a hard fault.
> 
> template<typename T>
> struct S : T {
>    using T::operator();
>    void operator()(this int&, auto) {}
> };
> 
> int main()
> {
>    auto s0 = S{[](this auto&&, auto){}};
>    // error, ambiguous
>    void (*p0)(int&, int) = &decltype(s0)::operator();
> 
>    auto s1 = S{[x = 42](this auto&&, auto){}};
>    // SFINAE's, calls S::operator()
>    void (*p1)(int&, int) = &decltype(s1)::operator();
> }
> 
> The wording in [expr.prim.lambda.closure-5] is similar to that in
> [dcl.fct-15], and we harness SFINAE in that case, so he concluded that
> this case (initializing p1) should also harness SFINAE.

Agreed.

>>> The other problem I'm having is
>>>
>>> auto f0 = [n = 5, &m](this auto const&){ n = 10; };
>>> This errors just fine, the lambda is unconditionally const so
>>> LAMBDA_EXPR_MUTABLE_P flag is set for the closure.
>>>
>>> This on the other hand does not. The constness of the captures depends
>>> on (I assume) LAMBDA_EXPR_MUTABLE_P so all the captures are non-const
>>> here.
>>> auto f1 = [n = 5](this auto&& self){ n = 10; };
>>> as_const(f1)();
>>
>> That sounds correct, why would this be an error?
>>
>> The constness of the captures doesn't depend on LAMBDA_EXPR_MUTABLE_P,
>> it depends on the type of the object parameter, which in this case is
>> non-const, so so are the captures.
> 
> Oops, I should have just made a less terse example, you missed the
> "as_const" in the call to the lambda. The object parameter should be
> deduced as const here. I definitely should have made that example
> better, my bad.

Ah, yes, I see it now.

>>> I was going to close out this message there but I just realized why
>>> exactly you think erroring in instantiate_body is too late, it's
>>> because at that point an error is an error, while if we error a little
>>> bit earlier during substitution it's not a hard error. I'm glad I
>>> realized this now, because I am much more confident in how to implement
>>> the errors for unrelated type now. I'm still a little confused on what
>>> the exact protocol for emitting errors at this stage is, as there
>>> aren't many explicit errors written in. It seems to me like the errors
>>> are supposed to be emitted when calling back into non-template stages
>>> of the compiler with substituted types. It also seems like that hasn't
>>> always been strictly followed, and I hate to contribute to code debt
>>> but I'm not sure how I would do it in this case, nor if I actually have
>>> a valid understanding of how this all works.
>>
>> Most errors that could occur in template substitution are guarded by if
>> (complain & tf_error) so that they aren't emitted during deduction
>> substitution. It's better if those are in code that's shared with the
>> non-template case, but sometimes that isn't feasible.
> 
> Yeah, makes sense, but the case going through
> resolve_address_of_overloaded_function -> fn_type_unification ->
> instantiate_template -> tsubst_decl -> tsubst_function_decl does not
> behave super nicely. I tried adding (complain & tf_error) to the
> explain_p parameter of fn_type_unification in
> resolve_address_of_overloaded_function, but I immediately learned why
> that wasn't being handled that way. I think
> resolve_address_of_overloaded_function has a number of problems and
> should probably use print_z_candidates instead of print_candidates in
> the long term. This would actually solve the problem in
> tsubst_function_decl where it never emits an error for an unrelated
> type, print_z_candidates does call fn_type_unification with true passed
> to explain_p. I go into this in detail in the large comment in
> tsubst_function_decl (that I have to revise) so I won't waste time
> rehashing it here.

Makes sense, we won't see a message about the unrelated type because 
print_candidates doesn't repeat deduction like print_z_candidates does.

> Thank you for explaining though, some of the code doesn't reflect the
> method you've relayed here (such as an unguarded error_at in
> tsubst_function_decl) so I was starting to get confused on what exactly
> is supposed to be going on. This clarifies things for me.

That unguarded error does indeed look suspicious, though it could be 
that OMP reductions are called in a sufficiently unusual way that it 
isn't actually a problem in practice. /shrug

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-27  1:16                                                                                                       ` Jason Merrill
       [not found]                                                                                                         ` <BEI8PD7nktTuX7dimb22uDnR0b8Bc8ozi4xx9KbiEFj8TjgUCxMfEPpcIPL0bkdThBBab97T1uEJ9rUM3va1eiE1TyRw=5FiLrxwKgg30ZaW0=3D@protonmail.com>
@ 2023-11-27  1:30                                                                                                         ` waffl3x
  2023-11-27  1:44                                                                                                         ` waffl3x
  2 siblings, 0 replies; 100+ messages in thread
From: waffl3x @ 2023-11-27  1:30 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

> > Now with that out of the way, I do still kind of feel icky about it.
> > This really is a MASSIVE edge case that will almost never matter. As
> > far as I know, instantiating explicitly like so...
> > 
> > auto f = [x = 42](this auto&&) -> int { return x; };
> > int (*fp)(int&) = &decltype(f)::operator();
> > 
> > ...is the only way to coerce the explicit object parameter of the
> > lambda's call operator into deducing as an unrelated type. Cases that
> > are not deduced can be caught trivially while parsing the lambda and
> > are the only reasonable cases where you might have an unrelated type.
> > Perhaps it might become relevant in the future if a proposal like
> > https://atomgalaxy.github.io/isocpp-1107/D1107.html ever made it in,
> > but we aren't there yet. So as it stands, I'm pretty certain that it's
> > just impossible to instantiate a lambda's call operator with an
> > unrelated xobj parameter type except for the above case with
> > address-of. If you can think of another, please do share, but I've
> > spent a fair amount of time on it and came up with nothing.
> 
> 
> I think you're right.
> 
I was about to send a quick e-mail amending this, before I respond to
everything else I want to include this test case I just came up with minutes
ago.

template<typename T>
struct S : T {
  using T::operator();
  operator int() const {return {};}
};

int main()
{
  auto s0 = S{[](this auto&& self) { return self; }};
  auto s1 = S{[x = 0](this auto&& self) { return self; }};

  s0.operator()<int>();
  s1.operator()<int>();
}

So I was wrong, but, the good news is that this does demonstrate that there
is a code path where my diagnostic works.

template<typename T>
concept NotInt = (!__is_same (T, int));

template<bool> struct enable_if {};
template<> struct enable_if<true> { using type = decltype(nullptr); };
template<bool B> using enable_if_t = typename enable_if<B>::type;

template<NotInt T>
void using_concepts(T) {}

template<typename T, enable_if_t<!__is_same (T, int)> = nullptr>
void using_enable_if(T) {}

void test()
{
  void (*fp_concepts)(int) = &using_concepts;
  void (*fp_enable_if)(int) = &using_enable_if;

  using_concepts(0);
  using_enable_if(0);
}

I also have this test case that demonstrates the difference in diagnostic
quality. This is unrelated to explicit object member functions though, but
it does demonstrate that the diagnostics that I currently produce are in
equal quality to the ones already produced in these cases.

At this point I feel like I am unlikely to start fixing the bug with
captures not being treated as const tonight. Cleaning up the tests is taking
me longer than I thought.

Anyway I'm just rushing this e-mail to clarify this mistake, admittedly I am
a little excited to have found (which in hindsight should have been obvious)
a test case that more directly calls a lambda's call operator with an
unrelated type.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-27  1:16                                                                                                       ` Jason Merrill
       [not found]                                                                                                         ` <BEI8PD7nktTuX7dimb22uDnR0b8Bc8ozi4xx9KbiEFj8TjgUCxMfEPpcIPL0bkdThBBab97T1uEJ9rUM3va1eiE1TyRw=5FiLrxwKgg30ZaW0=3D@protonmail.com>
  2023-11-27  1:30                                                                                                         ` waffl3x
@ 2023-11-27  1:44                                                                                                         ` waffl3x
  2023-11-27  2:40                                                                                                           ` Jason Merrill
  2023-11-27  5:35                                                                                                           ` [PATCH v6 " waffl3x
  2 siblings, 2 replies; 100+ messages in thread
From: waffl3x @ 2023-11-27  1:44 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

> > > > OKAY, I figured out SOMETHING, I think this should be fine. As noted in
> > > > the comments, this might be a better way of handling the static lambda
> > > > case too. This is still a nasty hack so it should probably be done
> > > > differently, but I question if making a whole new fntype node in
> > > > tsubst_lambda_expr makes sense. On the other hand, maybe there will be
> > > > problems if a lambda is already instantiated? I'm not sure, mutating
> > > > things this way makes me uneasy though.
> > > 
> > > A problem with changing TYPE_METHOD_BASETYPE for a FUNCTION_TYPE is that
> > > all equivalent FUNCTION_TYPE share the same tree node (through
> > > type_hash_canon), so you're messing with the type of unrelated functions
> > > at the same time. I think it's better to stick with the way static
> > > lambdas are handled.
> > 
> > Yes, that was my concern as well, without even thinking about hashing.
> > I will investigate it deeper but I'm pretty sure TYPE_METHOD_BASETYPE,
> > while a valid field, is not used at all for function_type nodes. I had
> > to look into this when looking into the DECL_CONTEXT stuff previously,
> > so I'm pretty confident in this. Come to think of it, does hashing even
> > take fields that aren't "supposed" to be set for a node into account?
> 
> 
> It doesn't. The issue is messing with the type of (potentially) a lot
> of different functions. Even if it doesn't actually break anything, it
> seems like the kind of hidden mutation that you were objecting to.

Oh... yeah..., I see the issue now. I still don't think the solution
used for static lambdas will work, or be painless anyhow, but if I
can't find something better I will try to use that one.

> > Well, even so, I can just clear it after it gets used as we just need
> > it to pass the closure type down. Perhaps I should have led with this,
> > but as it stands the version that uses TYPE_METHOD_BASETYPE bootstraps
> > with no regressions. I'll still look deeper but I'm pretty confident in
> > my decision here, I really don't want to try to unravel what
> > build_memfn_type does, I would rather find a whole different way of
> > passing that information down.
> 
> 
> But the existing code already works fine, it's just a question of
> changing the conditions so we handle xob lambdas the same way as static.

I'm still concerned it wont cooperate with xobj parameters of unrelated
type, but like I said, you've indicated my solution is definitely wrong
so I'll look at fixing it.

> > Anyway, due to this, I am not currently concerned about the lack of a
> > diagnostic, and I believe my implementation is the most correct way to
> > do it. The problem with resolve_address_of_overloaded_function that I
> > go into below (depending on if you agree that it's a problem) is what
> > needs to be fixed to allow the current diagnostic to be properly
> > emitted. As of right now, there is no execution path that I know of
> > where the diagnostic will be properly emitted.
> > 
> > I go into more detail about this in the comment in
> > tsubst_function_decl, although I do have to revise it a bit as I wrote
> > it before I had a discussion with another developer on the correct
> > behavior of the following case. I previously wondered if the overload
> > resolution from initializing p1 should result in a hard fault.
> > 
> > template<typename T>
> > struct S : T {
> > using T::operator();
> > void operator()(this int&, auto) {}
> > };
> > 
> > int main()
> > {
> > auto s0 = S{[](this auto&&, auto){}};
> > // error, ambiguous
> > void (*p0)(int&, int) = &decltype(s0)::operator();
> > 
> > auto s1 = S{[x = 42](this auto&&, auto){}};
> > // SFINAE's, calls S::operator()
> > void (*p1)(int&, int) = &decltype(s1)::operator();
> > }
> > 
> > The wording in [expr.prim.lambda.closure-5] is similar to that in
> > [dcl.fct-15], and we harness SFINAE in that case, so he concluded that
> > this case (initializing p1) should also harness SFINAE.
> 
> 
> Agreed.

Perfect, glad we are on the same page there.

> > > > The other problem I'm having is
> > > > 
> > > > auto f0 = [n = 5, &m](this auto const&){ n = 10; };
> > > > This errors just fine, the lambda is unconditionally const so
> > > > LAMBDA_EXPR_MUTABLE_P flag is set for the closure.
> > > > 
> > > > This on the other hand does not. The constness of the captures depends
> > > > on (I assume) LAMBDA_EXPR_MUTABLE_P so all the captures are non-const
> > > > here.
> > > > auto f1 = [n = 5](this auto&& self){ n = 10; };
> > > > as_const(f1)();
> > > 
> > > That sounds correct, why would this be an error?
> > > 
> > > The constness of the captures doesn't depend on LAMBDA_EXPR_MUTABLE_P,
> > > it depends on the type of the object parameter, which in this case is
> > > non-const, so so are the captures.
> > 
> > Oops, I should have just made a less terse example, you missed the
> > "as_const" in the call to the lambda. The object parameter should be
> > deduced as const here. I definitely should have made that example
> > better, my bad.
> 
> 
> Ah, yes, I see it now.

I don't remember if I relayed my planned fix for this to you. My
current idea is to modify the tree during instantiation of the lambda's
body somewhere near tsubst and apply const to all it's members. This is
unfortunately the best idea I have so far and it feels like an awful
hack. I am open to better ideas, but I don't think we can do anything
until the template is instantiated so I think it has to be there.

> > > > I was going to close out this message there but I just realized why
> > > > exactly you think erroring in instantiate_body is too late, it's
> > > > because at that point an error is an error, while if we error a little
> > > > bit earlier during substitution it's not a hard error. I'm glad I
> > > > realized this now, because I am much more confident in how to implement
> > > > the errors for unrelated type now. I'm still a little confused on what
> > > > the exact protocol for emitting errors at this stage is, as there
> > > > aren't many explicit errors written in. It seems to me like the errors
> > > > are supposed to be emitted when calling back into non-template stages
> > > > of the compiler with substituted types. It also seems like that hasn't
> > > > always been strictly followed, and I hate to contribute to code debt
> > > > but I'm not sure how I would do it in this case, nor if I actually have
> > > > a valid understanding of how this all works.
> > > 
> > > Most errors that could occur in template substitution are guarded by if
> > > (complain & tf_error) so that they aren't emitted during deduction
> > > substitution. It's better if those are in code that's shared with the
> > > non-template case, but sometimes that isn't feasible.
> > 
> > Yeah, makes sense, but the case going through
> > resolve_address_of_overloaded_function -> fn_type_unification ->
> > instantiate_template -> tsubst_decl -> tsubst_function_decl does not
> > behave super nicely. I tried adding (complain & tf_error) to the
> > explain_p parameter of fn_type_unification in
> > resolve_address_of_overloaded_function, but I immediately learned why
> > that wasn't being handled that way. I think
> > resolve_address_of_overloaded_function has a number of problems and
> > should probably use print_z_candidates instead of print_candidates in
> > the long term. This would actually solve the problem in
> > tsubst_function_decl where it never emits an error for an unrelated
> > type, print_z_candidates does call fn_type_unification with true passed
> > to explain_p. I go into this in detail in the large comment in
> > tsubst_function_decl (that I have to revise) so I won't waste time
> > rehashing it here.
> 
> 
> Makes sense, we won't see a message about the unrelated type because
> print_candidates doesn't repeat deduction like print_z_candidates does.
> 
> > Thank you for explaining though, some of the code doesn't reflect the
> > method you've relayed here (such as an unguarded error_at in
> > tsubst_function_decl) so I was starting to get confused on what exactly
> > is supposed to be going on. This clarifies things for me.
> 
> 
> That unguarded error does indeed look suspicious, though it could be
> that OMP reductions are called in a sufficiently unusual way that it
> isn't actually a problem in practice. /shrug
> 
> Jason

Fair enough.

Luckily, since I found that other test case, I have been able to
confirm that my diagnostic is properly reported when print_z_candidates
is called. So I feel like the diagnostic is implemented correctly.

Should I wait until I fix the issue in tsubst_lambda_expr before
submitting the patch? I'm fine to do it either way, just whatever you
prefer. If I finish cleaning up these tests before I hear back I'll go
ahead and submit it and then start looking at different solutions in
there.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v5 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-27  1:44                                                                                                         ` waffl3x
@ 2023-11-27  2:40                                                                                                           ` Jason Merrill
  2023-11-27  5:35                                                                                                           ` [PATCH v6 " waffl3x
  1 sibling, 0 replies; 100+ messages in thread
From: Jason Merrill @ 2023-11-27  2:40 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/26/23 20:44, waffl3x wrote:
>>>>> The other problem I'm having is
>>>>>
>>>>> auto f0 = [n = 5, &m](this auto const&){ n = 10; };
>>>>> This errors just fine, the lambda is unconditionally const so
>>>>> LAMBDA_EXPR_MUTABLE_P flag is set for the closure.
>>>>>
>>>>> This on the other hand does not. The constness of the captures depends
>>>>> on (I assume) LAMBDA_EXPR_MUTABLE_P so all the captures are non-const
>>>>> here.
>>>>> auto f1 = [n = 5](this auto&& self){ n = 10; };
>>>>> as_const(f1)();
>>>>
>>>> That sounds correct, why would this be an error?
>>>>
>>>> The constness of the captures doesn't depend on LAMBDA_EXPR_MUTABLE_P,
>>>> it depends on the type of the object parameter, which in this case is
>>>> non-const, so so are the captures.
>>>
>>> Oops, I should have just made a less terse example, you missed the
>>> "as_const" in the call to the lambda. The object parameter should be
>>> deduced as const here. I definitely should have made that example
>>> better, my bad.
>>
>> Ah, yes, I see it now.
> 
> I don't remember if I relayed my planned fix for this to you. My
> current idea is to modify the tree during instantiation of the lambda's
> body somewhere near tsubst and apply const to all it's members. This is
> unfortunately the best idea I have so far and it feels like an awful
> hack. I am open to better ideas, but I don't think we can do anything
> until the template is instantiated so I think it has to be there.

I think the answer should be in lambda_proxy_type.  The case where we 
build a DECLTYPE_TYPE may need to be expanded to cover this situation.

> Should I wait until I fix the issue in tsubst_lambda_expr before
> submitting the patch? I'm fine to do it either way, just whatever you
> prefer. If I finish cleaning up these tests before I hear back I'll go
> ahead and submit it and then start looking at different solutions in
> there.

Go ahead and submit.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-27  1:44                                                                                                         ` waffl3x
  2023-11-27  2:40                                                                                                           ` Jason Merrill
@ 2023-11-27  5:35                                                                                                           ` waffl3x
  2023-11-28  3:31                                                                                                             ` waffl3x
  2023-11-30  5:00                                                                                                             ` Jason Merrill
  1 sibling, 2 replies; 100+ messages in thread
From: waffl3x @ 2023-11-27  5:35 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 994 bytes --]

I think this is cleaned up enough to be presentable. Bootstrapped but
not tested but I don't think I changed anything substantial. I am
running tests right now and will report if anything fails. I have not
fixed the problem in tsubst_lambda_expr that we talked about, that will
be first on my list tomorrow. While writing/cleaning up tests I had to
investigate some things, one of which is calling an overloaded
function, where one of the candidates are introduced by a using
declaration, is considered ambiguous. I haven't narrowed down the case
for this yet so I don't know if it's related to xobj member
functions/lambda with xobj parameters or not. I had to pull a few tests
because of it though.

I did not get as much done as I would have hoped today. This really
just serves as a small progress update. Once again, todo is the issue
you raised in tsubst_lambda_expr, and fixing handling of captures when
a const xobj parameter is deduced in a lamdba call operator.

Alex

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-temp-deducing-this.patch --]
[-- Type: text/x-patch; name=0001-temp-deducing-this.patch, Size: 203929 bytes --]

From 2af9cf63ffc8a0b6a65614227cf1ccaf3d226574 Mon Sep 17 00:00:00 2001
From: waffl3x <waffl3x@protonmail.com>
Date: Sun, 26 Nov 2023 22:16:20 -0700
Subject: [PATCH] Temporary message, non-final patch. gcc/cp/ChangeLog:

	* call.cc (add_candidates):
	(build_over_call):
	* class.cc (add_method):
	(resolve_address_of_overloaded_function):
	* cp-tree.h (struct lang_decl_fn):
	(DECL_IOBJ_MEMBER_FUNC_P):
	(DECL_FUNCTION_XOBJ_FLAG):
	(DECL_XOBJ_MEMBER_FUNC_P):
	(DECL_OBJECT_MEMBER_FUNC_P):
	(DECL_FUNCTION_MEMBER_P):
	(enum auto_deduction_context):
	(TFF_XOBJ_FUNC):
	(enum cp_decl_spec):
	* decl.cc (grokfndecl):
	(grokdeclarator):
	(grok_op_properties):
	(start_preparsed_function):
	* error.cc (dump_function_decl):
	(dump_parameters):
	(function_category):
	* lambda.cc (build_capture_proxy):
	* module.cc (trees_out::lang_decl_bools):
	(trees_in::lang_decl_bools):
	* parser.cc (cp_parser_lambda_declarator_opt):
	(cp_parser_decl_specifier_seq):
	(cp_parser_parameter_declaration):
	(set_and_check_decl_spec_loc):
	* pt.cc (tsubst_function_decl):
	(tsubst_lambda_expr):
	* search.cc (look_for_overrides_here):
	(look_for_overrides_r):
	* semantics.cc (finish_this_expr):
	* tree.cc (build_min_non_dep_op_overload):
	* typeck.cc (cp_build_addr_expr_1):

gcc/testsuite/ChangeLog:

	* g++.dg/cpp23/explicit-obj-basic1.C: New test.
	* g++.dg/cpp23/explicit-obj-basic2.C: New test.
	* g++.dg/cpp23/explicit-obj-basic3.C: New test.
	* g++.dg/cpp23/explicit-obj-basic4.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-cxx-dialect-A.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-B.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-C.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-D.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-E.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics1.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics2.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics4.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics5.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics6.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics7.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda1.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda2.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda3.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX0.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX20.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX21.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX25.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX4.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX40.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX5.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX6.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX7.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.
	* g++.dg/cpp23/explicit-obj-redecl.C: New test.
	* g++.dg/cpp23/explicit-obj-redecl2.C: New test.
	* g++.dg/cpp23/explicit-obj-virtual.C: New test.

Signed-off-by: waffl3x <waffl3x@protonmail.com>
---
 gcc/cp/call.cc                                | 148 +++---
 gcc/cp/class.cc                               | 205 +++++++-
 gcc/cp/cp-tree.h                              |  39 +-
 gcc/cp/decl.cc                                | 180 ++++++-
 gcc/cp/error.cc                               |   8 +-
 gcc/cp/lambda.cc                              |   4 +-
 gcc/cp/module.cc                              |   2 +
 gcc/cp/parser.cc                              | 161 +++++-
 gcc/cp/pt.cc                                  |  73 ++-
 gcc/cp/search.cc                              |  16 +-
 gcc/cp/semantics.cc                           |  27 +-
 gcc/cp/tree.cc                                |  25 +-
 gcc/cp/typeck.cc                              |  29 +
 .../g++.dg/cpp23/explicit-obj-basic1.C        | 114 ++++
 .../g++.dg/cpp23/explicit-obj-basic2.C        |  28 +
 .../g++.dg/cpp23/explicit-obj-basic3.C        | 496 ++++++++++++++++++
 .../g++.dg/cpp23/explicit-obj-basic4.C        | 113 ++++
 .../g++.dg/cpp23/explicit-obj-by-value1.C     |  48 ++
 .../g++.dg/cpp23/explicit-obj-by-value2.C     |  58 ++
 .../g++.dg/cpp23/explicit-obj-by-value3.C     |  41 ++
 .../g++.dg/cpp23/explicit-obj-by-value4.C     |  20 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-A.C |   7 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-B.C |   7 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-C.C |   9 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-D.C |   8 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-E.C |   8 +
 .../g++.dg/cpp23/explicit-obj-diagnostics1.C  | 139 +++++
 .../g++.dg/cpp23/explicit-obj-diagnostics2.C  |  26 +
 .../g++.dg/cpp23/explicit-obj-diagnostics4.C  |  20 +
 .../g++.dg/cpp23/explicit-obj-diagnostics5.C  |  16 +
 .../g++.dg/cpp23/explicit-obj-diagnostics6.C  |  23 +
 .../g++.dg/cpp23/explicit-obj-diagnostics7.C  |  17 +
 .../g++.dg/cpp23/explicit-obj-lambda1.C       |  25 +
 .../g++.dg/cpp23/explicit-obj-lambda2.C       |  23 +
 .../g++.dg/cpp23/explicit-obj-lambda3.C       |  64 +++
 .../g++.dg/cpp23/explicit-obj-lambdaX0.C      |  30 ++
 .../g++.dg/cpp23/explicit-obj-lambdaX20.C     |  46 ++
 .../g++.dg/cpp23/explicit-obj-lambdaX21.C     |  39 ++
 .../g++.dg/cpp23/explicit-obj-lambdaX25.C     | 102 ++++
 .../g++.dg/cpp23/explicit-obj-lambdaX4.C      |  23 +
 .../g++.dg/cpp23/explicit-obj-lambdaX40.C     |  18 +
 .../g++.dg/cpp23/explicit-obj-lambdaX5.C      |  21 +
 .../g++.dg/cpp23/explicit-obj-lambdaX6.C      |  48 ++
 .../g++.dg/cpp23/explicit-obj-lambdaX7.C      |  87 +++
 .../g++.dg/cpp23/explicit-obj-ops-mem-arrow.C |  28 +
 .../cpp23/explicit-obj-ops-mem-assignment.C   |  27 +
 .../g++.dg/cpp23/explicit-obj-ops-mem-call.C  |  40 ++
 .../cpp23/explicit-obj-ops-mem-subscript.C    |  40 ++
 .../cpp23/explicit-obj-ops-non-mem-dep.C      |  58 ++
 .../cpp23/explicit-obj-ops-non-mem-non-dep.C  |  57 ++
 .../g++.dg/cpp23/explicit-obj-ops-non-mem.h   | 210 ++++++++
 .../cpp23/explicit-obj-ops-requires-mem.C     | 171 ++++++
 .../cpp23/explicit-obj-ops-requires-non-mem.C | 237 +++++++++
 .../g++.dg/cpp23/explicit-obj-redecl.C        | 246 +++++++++
 .../g++.dg/cpp23/explicit-obj-redecl2.C       | 161 ++++++
 .../g++.dg/cpp23/explicit-obj-virtual.C       |  95 ++++
 56 files changed, 3867 insertions(+), 144 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-basic3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.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-cxx-dialect-A.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX0.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX20.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX21.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX25.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX40.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX5.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX7.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
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-virtual.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 2eb54b5b6ed..a900c969182 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -6527,7 +6527,7 @@ 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))
+      if (DECL_OBJECT_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
@@ -9728,14 +9728,9 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
   const vec<tree, va_gc> *args = cand->args;
   tree first_arg = cand->first_arg;
   conversion **convs = cand->convs;
-  conversion *conv;
   tree parm = TYPE_ARG_TYPES (TREE_TYPE (fn));
   int parmlen;
   tree val;
-  int i = 0;
-  int j = 0;
-  unsigned int arg_index = 0;
-  int is_method = 0;
   int nargs;
   tree *argarray;
   bool already_used = false;
@@ -9921,45 +9916,46 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
   if (immediate_invocation_p (STRIP_TEMPLATE (fn)))
     in_consteval_if_p = true;
 
+  int argarray_size = 0;
+  unsigned int arg_index = 0;
+  int conv_index = 0;
+  int param_index = 0;
+
+  auto consume_object_arg = [&arg_index, &first_arg, args]()
+    {
+      if (!first_arg)
+	return (*args)[arg_index++];
+      tree object_arg = first_arg;
+      first_arg = NULL_TREE;
+      return object_arg;
+    };
+
   /* The implicit parameters to a constructor are not considered by overload
      resolution, and must be of the proper type.  */
   if (DECL_CONSTRUCTOR_P (fn))
     {
-      tree object_arg;
-      if (first_arg != NULL_TREE)
-	{
-	  object_arg = first_arg;
-	  first_arg = NULL_TREE;
-	}
-      else
-	{
-	  object_arg = (*args)[arg_index];
-	  ++arg_index;
-	}
-      argarray[j++] = build_this (object_arg);
+      tree object_arg = consume_object_arg ();
+      argarray[argarray_size++] = build_this (object_arg);
       parm = TREE_CHAIN (parm);
       /* We should never try to call the abstract constructor.  */
       gcc_assert (!DECL_HAS_IN_CHARGE_PARM_P (fn));
 
       if (DECL_HAS_VTT_PARM_P (fn))
 	{
-	  argarray[j++] = (*args)[arg_index];
+	  argarray[argarray_size++] = (*args)[arg_index];
 	  ++arg_index;
 	  parm = TREE_CHAIN (parm);
 	}
     }
   /* Bypass access control for 'this' parameter.  */
-  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
+  else if (DECL_IOBJ_MEMBER_FUNC_P (fn))
     {
-      tree arg = build_this (first_arg != NULL_TREE
-			     ? first_arg
-			     : (*args)[arg_index]);
+      tree arg = build_this (consume_object_arg ());
       tree argtype = TREE_TYPE (arg);
 
       if (arg == error_mark_node)
 	return error_mark_node;
-
-      if (convs[i]->bad_p)
+      if (convs[conv_index++]->bad_p)
 	{
 	  if (complain & tf_error)
 	    {
@@ -10034,25 +10030,57 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
       tree converted_arg = build_base_path (PLUS_EXPR, arg,
 					    base_binfo, 1, complain);
 
-      argarray[j++] = converted_arg;
+      argarray[argarray_size++] = converted_arg;
       parm = TREE_CHAIN (parm);
-      if (first_arg != NULL_TREE)
-	first_arg = NULL_TREE;
+    }
+
+  auto handle_arg = [fn, flags, complain](tree type,
+  					  tree arg,
+					  int const param_index,
+					  conversion *conv,
+					  bool const conversion_warning)
+    {
+      /* Set user_conv_p on the argument conversions, so rvalue/base handling
+	 knows not to allow any more UDCs.  This needs to happen after we
+	 process cand->warnings.  */
+      if (flags & LOOKUP_NO_CONVERSION)
+	conv->user_conv_p = true;
+
+      tsubst_flags_t const arg_complain
+	= conversion_warning ? complain : complain & ~tf_warning;
+
+      if (arg_complain & tf_warning)
+	maybe_warn_pessimizing_move (arg, type, /*return_p*/false);
+
+      tree val = convert_like_with_context (conv, arg, fn,
+      					    param_index, arg_complain);
+      val = convert_for_arg_passing (type, val, arg_complain);
+      return val;
+    };
+
+  if (DECL_XOBJ_MEMBER_FUNC_P (fn))
+    {
+      gcc_assert (cand->num_convs > 0);
+      static constexpr bool conversion_warning = true;
+      tree object_arg = consume_object_arg ();
+      val = handle_arg(TREE_VALUE (parm),
+		       object_arg,
+		       param_index++,
+		       convs[conv_index++],
+		       conversion_warning);
+
+      if (val == error_mark_node)
+        return error_mark_node;
       else
-	++arg_index;
-      ++i;
-      is_method = 1;
+        argarray[argarray_size++] = val;
+      parm = TREE_CHAIN (parm);
     }
 
   gcc_assert (first_arg == NULL_TREE);
   for (; arg_index < vec_safe_length (args) && parm;
-       parm = TREE_CHAIN (parm), ++arg_index, ++i)
+       parm = TREE_CHAIN (parm), ++arg_index, ++param_index, ++conv_index)
     {
-      tree type = TREE_VALUE (parm);
-      tree arg = (*args)[arg_index];
-      bool conversion_warning = true;
-
-      conv = convs[i];
+      tree current_arg = (*args)[arg_index];
 
       /* If the argument is NULL and used to (implicitly) instantiate a
          template function (and bind one of the template arguments to
@@ -10074,47 +10102,35 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
              func(NULL);
            }
       */
-      if (null_node_p (arg)
-          && DECL_TEMPLATE_INFO (fn)
-          && cand->template_decl
-	  && !cand->explicit_targs)
-        conversion_warning = false;
-
-      /* Set user_conv_p on the argument conversions, so rvalue/base handling
-	 knows not to allow any more UDCs.  This needs to happen after we
-	 process cand->warnings.  */
-      if (flags & LOOKUP_NO_CONVERSION)
-	conv->user_conv_p = true;
-
-      tsubst_flags_t arg_complain = complain;
-      if (!conversion_warning)
-	arg_complain &= ~tf_warning;
-
-      if (arg_complain & tf_warning)
-	maybe_warn_pessimizing_move (arg, type, /*return_p*/false);
+      bool const conversion_warning = !(null_node_p (current_arg)
+					&& DECL_TEMPLATE_INFO (fn)
+					&& cand->template_decl
+					&& !cand->explicit_targs);
 
-      val = convert_like_with_context (conv, arg, fn, i - is_method,
-				       arg_complain);
-      val = convert_for_arg_passing (type, val, arg_complain);
+      val = handle_arg (TREE_VALUE (parm),
+			current_arg,
+			param_index,
+			convs[conv_index],
+			conversion_warning);
 
       if (val == error_mark_node)
         return error_mark_node;
       else
-        argarray[j++] = val;
+        argarray[argarray_size++] = val;
     }
 
   /* Default arguments */
-  for (; parm && parm != void_list_node; parm = TREE_CHAIN (parm), i++)
+  for (; parm && parm != void_list_node; parm = TREE_CHAIN (parm), param_index++)
     {
       if (TREE_VALUE (parm) == error_mark_node)
 	return error_mark_node;
       val = convert_default_arg (TREE_VALUE (parm),
 				 TREE_PURPOSE (parm),
-				 fn, i - is_method,
+				 fn, param_index,
 				 complain);
       if (val == error_mark_node)
         return error_mark_node;
-      argarray[j++] = val;
+      argarray[argarray_size++] = val;
     }
 
   /* Ellipsis */
@@ -10151,11 +10167,11 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
 	a = convert_arg_to_ellipsis (a, complain);
       if (a == error_mark_node)
 	return error_mark_node;
-      argarray[j++] = a;
+      argarray[argarray_size++] = a;
     }
 
-  gcc_assert (j <= nargs);
-  nargs = j;
+  gcc_assert (argarray_size <= nargs);
+  nargs = argarray_size;
   icip.reset ();
 
   /* Avoid performing argument transformation if warnings are disabled.
@@ -10171,7 +10187,7 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
     {
       tree *fargs = (!nargs ? argarray
 			    : (tree *) alloca (nargs * sizeof (tree)));
-      for (j = 0; j < nargs; j++)
+      for (int j = 0; j < nargs; j++)
 	{
 	  /* For -Wformat undo the implicit passing by hidden reference
 	     done by convert_arg_to_ellipsis.  */
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index 0d8b780ba2f..f921b8a51b1 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -1079,8 +1079,8 @@ add_method (tree type, tree method, bool via_using)
       /* Compare the quals on the 'this' parm.  Don't compare
 	 the whole types, as used functions are treated as
 	 coming from the using class in overload resolution.  */
-      if (! DECL_STATIC_FUNCTION_P (fn)
-	  && ! DECL_STATIC_FUNCTION_P (method)
+      if (DECL_IOBJ_MEMBER_FUNC_P (fn)
+	  && DECL_IOBJ_MEMBER_FUNC_P (method)
 	  /* Either both or neither need to be ref-qualified for
 	     differing quals to allow overloading.  */
 	  && (FUNCTION_REF_QUALIFIED (fn_type)
@@ -1089,6 +1089,164 @@ add_method (tree type, tree method, bool via_using)
 	      || type_memfn_rqual (fn_type) != type_memfn_rqual (method_type)))
 	  continue;
 
+      auto get_object_param = [](tree fn)
+	{
+	  gcc_assert (DECL_OBJECT_MEMBER_FUNC_P (fn));
+	  return TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (fn)));
+	};
+      auto reference_qual = [](tree ref)
+	{
+	  gcc_assert (TYPE_REF_P (ref));
+	  return TYPE_REF_IS_RVALUE (ref) ? REF_QUAL_RVALUE
+					  : REF_QUAL_LVALUE;
+	};
+
+      /* Handle special correspondence rules for xobj vs xobj and xobj vs iobj
+	 member function declarations.
+	 We don't worry about static member functions here.  */
+      if ((!DECL_XOBJ_MEMBER_FUNC_P (fn) && !DECL_XOBJ_MEMBER_FUNC_P (method))
+	   || DECL_STATIC_FUNCTION_P (fn) || DECL_STATIC_FUNCTION_P (method))
+        /* Early escape.  */;
+      else if (DECL_XOBJ_MEMBER_FUNC_P (fn)
+	       && DECL_XOBJ_MEMBER_FUNC_P (method))
+        {
+	  tree fn_param = get_object_param (fn);
+	  tree method_param = get_object_param (method);
+	  if (!same_type_p (fn_param, method_param))
+	    continue;
+	}
+      else if (DECL_XOBJ_MEMBER_FUNC_P (fn)
+	       || DECL_XOBJ_MEMBER_FUNC_P (method))
+        {
+	  tree xobj_fn = DECL_XOBJ_MEMBER_FUNC_P (fn) ? fn : method;
+	  /* A reference, pointer, or something else.  */
+	  tree xobj_param = get_object_param (xobj_fn);
+
+	  /* We know an iobj parameter must be a reference. If our xobj
+	     parameter is a pointer, we know this is not a redeclaration.
+	     This also catches array parameters, those are pointers too.  */
+	  if (TYPE_PTR_P (xobj_param))
+	    continue;
+
+	  /* An iobj member function's object parameter can't be an unrelated
+	     type, we know this can't be a redeclaration if the xobj member
+	     function's object parameter is an unrelated type.
+	     If the iobj member function was introduced with a using
+	     declaration, then the type of its object parameter is still
+	     that of the class we are currently adding a member function to,
+	     so this assumption holds true in that case as well.
+
+	     [over.match.funcs.general.4]
+	     For non-conversion functions that are implicit object member
+	     functions nominated by a using-declaration in a derived class,
+	     the function is considered to be a member of the derived class
+	     for the purpose of defining the type of the implicit object
+	     parameter.
+	     
+	     We don't get to bail yet out even if the xobj parameter is
+	     by-value as elaborated on below.  */
+	  if (DECL_CONTEXT (xobj_fn)
+	      != TYPE_MAIN_VARIANT (non_reference (xobj_param)))
+	    continue;
+
+	  /* From this point on, we know we are dealing with an xobj parameter
+	     that has an object parameter of the same type as the class it
+	     was declared in.
+	     We still don't know if we have a reference or by-value parameter
+	     yet though.  */
+
+	  tree iobj_fn = DECL_IOBJ_MEMBER_FUNC_P (fn) ? fn : method;
+	  tree iobj_fn_type = TREE_TYPE (iobj_fn);
+	  cp_ref_qualifier const iobj_ref_qual
+	    = type_memfn_rqual (iobj_fn_type);
+	  /* I am concerned about the other qualifier bits, for now I will mask
+	     them off.  */
+	  static constexpr cp_cv_quals cv_bits = TYPE_QUAL_VOLATILE
+					       | TYPE_QUAL_CONST;
+	  cp_cv_quals const iobj_cv_quals
+	    = type_memfn_quals (iobj_fn_type) & cv_bits;
+	  /* We need to ignore the ref qualifier of the xobj parameter if the
+	     iobj member function lacks a ref qualifier.
+
+	     [basic.scope.scope.3]
+	     Two non-static member functions have corresponding object
+	     parameters if:
+	     -- exactly one is an implicit object member function with no
+		ref-qualifier and the types of their object parameters
+		([dcl.fct]), after removing top-level references, are the
+		same, or
+	     -- their object parameters have the same type.
+
+	     The cv qualifiers of a by-value parameter are supposed to be
+	     discarded, so we ignore them.
+
+	     [dcl.fct.5]
+	     After producing the list of parameter types, any top-level
+	     cv-qualifiers modifying a parameter type are deleted when
+	     forming the function type.
+
+	     They still need to be taken into account when our xobj parameter
+	     is a reference that is being ignored (according to
+	     [basic.scope.scope.3] quoted above), but when we are actually
+	     dealing with a by-value xobj parameter we can procede following
+	     this table.
+	     | iobj | xobj | equal |
+	     | none | none |   X   |
+	     | none |    c |   X   |
+	     | none |    v |   X   |
+	     | none |   cv |   X   |
+	     |    c | none |   O   |
+	     |    c |    c |   O   |
+	     |    c |    v |   O   |
+	     |    c |   cv |   O   |
+	     |    v | none |   O   |
+	     |    v |    c |   O   |
+	     |    v |    v |   O   |
+	     |    v |   cv |   O   |
+	     |   cv | none |   O   |
+	     |   cv |    c |   O   |
+	     |   cv |    v |   O   |
+	     |   cv |   cv |   O   |
+
+	     Additionally, if the iobj member function is ref qualified,
+	     we aren't ignoring the ref qualifier of the iobj parameter,
+	     so we can't be dealing with a redeclaration in that case either.
+
+	     So to recap, if we have a by-value xobj parameter, we know for
+	     sure that we aren't dealing with a redeclaration if the iobj
+	     member function has any cv-ref qualifiers. The only case where
+	     we might still be dealing with a redeclaration is when the iobj
+	     member function lacks any cv-ref qualification.  */
+	  if (!TYPE_REF_P (xobj_param))
+	    {
+	      if (iobj_ref_qual || iobj_cv_quals)
+		continue;
+	    }
+	  else
+	    {
+	      /* We are dealing with an xobj parameter that is a reference now,
+		 but due to [basic.scope.scope.3] we need to ignore its
+		 reference qualifier.  */
+	      cp_ref_qualifier const xobj_ref_qual
+		= !TYPE_REF_P (xobj_param) || !iobj_ref_qual
+		  ? REF_QUAL_NONE : reference_qual (xobj_param);
+
+	      /* Even if we are ignoring the reference qualifier, the xobj
+		 parameter was still a reference so we still take the cv
+		 qualifiers into account.  */
+	      cp_cv_quals const xobj_cv_quals
+		= cp_type_quals (TREE_TYPE (xobj_param)) & cv_bits;
+
+	      /* Finally, if the qualifications don't match exactly, this
+		 definitely isn't a redeclaration.  */
+	      if (iobj_ref_qual != xobj_ref_qual
+		  || iobj_cv_quals != xobj_cv_quals)
+		continue;
+	    }
+	}
+      else
+	gcc_unreachable ();
+
       tree real_fn = fn;
       tree real_method = method;
 
@@ -8724,21 +8882,42 @@ resolve_address_of_overloaded_function (tree target_type,
   /* Good, exactly one match.  Now, convert it to the correct type.  */
   fn = TREE_PURPOSE (matches);
 
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
-      && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
+  if (DECL_OBJECT_MEMBER_FUNC_P (fn)
+      && !(complain & tf_ptrmem_ok))
     {
-      static int explained;
-
-      if (!(complain & tf_error))
+      /* For iobj member functions, if if -fms_extensions was passed in, this
+	 is not an error, so we do nothing. It is still an error regardless for
+	 xobj member functions though, as it is a new feature we (hopefully)
+	 don't need to support the behavior.  */
+      if (DECL_IOBJ_MEMBER_FUNC_P (fn) && flag_ms_extensions)
+	/* Early escape.  */;
+      else if (!(complain & tf_error))
 	return error_mark_node;
-
-      auto_diagnostic_group d;
-      if (permerror (input_location, "assuming pointer to member %qD", fn)
-	  && !explained)
+      else if (DECL_XOBJ_MEMBER_FUNC_P (fn))
 	{
-	  inform (input_location, "(a pointer to member can only be "
+	  auto_diagnostic_group d;
+	  /* Error message could be better, fixit would be ideal.
+	     Should also match the error in typeck.cc:cp_build_addr_expr_1.  */
+	  error_at (input_location,
+		    "taking the address of an explicit object member "
+		    "function must be qualified");
+	  inform (input_location,
+		  "(a pointer to explicit object member function can only be "
 		  "formed with %<&%E%>)", fn);
-	  explained = 1;
+	}
+      else
+	{
+	  static int explained;
+	  gcc_assert (DECL_IOBJ_MEMBER_FUNC_P (fn) && !flag_ms_extensions);
+
+	  auto_diagnostic_group d;
+	  if (permerror (input_location, "assuming pointer to member %qD", fn)
+	      && !explained)
+	    {
+	      inform (input_location, "(a pointer to member can only be "
+				      "formed with %<&%E%>)", fn);
+	      explained = 1;
+	    }
 	}
     }
 
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 98b29e9cf81..e6cfc608100 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,34 @@ 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, use DECL_IOBJ_MEMBER_FUNC_P instead.  */
 #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
   (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
 
+/* Nonzero for FUNCTION_DECL means that this decl is an implicit object
+   member function.  */
+#define DECL_IOBJ_MEMBER_FUNC_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 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 if NODE is a member function with an object argument,
+   in other words, a non-static member function.  */
+#define DECL_OBJECT_MEMBER_FUNC_P(NODE) \
+  (DECL_IOBJ_MEMBER_FUNC_P (NODE) || DECL_XOBJ_MEMBER_FUNC_P (NODE))
+
 /* 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_OBJECT_MEMBER_FUNC_P (NODE) || DECL_STATIC_FUNCTION_P (NODE)) \
 
 /* Nonzero for FUNCTION_DECL means that this member function
    has `this' as const X *const.  */
@@ -6104,7 +6125,9 @@ enum auto_deduction_context
        identical to their defaults.
    TFF_NO_TEMPLATE_BINDINGS: do not print information about the template
        arguments for a function template specialization.
-   TFF_POINTER: we are printing a pointer type.  */
+   TFF_POINTER: we are printing a pointer type.
+   TFF_XOBJ_FUNC: we are printing an explicit object member function's
+       parameters.  */
 
 #define TFF_PLAIN_IDENTIFIER			(0)
 #define TFF_SCOPE				(1)
@@ -6122,6 +6145,7 @@ enum auto_deduction_context
 #define TFF_NO_OMIT_DEFAULT_TEMPLATE_ARGUMENTS	(1 << 12)
 #define TFF_NO_TEMPLATE_BINDINGS		(1 << 13)
 #define TFF_POINTER		                (1 << 14)
+#define TFF_XOBJ_FUNC				(1 << 15)
 
 /* These constants can be used as bit flags to control strip_typedefs.
 
@@ -6264,11 +6288,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 +6311,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..c13396a103d 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;
@@ -10610,24 +10607,30 @@ grokfndecl (tree ctype,
 	TREE_TYPE (decl) = apply_memfn_quals (TREE_TYPE (decl),
 					      TYPE_UNQUALIFIED,
 					      REF_QUAL_NONE);
-
+      auto_diagnostic_group d;
       if (quals)
-	{
-	  error (ctype
+	error (!ctype
+	       ? G_("non-member function %qD cannot have cv-qualifier")
+	       : !xobj_func_p
 		 ? G_("static member function %qD cannot have cv-qualifier")
-		 : G_("non-member function %qD cannot have cv-qualifier"),
-		 decl);
-	  quals = TYPE_UNQUALIFIED;
-	}
-
+		 : G_("explicit object member function "
+		      "%qD cannot have cv-qualifier"),
+	       decl);
       if (rqual)
-	{
-	  error (ctype
+	error (!ctype
+	       ? G_("non-member function %qD cannot have ref-qualifier")
+	       : !xobj_func_p
 		 ? G_("static member function %qD cannot have ref-qualifier")
-		 : G_("non-member function %qD cannot have ref-qualifier"),
+		 : G_("explicit object member function "
+		      "%qD cannot have ref-qualifier"),
 		 decl);
-	  rqual = REF_QUAL_NONE;
-	}
+
+      if (xobj_func_p && (quals || rqual))
+	inform (DECL_SOURCE_LOCATION (DECL_ARGUMENTS (decl)),
+		"explicit object parameter declared here");
+      quals = TYPE_UNQUALIFIED;
+      rqual = REF_QUAL_NONE;
+
     }
 
   if (deduction_guide_p (decl))
@@ -12998,6 +13001,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 +13118,91 @@ 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 || TREE_PURPOSE (parm_list) != this_identifier)
+		  return NULL_TREE;
+		/* 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 TREE_VALUE (parm_list);
+	      };
+
+	    tree xobj_parm
+	      = find_xobj_parm (declarator->u.function.parameters);
+	    is_xobj_member_function = xobj_parm;
+
+	    if (xobj_parm && cxx_dialect < cxx23)
+	      pedwarn (DECL_SOURCE_LOCATION (xobj_parm), OPT_Wc__23_extensions,
+		       "explicit object member function only available "
+		       "with %<-std=c++23%> or %<-std=gnu++23%>");
+
+	    if (xobj_parm && decl_context == TYPENAME)
+	      {
+		/* We inform in every case, just differently depending on what
+		   case it is.  */
+		auto_diagnostic_group d;
+		bool ptr_type = true;
+		/* If declarator->kind is cdk_function and we are at the end of
+		   the declarator chain, we are looking at a function type.  */
+		if (!declarator->declarator)
+		  {
+		    error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			      "a function type cannot "
+			      "have an explicit object parameter");
+		    ptr_type = false;
+		  }
+		else if (declarator->declarator->kind == cdk_pointer)
+		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			    "a pointer to function type cannot "
+			    "have an explicit object parameter");
+		else if (declarator->declarator->kind == cdk_ptrmem)
+		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			    "a pointer to member function type "
+			    "cannot have an explicit object parameter");
+		else
+		  gcc_unreachable ();
+		
+		/* The locations being used here are probably not correct.  */
+		if (ptr_type)
+		  inform (DECL_SOURCE_LOCATION (xobj_parm),
+			  "the type of a pointer to explicit object member "
+			  "function is a regular pointer to function type");
+		else
+		  inform (DECL_SOURCE_LOCATION (xobj_parm),
+			  "the type of an explicit object "
+			  "member function is a regular function type");
+		/* Ideally we should synthesize the correct syntax
+		   for the user, perhaps this could be added later.  */
+	      }
+	    /* Since a valid xobj parm has its purpose cleared in find_xobj_parm
+	       the first parm node will never erroneously be detected here.  */
+	    {
+	      auto_diagnostic_group d;
+	      bool bad_xobj_parm_encountered = false;
+	      for (tree parm = declarator->u.function.parameters;
+		   parm && parm != void_list_node;
+		   parm = TREE_CHAIN (parm))
+		{
+		  if (TREE_PURPOSE (parm) != this_identifier)
+		    continue;
+		  bad_xobj_parm_encountered = true;
+		  gcc_rich_location bad_xobj_parm
+		    (DECL_SOURCE_LOCATION (TREE_VALUE (parm)));
+		  /* I'm keeping it more basic for now.  */
+		  error_at (&bad_xobj_parm,
+			  "Only the first parameter of a member function "
+			  "can be declared as an explicit object parameter");
+		}
+	      if (bad_xobj_parm_encountered && xobj_parm)
+		inform (DECL_SOURCE_LOCATION (xobj_parm),
+			"Valid explicit object parameter declared here");
+	    }
+
 	    if (reqs)
 	      error_at (location_of (reqs), "requires-clause on return type");
 	    reqs = declarator->u.function.requires_clause;
@@ -13400,6 +13490,38 @@ grokdeclarator (const cp_declarator *declarator,
 		  explicitp = 2;
 	      }
 
+	    if (xobj_parm)
+	      {
+		if (!ctype
+		    && decl_context == NORMAL
+		    && (in_namespace
+			|| !declarator->declarator->u.id.qualifying_scope))
+		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			    "a non-member function cannot have "
+			    "an explicit object parameter");
+		else
+		  {
+		    if (virtualp)
+		      {
+			auto_diagnostic_group d;
+			error_at (declspecs->locations[ds_virtual],
+				  "an explicit object member function cannot be "
+				  "%<virtual%>");
+			inform (DECL_SOURCE_LOCATION (xobj_parm),
+				"explicit object parameter declared here");
+			virtualp = false;
+		      }
+		    if (staticp >= 2)
+		      {
+			auto_diagnostic_group d;
+			error_at (declspecs->locations[ds_storage_class],
+				  "an explicit object member function cannot be "
+				  "%<static%>");
+			inform (DECL_SOURCE_LOCATION (xobj_parm),
+				"explicit object parameter declared here");
+		      }
+		  }
+	      }
 	    tree pushed_scope = NULL_TREE;
 	    if (funcdecl_p
 		&& decl_context != FIELD
@@ -14177,6 +14299,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 +14522,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 +14858,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);
@@ -15624,11 +15749,12 @@ grok_op_properties (tree decl, bool complain)
 
       return true;
     }
-
   /* 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))
+  /* This can just be DECL_STATIC_FUNCTION_P (decl) I think?  */
+  if ((!methodp && !DECL_XOBJ_MEMBER_FUNC_P (decl))
+      || DECL_STATIC_FUNCTION_P (decl))
     {
       if (operator_code == TYPE_EXPR
 	  || operator_code == COMPONENT_REF
@@ -17382,6 +17508,8 @@ start_preparsed_function (tree decl1, tree attrs, int flags)
   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);
   else
     {
       ctype = DECL_FRIEND_CONTEXT (decl1);
@@ -17609,7 +17737,7 @@ start_preparsed_function (tree decl1, tree attrs, int flags)
   /* Start the statement-tree, start the tree now.  */
   DECL_SAVED_TREE (decl1) = push_stmt_list ();
 
-  if (ctype && !doing_friend && !DECL_STATIC_FUNCTION_P (decl1))
+  if (ctype && !doing_friend && DECL_IOBJ_MEMBER_FUNC_P (decl1))
     {
       /* We know that this was set up by `grokclassfn'.  We do not
 	 wait until `store_parm_decls', since evil parse errors may
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 0ed69bca6fc..c9870d45706 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -1831,7 +1831,9 @@ dump_function_decl (cxx_pretty_printer *pp, tree t, int flags)
 
   if (!(flags & TFF_NO_FUNCTION_ARGUMENTS))
     {
-      dump_parameters (pp, parmtypes, flags);
+      dump_parameters (pp, parmtypes,
+		       DECL_XOBJ_MEMBER_FUNC_P (t) ? TFF_XOBJ_FUNC | flags
+						    : flags);
 
       if (TREE_CODE (fntype) == METHOD_TYPE)
 	{
@@ -1910,6 +1912,8 @@ dump_parameters (cxx_pretty_printer *pp, tree parmtypes, int flags)
   for (first = 1; parmtypes != void_list_node;
        parmtypes = TREE_CHAIN (parmtypes))
     {
+      if (first && flags & TFF_XOBJ_FUNC)
+	pp_string (pp, "this ");
       if (!first)
 	pp_separate_with_comma (pp);
       first = 0;
@@ -3685,6 +3689,8 @@ function_category (tree fn)
 	return _("In destructor %qD");
       else if (LAMBDA_FUNCTION_P (fn))
 	return _("In lambda function");
+      else if (DECL_XOBJ_MEMBER_FUNC_P (fn))
+	return _("In explicit object member function %qD");
       else
 	return _("In member function %qD");
     }
diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
index a359bc6ee8d..ae2601c7b54 100644
--- a/gcc/cp/lambda.cc
+++ b/gcc/cp/lambda.cc
@@ -405,8 +405,10 @@ build_capture_proxy (tree member, tree init)
   fn = lambda_function (closure);
   lam = CLASSTYPE_LAMBDA_EXPR (closure);
 
+  object = DECL_ARGUMENTS (fn);
   /* The proxy variable forwards to the capture field.  */
-  object = build_fold_indirect_ref (DECL_ARGUMENTS (fn));
+  if (INDIRECT_TYPE_P (TREE_TYPE (object)))
+    object = build_fold_indirect_ref (object);
   object = finish_non_static_data_member (member, object, NULL_TREE);
   if (REFERENCE_REF_P (object))
     object = TREE_OPERAND (object, 0);
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index c1c8c226bc1..8723f23c4ae 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5683,6 +5683,7 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      WB (lang->u.fn.xobj_func);
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5751,6 +5752,7 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      RB (lang->u.fn.xobj_func);
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 20e18365906..6deceedc193 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -11750,6 +11750,35 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
     }
   else if (cxx_dialect < cxx23)
     omitted_parms_loc = cp_lexer_peek_token (parser->lexer)->location;
+  /* We just need to peek here,
+     grokdeclarator does the rest with this so don't mutate it.  */
+  tree const xobj_param
+    = param_list && TREE_PURPOSE (param_list) == this_identifier
+      ? TREE_VALUE (param_list) : NULL_TREE;
+
+  if (xobj_param
+      && (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE
+	  || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr)))
+    {
+      /* Since a lambda's type is anonymous, we can assume an explicit object
+	 parameter is unrelated... at least I believe so. I can think of a
+	 paradoxical case that almost works, where a class is forward declared
+	 and then defined deriving from a lambda that has an explicit object
+	 parameter of that class type, but the only way that can work is with
+	 decltype... okay yeah I think it can work for a struct defined in
+	 block scope, but I'm leaving this as is until I can confirm my
+	 hypothesis.  */
+
+      /* Since a lambda's type is anonymous, we can assume an xobj parameter
+	 is unrelated if it is non-dependent.  */
+      if (!dependent_type_p (non_reference (TREE_TYPE (xobj_param))))
+	{
+          error_at (DECL_SOURCE_LOCATION (xobj_param),
+		    "a lambda with captures may not have an explicit object "
+		    "parameter of an unrelated type");
+	  LAMBDA_EXPR_CAPTURE_LIST (lambda_expr) = NULL_TREE;
+	}
+    }
 
   /* In the decl-specifier-seq of the lambda-declarator, each
      decl-specifier shall either be mutable or constexpr.  */
@@ -11767,24 +11796,79 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
 	       "%<-std=gnu++2b%>");
       omitted_parms_loc = UNKNOWN_LOCATION;
     }
-
   if (lambda_specs.storage_class == sc_mutable)
     {
-      LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
-      quals = TYPE_UNQUALIFIED;
+      /* This might not be the most appropriate place for this, but I'm going
+	 to hold back on changing it for the time being since we are short on
+	 time. It's not really a parser error, it's more a semantic error.
+	 But this is where the other errors were so...  */
+      if (xobj_param)
+	{
+	  auto_diagnostic_group d;
+	  error_at (lambda_specs.locations[ds_storage_class],
+		    "%<mutable%> lambda specifier "
+		    "with explicit object parameter");
+	  /* Error reporting here is a little awkward, if the type of the
+	     object parameter is deduced, we should tell them the lambda
+	     is effectively already const, or to make the param const if it is
+	     not, but if it is deduced and taken by value shouldn't we say
+	     that it's taken by copy and won't mutate?
+	     Seems right to me, but it's a little strange.  */
+
+	  /* An xobj parameter with an unrelated type should already have been
+	     diagnosed, that means we definitely have a template type param.
+	     We don't suppress these informs right now when the xobj param is
+	     unrelated, we probably should though.  */
+	  if (!TYPE_REF_P (TREE_TYPE (xobj_param)))
+	    inform (DECL_SOURCE_LOCATION (xobj_param),
+		    "mutations of a by-value explicit object parameter "
+		    "will not persist across multiple calls");
+	  else if (TYPE_READONLY (TREE_TYPE (TREE_TYPE (xobj_param))))
+	    inform (DECL_SOURCE_LOCATION (xobj_param),
+		    "declare the explicit object parameter without const");
+	  else
+	    inform (DECL_SOURCE_LOCATION (xobj_param),
+		    "explicit object parameter is already mutable");
+	}
+      else
+	{
+	  LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
+	  quals = TYPE_UNQUALIFIED;
+	}
     }
   else if (lambda_specs.storage_class == sc_static)
     {
+      bool error_emitted = false;
       if (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE
 	  || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr))
-	error_at (lambda_specs.locations[ds_storage_class],
-		  "%<static%> lambda specifier with lambda capture");
-      else
+	{
+	  error_at (lambda_specs.locations[ds_storage_class],
+		    "%<static%> lambda specifier with lambda capture");
+	  error_emitted = true;
+	}
+      if (xobj_param)
+	{
+	  auto_diagnostic_group d;
+	  error_at (lambda_specs.locations[ds_storage_class],
+		    "%<static%> lambda specifier "
+		    "with explicit object parameter");
+	  inform (DECL_SOURCE_LOCATION (xobj_param),
+		  "explicit object parameter declared here");
+	  error_emitted = true;
+	}
+      if (!error_emitted)
 	{
 	  LAMBDA_EXPR_STATIC_P (lambda_expr) = 1;
 	  quals = TYPE_UNQUALIFIED;
 	}
     }
+  if (xobj_param)
+    {
+      quals = TYPE_UNQUALIFIED;
+      if (TYPE_REF_P (xobj_param)
+	  && !(cp_type_quals (TREE_TYPE (xobj_param)) & TYPE_QUAL_CONST))
+        LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
+    }
 
   tx_qual = cp_parser_tx_qualifier_opt (parser);
   if (omitted_parms_loc && tx_qual)
@@ -11899,7 +11983,8 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
       {
 	DECL_INITIALIZED_IN_CLASS_P (fco) = 1;
 	DECL_ARTIFICIAL (fco) = 1;
-	if (!LAMBDA_EXPR_STATIC_P (lambda_expr))
+	if (!LAMBDA_EXPR_STATIC_P (lambda_expr)
+	    && !DECL_XOBJ_MEMBER_FUNC_P (fco))
 	  /* Give the object parameter a different name.  */
 	  DECL_NAME (DECL_ARGUMENTS (fco)) = closure_identifier;
 	DECL_SET_LAMBDA_FUNCTION (fco, true);
@@ -16019,6 +16104,8 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
   /* Assume no class or enumeration type is declared.  */
   *declares_class_or_enum = 0;
 
+  /* Keep a token that additionally will be used for diagnostics.  */
+  cp_token *first_specifier = NULL;
   /* Keep reading specifiers until there are no more to read.  */
   while (true)
     {
@@ -16091,6 +16178,40 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
 	    decl_specs->locations[ds_attribute] = token->location;
 	  continue;
 	}
+      /* We know by this point that the token is not part of an attribute.  */
+      if (!first_specifier)
+	first_specifier = token;
+      /* 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);
+	  if (token != first_specifier)
+	    {
+	      /* Don't emit diagnostics if we have already seen "this",
+		 leave it for set_and_check_decl_spec_loc.  */
+	      if (decl_specs->locations[ds_this] == 0)
+		{
+		  auto_diagnostic_group d;
+		  gcc_rich_location richloc (token->location);
+		  /* Ideally we synthesize a full rewrite, at the moment
+		     there are issues with it though.
+		     It rewrites "f(S this & s)" correctly, but fails
+		     to rewrite "f(const this S s)" correctly. It also
+		     does not handle "f(S& this s)" correctly at all.  */
+		  richloc.add_fixit_remove ();
+		  richloc.add_fixit_insert_before (first_specifier->location,
+						   "this ");
+		  error_at (&richloc,
+			    "%<this%> must be the first specifier "
+			    "in a parameter declaration");
+		}
+	    }
+	  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
@@ -25421,12 +25542,14 @@ cp_parser_parameter_declaration (cp_parser *parser,
   /* The restriction on defining new types applies only to the type
      of the parameter, not to the default argument.  */
   parser->type_definition_forbidden_message = saved_message;
-
+  cp_token *eq_token = NULL;
   /* If the next token is `=', then process a default argument.  */
   if (cp_lexer_next_token_is (parser->lexer, CPP_EQ))
     {
       tree type = decl_specifiers.type;
       token = cp_lexer_peek_token (parser->lexer);
+      /* Used for diagnostics with an xobj parameter.  */
+      eq_token = token;
       if (declarator)
 	declarator->init_loc = token->location;
       /* If we are defining a class, then the tokens that make up the
@@ -25495,6 +25618,25 @@ 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))
+    {
+      if (default_argument)
+	{
+	  /* If there is a default_argument, eq_token should always be set.  */
+	  gcc_assert(eq_token);
+	  location_t param_with_init_loc
+	    = make_location (eq_token->location,
+			     decl_spec_token_start->location,
+			     input_location);
+	  error_at (param_with_init_loc,
+		    "an explicit object parameter "
+		    "may not have a default argument");
+	}
+      /* 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 +34015,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/pt.cc b/gcc/cp/pt.cc
index 86c95b278ba..7925d28f867 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -14407,14 +14407,20 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
       gen_tmpl = NULL_TREE;
       argvec = NULL_TREE;
     }
-
+  /* We hack TYPE_METHOD_BASETYPE onto xobj member functions in
+     tsubst_lambda_expr to get the proper closure type here.  */
   tree closure = (lambda_fntype ? TYPE_METHOD_BASETYPE (lambda_fntype)
 		  : NULL_TREE);
   tree ctx = closure ? closure : DECL_CONTEXT (t);
   bool member = ctx && TYPE_P (ctx);
 
   /* If this is a static lambda, remove the 'this' pointer added in
-     tsubst_lambda_expr now that we know the closure type.  */
+     tsubst_lambda_expr now that we know the closure type.
+     I suspect that we can just carry this information down in
+     TYPE_METHOD_BASETYPE without building the full method type in
+     tsubst_lambda_expr.  This wouldn't be ideal but neither is this.
+     Since that field isn't used for anything for static or xobj member
+     functions, it should be fine to do that.  */
   if (lambda_fntype && DECL_STATIC_FUNCTION_P (t))
     lambda_fntype = static_fn_type (lambda_fntype);
 
@@ -14490,12 +14496,12 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
     DECL_NAME (r) = make_conv_op_name (TREE_TYPE (type));
 
   tree parms = DECL_ARGUMENTS (t);
-  if (closure && !DECL_STATIC_FUNCTION_P (t))
+  if (closure && DECL_IOBJ_MEMBER_FUNC_P (t) && !DECL_STATIC_FUNCTION_P (t))
     parms = DECL_CHAIN (parms);
   parms = tsubst (parms, args, complain, t);
   for (tree parm = parms; parm; parm = DECL_CHAIN (parm))
     DECL_CONTEXT (parm) = r;
-  if (closure && !DECL_STATIC_FUNCTION_P (t))
+  if (closure && DECL_IOBJ_MEMBER_FUNC_P (t) && !DECL_STATIC_FUNCTION_P (t))
     {
       tree tparm = build_this_parm (r, closure, type_memfn_quals (type));
       DECL_NAME (tparm) = closure_identifier;
@@ -14530,6 +14536,52 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
       && IDENTIFIER_ANY_OP_P (DECL_NAME (r))
       && !grok_op_properties (r, /*complain=*/false))
     return error_mark_node;
+  /* We don't touch a lambda's func when it's just trying to create the
+     closure type.  */
+  if (!lambda_fntype && LAMBDA_FUNCTION_P (r) && DECL_XOBJ_MEMBER_FUNC_P (r))
+    {
+      tree lambda_expr = CLASSTYPE_LAMBDA_EXPR (DECL_CONTEXT (r));
+      if (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE
+	  || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr))
+	{
+	  gcc_assert (TREE_VEC_LENGTH (args) > 0);
+	  tree obj = TREE_VEC_ELT (args, 0);
+	  if (!same_or_base_type_p (DECL_CONTEXT (r),
+				    TYPE_MAIN_VARIANT (non_reference (obj))))
+	    {
+	      /* This error does not emit when the lambda's call operator
+		 template is instantiated by taking its address, such as in
+		 the following case:
+
+		 auto f = [x = 0](this auto&&){};
+		 int (*fp)(int&) = &decltype(f)::operator();
+
+		 It only emits when explicitly calling the call operator with
+		 an explicit template parameter:
+
+		 template<typename T>
+		 struct S : T {
+		   using T::operator();
+		   operator int() const {return {};}
+		 };
+
+		 auto s = S{[x = 0](this auto&&) {}};
+		 s.operator()<int>();
+
+		 This is due to resolve_address_of_overloaded_function being
+		 deficient at reporting candidates when overload resolution
+		 fails. 
+
+		 This diagnostic will be active in the first case if/when
+		 resolve_address_of_overloaded_function is fixed to properly
+		 emit candidates upon failure to resolve to an overload.  */
+	      if (complain & tf_error)
+		error ("a lambda with captures may not have an explicit "
+		       "object parameter of an unrelated type");
+	      return error_mark_node;
+	    }
+	}
+    }
 
   /* Associate the constraints directly with the instantiation. We
      don't substitute through the constraints; that's only done when
@@ -19491,9 +19543,16 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
       cp_evaluated ev;
 
       /* Fix the type of 'this'.  */
-      fntype = build_memfn_type (fntype, type,
-				 type_memfn_quals (fntype),
-				 type_memfn_rqual (fntype));
+      if (!DECL_XOBJ_MEMBER_FUNC_P (oldfn))
+	fntype = build_memfn_type (fntype, type,
+				   type_memfn_quals (fntype),
+				   type_memfn_rqual (fntype));
+      /* We don't use this field anywhere else for xobj member functions, and
+	 we need a way to pass this information down.  Theres some really
+	 convoluted stuff going on unfortunately, and I can only begin to
+	 unravel it all.   */
+      if (DECL_XOBJ_MEMBER_FUNC_P (oldfn))
+	TYPE_METHOD_BASETYPE (fntype) = type;
       tree inst = (oldtmpl
 		   ? tsubst_template_decl (oldtmpl, args, complain,
 					   fntype, tparms)
diff --git a/gcc/cp/search.cc b/gcc/cp/search.cc
index cd80f285ac9..08c22bcd5d8 100644
--- a/gcc/cp/search.cc
+++ b/gcc/cp/search.cc
@@ -2212,11 +2212,14 @@ look_for_overrides_here (tree type, tree fndecl)
 	/* Not a virtual.  */;
       else if (DECL_CONTEXT (fn) != type)
 	/* Introduced with a using declaration.  */;
-      else if (DECL_STATIC_FUNCTION_P (fndecl))
+      else if (DECL_STATIC_FUNCTION_P (fndecl)
+	       || DECL_XOBJ_MEMBER_FUNC_P (fndecl))
 	{
 	  tree btypes = TYPE_ARG_TYPES (TREE_TYPE (fn));
 	  tree dtypes = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
-	  if (compparms (TREE_CHAIN (btypes), dtypes))
+	  if (compparms (TREE_CHAIN (btypes),
+			 DECL_XOBJ_MEMBER_FUNC_P (fndecl)
+			   ? TREE_CHAIN (dtypes) : dtypes))
 	    return fn;
 	}
       else if (same_signature_p (fndecl, fn))
@@ -2243,6 +2246,15 @@ look_for_overrides_r (tree type, tree fndecl)
 	  error ("%q+#D cannot be declared", fndecl);
 	  error ("  since %q+#D declared in base class", fn);
 	}
+      else if (DECL_XOBJ_MEMBER_FUNC_P (fndecl))
+        {
+	  auto_diagnostic_group d;
+	  error_at (DECL_SOURCE_LOCATION (fndecl),
+		    "explicit object member function "
+		    "overrides virtual function");
+	  inform (DECL_SOURCE_LOCATION (fn),
+		  "virtual function declared here");
+	}
       else
 	{
 	  /* It's definitely virtual, even if not explicitly set.  */
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 52044be7af8..9f79d8cb9f2 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -45,6 +45,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gomp-constants.h"
 #include "predict.h"
 #include "memmodel.h"
+#include "gcc-rich-location.h"
 
 /* There routines provide a modular interface to perform many parsing
    operations.  They may therefore be used during actual parsing, or
@@ -3085,7 +3086,31 @@ finish_this_expr (void)
     return rvalue (result);
 
   tree fn = current_nonlambda_function ();
-  if (fn && DECL_STATIC_FUNCTION_P (fn))
+  if (fn && DECL_XOBJ_MEMBER_FUNC_P (fn))
+    {
+      auto_diagnostic_group d;
+      error ("%<this%> is unavailable for explicit object member "
+	     "functions");
+      /* Doing a fixit here is possible, but hard, might be worthwhile
+	 in the future.  */
+      tree xobj_parm = DECL_ARGUMENTS (fn);
+      gcc_assert (xobj_parm);
+      tree parm_name = DECL_NAME (xobj_parm);
+      if (parm_name)
+	inform (DECL_SOURCE_LOCATION (xobj_parm),
+		"use explicit object parameter %qD instead",
+		parm_name);
+      else
+	{
+          gcc_rich_location xobj_loc (DECL_SOURCE_LOCATION (xobj_parm));
+	  /* This doesn't work and I don't know why. I'll probably remove it
+	     before the final version.  */
+	  xobj_loc.add_fixit_insert_after(" self");
+	  inform (DECL_SOURCE_LOCATION (xobj_parm),
+		  "name and use the explicit object parameter instead");
+	}
+    }
+  else if (fn && DECL_STATIC_FUNCTION_P (fn))
     error ("%<this%> is unavailable for static member functions");
   else if (fn && processing_contract_condition && DECL_CONSTRUCTOR_P (fn))
     error ("invalid use of %<this%> before it is valid");
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index 417c92ba76f..d861593d9e4 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -3659,7 +3659,7 @@ build_min_non_dep_op_overload (enum tree_code op,
   nargs = call_expr_nargs (non_dep);
 
   expected_nargs = cp_tree_code_length (op);
-  if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE
+  if (DECL_OBJECT_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 +3677,25 @@ 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 (DECL_OBJECT_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 +3730,7 @@ 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 (DECL_OBJECT_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..a724d21a9c7 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -7133,6 +7133,24 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 
     case BASELINK:
       arg = BASELINK_FUNCTIONS (arg);
+      /* If we are passed a BASELINK, the operand of the address-of operator
+	 was not qualified, taking the address of a non-static member function
+	 must be qualified by the class.
+	 It's possible we shouldn't be making this baselink optimization for
+	 xobj member functions at all, we should evaluate that later.  */
+      if (DECL_XOBJ_MEMBER_FUNC_P (arg))
+	{
+	  /* Error should match that in
+	     class.cc:resolve_address_of_overloaded_function.
+	     We really should do a fixit here.  */
+	  error_at (input_location,
+		    "taking the address of an explicit object member "
+		    "function must be qualified");
+	  inform (input_location,
+		  "(a pointer to explicit object member function can only be "
+		  "formed with %<&%E%>)", arg);
+	  return error_mark_node;
+	}
       /* Fall through.  */
 
     case OVERLOAD:
@@ -7164,6 +7182,17 @@ 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;
 
+	/* Pull out the function_decl for a single xobj member function, and
+	   let the rest of this function handle it. This is handled the same
+	   way as the BASELINK case above, but we can't handle xobj member
+	   functions there as xobj member functions must be qualified, hence
+	   the error above.  */
+	if (DECL_XOBJ_MEMBER_FUNC_P (t))
+	  {
+	    arg = t;
+	    break;
+	  }
+
 	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..134182c7741
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic1.C
@@ -0,0 +1,114 @@
+// 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..6b2cad130fa
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic2.C
@@ -0,0 +1,28 @@
+// 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-basic3.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic3.C
new file mode 100644
index 00000000000..e466cc9761c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic3.C
@@ -0,0 +1,496 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// bogus diagnosis of valid declarations as redeclarations
+// tests for by-value are elsewhere (todo: add filename)
+
+// each group has 8 overloads that each take
+// lvalue ref to S
+// rvalue ref to S
+// lvalue c ref to S
+// rvalue c ref to S
+// lvalue v ref to S
+// rvalue v ref to S
+// lvalue cv ref to S
+// rvalue cv ref to S
+// where S is the struct the function is declared in
+
+// only xobj (the most basic case)
+
+struct S {
+  void f(this S &);
+  void f(this S &&);
+  void f(this S const&);
+  void f(this S const&&);
+  void f(this S volatile&);
+  void f(this S volatile&&);
+  void f(this S const volatile&);
+  void f(this S const volatile&&);
+};
+
+// I* has the 1 xobj 7 iobj cases
+// X* has the 7 xobj 1 iobj cases
+// *0 has the unique function first, the rest after
+// *1 has the unique function last, the rest after
+// *2 has the functions in the order stated above
+// xobj first, 1 xobj, 7 iobj
+
+// (yes there are some redundant cases)
+
+// unique first, 1 xobj 7 iobj
+
+struct I0 {
+  void f0(this I0&);
+  void f0() &&;
+  void f0() const&;
+  void f0() const&&;
+  void f0() volatile&;
+  void f0() volatile&&;
+  void f0() const volatile&;
+  void f0() const volatile&&;
+
+  void f1(this I0&&);
+  void f1() &;
+  void f1() const&;
+  void f1() const&&;
+  void f1() volatile&;
+  void f1() volatile&&;
+  void f1() const volatile&;
+  void f1() const volatile&&;
+
+  void fc0(this I0 const&);
+  void fc0() &;
+  void fc0() &&;
+  void fc0() const&&;
+  void fc0() volatile&;
+  void fc0() volatile&&;
+  void fc0() const volatile&;
+  void fc0() const volatile&&;
+
+  void fc1(this I0 const&&);
+  void fc1() &;
+  void fc1() &&;
+  void fc1() const&;
+  void fc1() volatile&;
+  void fc1() volatile&&;
+  void fc1() const volatile&;
+  void fc1() const volatile&&;
+
+  void fv0(this I0 volatile&);
+  void fv0() &;
+  void fv0() &&;
+  void fv0() const&;
+  void fv0() const&&;
+  void fv0() volatile&&;
+  void fv0() const volatile&;
+  void fv0() const volatile&&;
+
+  void fv1(this I0 volatile&&);
+  void fv1() &;
+  void fv1() &&;
+  void fv1() const&;
+  void fv1() const&&;
+  void fv1() volatile&;
+  void fv1() const volatile&;
+  void fv1() const volatile&&;
+
+  void fcv0(this I0 const volatile&);
+  void fcv0() &;
+  void fcv0() &&;
+  void fcv0() const&;
+  void fcv0() const&&;
+  void fcv0() volatile&;
+  void fcv0() volatile&&;
+  void fcv0() const volatile&&;
+
+  void fcv1(this I0 const volatile&&);
+  void fcv1() &;
+  void fcv1() &&;
+  void fcv1() const&;
+  void fcv1() const&&;
+  void fcv1() volatile&;
+  void fcv1() volatile&&;
+  void fcv1() const volatile&;
+};
+
+// unique last, 1 xobj 7 iobj
+
+struct I1 {
+  void f0() &&;
+  void f0() const&;
+  void f0() const&&;
+  void f0() volatile&;
+  void f0() volatile&&;
+  void f0() const volatile&;
+  void f0() const volatile&&;
+  void f0(this I1&);
+
+  void f1() &;
+  void f1() const&;
+  void f1() const&&;
+  void f1() volatile&;
+  void f1() volatile&&;
+  void f1() const volatile&;
+  void f1() const volatile&&;
+  void f1(this I1&&);
+
+  void fc0() &;
+  void fc0() &&;
+  void fc0() const&&;
+  void fc0() volatile&;
+  void fc0() volatile&&;
+  void fc0() const volatile&;
+  void fc0() const volatile&&;
+  void fc0(this I1 const&);
+
+  void fc1() &;
+  void fc1() &&;
+  void fc1() const&;
+  void fc1() volatile&;
+  void fc1() volatile&&;
+  void fc1() const volatile&;
+  void fc1() const volatile&&;
+  void fc1(this I1 const&&);
+
+  void fv0() &;
+  void fv0() &&;
+  void fv0() const&;
+  void fv0() const&&;
+  void fv0() volatile&&;
+  void fv0() const volatile&;
+  void fv0() const volatile&&;
+  void fv0(this I1 volatile&);
+
+  void fv1() &;
+  void fv1() &&;
+  void fv1() const&;
+  void fv1() const&&;
+  void fv1() volatile&;
+  void fv1() const volatile&;
+  void fv1() const volatile&&;
+  void fv1(this I1 volatile&&);
+
+  void fcv0() &;
+  void fcv0() &&;
+  void fcv0() const&;
+  void fcv0() const&&;
+  void fcv0() volatile&;
+  void fcv0() volatile&&;
+  void fcv0() const volatile&&;
+  void fcv0(this I1 const volatile&);
+
+  void fcv1() &;
+  void fcv1() &&;
+  void fcv1() const&;
+  void fcv1() const&&;
+  void fcv1() volatile&;
+  void fcv1() volatile&&;
+  void fcv1() const volatile&;
+  void fcv1(this I1 const volatile&&);
+};
+
+// ordered, 1 xobj 7 iobj
+
+struct I2 {
+  void f0(this I2&);
+  void f0() &&;
+  void f0() const&;
+  void f0() const&&;
+  void f0() volatile&;
+  void f0() volatile&&;
+  void f0() const volatile&;
+  void f0() const volatile&&;
+
+  void f1() &;
+  void f1(this I2&&);
+  void f1() const&;
+  void f1() const&&;
+  void f1() volatile&;
+  void f1() volatile&&;
+  void f1() const volatile&;
+  void f1() const volatile&&;
+
+  void fc0() &;
+  void fc0() &&;
+  void fc0(this I2 const&);
+  void fc0() const&&;
+  void fc0() volatile&;
+  void fc0() volatile&&;
+  void fc0() const volatile&;
+  void fc0() const volatile&&;
+
+  void fc1() &;
+  void fc1() &&;
+  void fc1() const&;
+  void fc1(this I2 const&&);
+  void fc1() volatile&;
+  void fc1() volatile&&;
+  void fc1() const volatile&;
+  void fc1() const volatile&&;
+
+  void fv0() &;
+  void fv0() &&;
+  void fv0() const&;
+  void fv0() const&&;
+  void fv0(this I2 volatile&);
+  void fv0() volatile&&;
+  void fv0() const volatile&;
+  void fv0() const volatile&&;
+
+  void fv1() &;
+  void fv1() &&;
+  void fv1() const&;
+  void fv1() const&&;
+  void fv1() volatile&;
+  void fv1(this I2 volatile&&);
+  void fv1() const volatile&;
+  void fv1() const volatile&&;
+
+  void fcv0() &;
+  void fcv0() &&;
+  void fcv0() const&;
+  void fcv0() const&&;
+  void fcv0() volatile&;
+  void fcv0() volatile&&;
+  void fcv0(this I2 const volatile&);
+  void fcv0() const volatile&&;
+
+  void fcv1() &;
+  void fcv1() &&;
+  void fcv1() const&;
+  void fcv1() const&&;
+  void fcv1() volatile&;
+  void fcv1() volatile&&;
+  void fcv1() const volatile&;
+  void fcv1(this I2 const volatile&&);
+};
+
+
+// iobj first, 7 xobj, 1 iobj
+
+struct X0 {
+  void f0() &;
+  void f0(this X0 &&);
+  void f0(this X0 const&);
+  void f0(this X0 const&&);
+  void f0(this X0 volatile&);
+  void f0(this X0 volatile&&);
+  void f0(this X0 const volatile&);
+  void f0(this X0 const volatile&&);
+
+  void f1() &&;
+  void f1(this X0 &);
+  void f1(this X0 const&);
+  void f1(this X0 const&&);
+  void f1(this X0 volatile&);
+  void f1(this X0 volatile&&);
+  void f1(this X0 const volatile&);
+  void f1(this X0 const volatile&&);
+
+  void fc0() const&;
+  void fc0(this X0 &);
+  void fc0(this X0 &&);
+  void fc0(this X0 const&&);
+  void fc0(this X0 volatile&);
+  void fc0(this X0 volatile&&);
+  void fc0(this X0 const volatile&);
+  void fc0(this X0 const volatile&&);
+
+  void fc1() const&&;
+  void fc1(this X0 &);
+  void fc1(this X0 &&);
+  void fc1(this X0 const&);
+  void fc1(this X0 volatile&);
+  void fc1(this X0 volatile&&);
+  void fc1(this X0 const volatile&);
+  void fc1(this X0 const volatile&&);
+
+  void fv0() volatile&;
+  void fv0(this X0 &);
+  void fv0(this X0 &&);
+  void fv0(this X0 const&);
+  void fv0(this X0 const&&);
+  void fv0(this X0 volatile&&);
+  void fv0(this X0 const volatile&);
+  void fv0(this X0 const volatile&&);
+
+  void fv1() volatile&&;
+  void fv1(this X0 &);
+  void fv1(this X0 &&);
+  void fv1(this X0 const&);
+  void fv1(this X0 const&&);
+  void fv1(this X0 volatile&);
+  void fv1(this X0 const volatile&);
+  void fv1(this X0 const volatile&&);
+
+  void fcv0() const volatile&;
+  void fcv0(this X0 &);
+  void fcv0(this X0 &&);
+  void fcv0(this X0 const&);
+  void fcv0(this X0 const&&);
+  void fcv0(this X0 volatile&);
+  void fcv0(this X0 volatile&&);
+  void fcv0(this X0 const volatile&&);
+
+  void fcv1() const volatile&&;
+  void fcv1(this X0 &);
+  void fcv1(this X0 &&);
+  void fcv1(this X0 const&);
+  void fcv1(this X0 const&&);
+  void fcv1(this X0 volatile&);
+  void fcv1(this X0 volatile&&);
+  void fcv1(this X0 const volatile&);
+};
+
+// iobj last, 7 xobj 1 iobj
+
+struct X1 {
+  void f0(this X1 &&);
+  void f0(this X1 const&);
+  void f0(this X1 const&&);
+  void f0(this X1 volatile&);
+  void f0(this X1 volatile&&);
+  void f0(this X1 const volatile&);
+  void f0(this X1 const volatile&&);
+  void f0() &;
+
+  void f1(this X1 &);
+  void f1(this X1 const&);
+  void f1(this X1 const&&);
+  void f1(this X1 volatile&);
+  void f1(this X1 volatile&&);
+  void f1(this X1 const volatile&);
+  void f1(this X1 const volatile&&);
+  void f1() &&;
+
+  void fc0(this X1 &);
+  void fc0(this X1 &&);
+  void fc0(this X1 const&&);
+  void fc0(this X1 volatile&);
+  void fc0(this X1 volatile&&);
+  void fc0(this X1 const volatile&);
+  void fc0(this X1 const volatile&&);
+  void fc0() const&;
+
+  void fc1(this X1 &);
+  void fc1(this X1 &&);
+  void fc1(this X1 const&);
+  void fc1(this X1 volatile&);
+  void fc1(this X1 volatile&&);
+  void fc1(this X1 const volatile&);
+  void fc1(this X1 const volatile&&);
+  void fc1() const&&;
+
+  void fv0(this X1 &);
+  void fv0(this X1 &&);
+  void fv0(this X1 const&);
+  void fv0(this X1 const&&);
+  void fv0(this X1 volatile&&);
+  void fv0(this X1 const volatile&);
+  void fv0(this X1 const volatile&&);
+  void fv0() volatile&;
+
+  void fv1(this X1 &);
+  void fv1(this X1 &&);
+  void fv1(this X1 const&);
+  void fv1(this X1 const&&);
+  void fv1(this X1 volatile&);
+  void fv1(this X1 const volatile&);
+  void fv1(this X1 const volatile&&);
+  void fv1() volatile&&;
+
+  void fcv0(this X1 &);
+  void fcv0(this X1 &&);
+  void fcv0(this X1 const&);
+  void fcv0(this X1 const&&);
+  void fcv0(this X1 volatile&);
+  void fcv0(this X1 volatile&&);
+  void fcv0(this X1 const volatile&&);
+  void fcv0() const volatile&;
+
+  void fcv1(this X1 &);
+  void fcv1(this X1 &&);
+  void fcv1(this X1 const&);
+  void fcv1(this X1 const&&);
+  void fcv1(this X1 volatile&);
+  void fcv1(this X1 volatile&&);
+  void fcv1(this X1 const volatile&);
+  void fcv1() const volatile&&;
+};
+
+// ordered, 7 xobj 1 iobj
+
+struct X2 {
+  void f0() &;
+  void f0(this X2 &&);
+  void f0(this X2 const&);
+  void f0(this X2 const&&);
+  void f0(this X2 volatile&);
+  void f0(this X2 volatile&&);
+  void f0(this X2 const volatile&);
+  void f0(this X2 const volatile&&);
+
+  void f1(this X2 &);
+  void f1() &&;
+  void f1(this X2 const&);
+  void f1(this X2 const&&);
+  void f1(this X2 volatile&);
+  void f1(this X2 volatile&&);
+  void f1(this X2 const volatile&);
+  void f1(this X2 const volatile&&);
+
+  void fc0(this X2 &);
+  void fc0(this X2 &&);
+  void fc0() const&;
+  void fc0(this X2 const&&);
+  void fc0(this X2 volatile&);
+  void fc0(this X2 volatile&&);
+  void fc0(this X2 const volatile&);
+  void fc0(this X2 const volatile&&);
+
+  void fc1(this X2 &);
+  void fc1(this X2 &&);
+  void fc1(this X2 const&);
+  void fc1() const&&;
+  void fc1(this X2 volatile&);
+  void fc1(this X2 volatile&&);
+  void fc1(this X2 const volatile&);
+  void fc1(this X2 const volatile&&);
+
+  void fv0(this X2 &);
+  void fv0(this X2 &&);
+  void fv0(this X2 const&);
+  void fv0(this X2 const&&);
+  void fv0() volatile&;
+  void fv0(this X2 volatile&&);
+  void fv0(this X2 const volatile&);
+  void fv0(this X2 const volatile&&);
+
+  void fv1(this X2 &);
+  void fv1(this X2 &&);
+  void fv1(this X2 const&);
+  void fv1(this X2 const&&);
+  void fv1(this X2 volatile&);
+  void fv1() volatile&&;
+  void fv1(this X2 const volatile&);
+  void fv1(this X2 const volatile&&);
+
+  void fcv0(this X2 &);
+  void fcv0(this X2 &&);
+  void fcv0(this X2 const&);
+  void fcv0(this X2 const&&);
+  void fcv0(this X2 volatile&);
+  void fcv0(this X2 volatile&&);
+  void fcv0() const volatile&;
+  void fcv0(this X2 const volatile&&);
+
+  void fcv1(this X2 &);
+  void fcv1(this X2 &&);
+  void fcv1(this X2 const&);
+  void fcv1(this X2 const&&);
+  void fcv1(this X2 volatile&);
+  void fcv1(this X2 volatile&&);
+  void fcv1(this X2 const volatile&);
+  void fcv1() const volatile&&;
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C
new file mode 100644
index 00000000000..691ef6804cf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C
@@ -0,0 +1,113 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// valid overloading of iobj member functions without ref qualifiers
+// with xobj member functions (and vice-versa)
+
+// this is the most you can mix these, it may look short but it does test
+// all allowed cases (other than by-value and unrelated types)
+
+// [over.match.funcs.general.4]
+// For implicit object member functions, the type of the implicit
+// object parameter is
+// -- “lvalue reference to cv X” for functions declared
+//    without a ref-qualifier or with the & ref-qualifier
+// -- “rvalue reference to cv X” for functions declared with
+//    the && ref-qualifier
+
+// [basic.scope.scope.3]
+// Two non-static member functions have corresponding object
+// parameters if:
+// -- exactly one is an implicit object member function with no
+//    ref-qualifier and the types of their object parameters
+//    ([dcl.fct]), after removing top-level references, are the
+//    same, or
+
+// in simpler terms, only the cv qualification of the explicit/implicit object
+// parameter matter for determining whether these are redeclarations or overloads
+// (when a ref qualifier is not present on the iobj member function)
+
+// xobj first, iobj last
+
+struct S0 {
+  void f(this S0 &);
+  void f(this S0 &&);
+  void f() const;
+  void f() volatile;
+  void f() const volatile;
+
+  void fc(this S0 const&);
+  void fc(this S0 const&&);
+  void fc();
+  void fc() volatile;
+  void fc() const volatile;
+
+  void fv(this S0 volatile&);
+  void fv(this S0 volatile&&);
+  void fv();
+  void fv() const;
+  void fv() const volatile;
+
+  void fcv(this S0 const volatile&);
+  void fcv(this S0 const volatile&&);
+  void fcv();
+  void fcv() const;
+  void fcv() volatile;
+};
+
+// iobj first, xobj last
+
+struct S1 {
+  void f() const;
+  void f() volatile;
+  void f() const volatile;
+  void f(this S1 &);
+  void f(this S1 &&);
+
+  void fc();
+  void fc() volatile;
+  void fc() const volatile;
+  void fc(this S1 const&);
+  void fc(this S1 const&&);
+
+  void fv();
+  void fv() const;
+  void fv() const volatile;
+  void fv(this S1 volatile&);
+  void fv(this S1 volatile&&);
+
+  void fcv();
+  void fcv() const;
+  void fcv() volatile;
+  void fcv(this S1 const volatile&);
+  void fcv(this S1 const volatile&&);
+};
+
+// in order
+
+struct S2 {
+  void f(this S2 &);
+  void f(this S2 &&);
+  void f() const;
+  void f() volatile;
+  void f() const volatile;
+
+  void fc();
+  void fc(this S2 const&);
+  void fc(this S2 const&&);
+  void fc() volatile;
+  void fc() const volatile;
+
+  void fv();
+  void fv() const;
+  void fv(this S2 volatile&);
+  void fv(this S2 volatile&&);
+  void fv() const volatile;
+
+  void fcv();
+  void fcv() const;
+  void fcv() volatile;
+  void fcv(this S2 const volatile&);
+  void fcv(this S2 const volatile&&);
+};
+
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..5ea5bcb7faa
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value1.C
@@ -0,0 +1,48 @@
+// 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.
+
+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..b8e8e73dfaf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value2.C
@@ -0,0 +1,58 @@
+// 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
+
+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..e6aff0190fb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value3.C
@@ -0,0 +1,41 @@
+// 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
+
+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..9b4e00582cf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value4.C
@@ -0,0 +1,20 @@
+// 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-cxx-dialect-A.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-A.C
new file mode 100644
index 00000000000..5043e91bb28
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-A.C
@@ -0,0 +1,7 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+struct S {
+    void f(this S); // { dg-bogus {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C
new file mode 100644
index 00000000000..fb2a6a0e41b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C
@@ -0,0 +1,7 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+
+struct S {
+    void f(this S); // { dg-error {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C
new file mode 100644
index 00000000000..182e294c883
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C
@@ -0,0 +1,9 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+// don't pass in -pedantic-errors
+// { dg-options "" }
+
+struct S {
+    void f(this S); // { dg-warning {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C
new file mode 100644
index 00000000000..49b7ea0df44
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C
@@ -0,0 +1,8 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+// { dg-options "-Wno-c++23-extensions" }
+
+struct S {
+    void f(this S); // { dg-bogus {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C
new file mode 100644
index 00000000000..411b70c3d9d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C
@@ -0,0 +1,8 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+// { dg-options "-Wno-c++23-extensions -pedantic-errors" }
+
+struct S {
+    void f(this S); // { dg-bogus {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C
new file mode 100644
index 00000000000..dfac1188fba
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C
@@ -0,0 +1,139 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of xobj member functions that have member function qualifiers.
+
+struct S {
+    void f_value_0(this S) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_value_1(this S) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_value_2(this S) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_value_3(this S) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_value_4(this S) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_value_5(this S) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_6(this S) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_7(this S) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_8(this S) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_9(this S) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_A(this S) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_ref_0(this S&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_ref_1(this S&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_ref_2(this S&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_ref_3(this S&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_ref_4(this S&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_ref_5(this S&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_6(this S&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_7(this S&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_8(this S&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_9(this S&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_A(this S&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_refref_0(this S&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_refref_1(this S&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_refref_2(this S&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_refref_3(this S&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_refref_4(this S&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_refref_5(this S&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_6(this S&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_7(this S&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_8(this S&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_9(this S&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_A(this S&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_cref_0(this S const&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cref_1(this S const&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cref_2(this S const&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cref_3(this S const&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cref_4(this S const&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cref_5(this S const&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_6(this S const&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_7(this S const&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_8(this S const&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_9(this S const&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_A(this S const&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_crefref_0(this S const&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_crefref_1(this S const&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_crefref_2(this S const&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_crefref_3(this S const&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_crefref_4(this S const&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_crefref_5(this S const&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_6(this S const&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_7(this S const&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_8(this S const&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_9(this S const&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_A(this S const&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_vref_0(this S volatile&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vref_1(this S volatile&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vref_2(this S volatile&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vref_3(this S volatile&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vref_4(this S volatile&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vref_5(this S volatile&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_6(this S volatile&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_7(this S volatile&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_8(this S volatile&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_9(this S volatile&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_A(this S volatile&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_vrefref_0(this S volatile&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vrefref_1(this S volatile&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vrefref_2(this S volatile&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vrefref_3(this S volatile&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vrefref_4(this S volatile&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vrefref_5(this S volatile&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_6(this S volatile&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_7(this S volatile&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_8(this S volatile&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_9(this S volatile&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_A(this S volatile&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_cvref_0(this S const volatile&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvref_1(this S const volatile&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvref_2(this S const volatile&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvref_3(this S const volatile&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvref_4(this S const volatile&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvref_5(this S const volatile&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_6(this S const volatile&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_7(this S const volatile&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_8(this S const volatile&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_9(this S const volatile&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_A(this S const volatile&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_cvrefref_0(this S const volatile&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvrefref_1(this S const volatile&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvrefref_2(this S const volatile&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvrefref_3(this S const volatile&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvrefref_4(this S const volatile&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvrefref_5(this S const volatile&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_6(this S const volatile&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_7(this S const volatile&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_8(this S const volatile&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_9(this S const volatile&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_A(this S const volatile&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    template<typename Self> void d_templ_0(this Self&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    template<typename Self> void d_templ_1(this Self&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    template<typename Self> void d_templ_2(this Self&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    template<typename Self> void d_templ_3(this Self&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    template<typename Self> void d_templ_4(this Self&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    template<typename Self> void d_templ_5(this Self&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_6(this Self&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_7(this Self&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_8(this Self&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_9(this Self&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_A(this Self&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void d_auto_0(this auto&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void d_auto_1(this auto&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void d_auto_2(this auto&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void d_auto_3(this auto&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void d_auto_4(this auto&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void d_auto_5(this auto&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_6(this auto&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_7(this auto&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_8(this auto&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_9(this auto&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_A(this auto&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C
new file mode 100644
index 00000000000..771200b839e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C
@@ -0,0 +1,26 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of incorrect uses of 'this' in declarations and definitions
+
+using func_type = void(this int); // { dg-line func_type_line }
+// { dg-error "a function type cannot have an explicit object parameter" "" { target *-*-* } func_type_line }
+// { dg-note "the type of an explicit object member function is a regular function type" "" { target *-*-* } func_type_line }
+
+using func_ptr_type = void(*)(this int); // { dg-line func_ptr_type_line }
+// { dg-error "a pointer to function type cannot have an explicit object parameter" "" { target *-*-* } func_ptr_type_line }
+// { dg-note "the type of a pointer to explicit object member function is a regular pointer to function type" "" { target *-*-* } func_ptr_type_line }
+
+struct S {
+    static void f(this S) {} // { dg-line static_member_func_line }
+};
+// { dg-error "an explicit object member function cannot be 'static'" "" { target *-*-* } static_member_func_line }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } static_member_func_line }
+
+using mem_func_type = void (S::*)(this S&); // { dg-line mem_func_type_line }
+// { dg-error "a pointer to member function type cannot have an explicit object parameter" "" { target *-*-* } mem_func_type_line }
+// { dg-note "the type of a pointer to explicit object member function is a regular pointer to function type" "" { target *-*-* } mem_func_type_line }
+
+void f(this int); // { dg-error "a non-member function cannot have an explicit object parameter" }
+void f(this int) {} // { dg-error "a non-member function cannot have an explicit object parameter" }
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C
new file mode 100644
index 00000000000..ec091d6ca67
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C
@@ -0,0 +1,20 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of an xobj parameter declared with a default argument
+
+struct S {
+  void f0(this S = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+  void f1(this S = {}); // { dg-error "an explicit object parameter may not have a default argument" }
+  void f2(this S);
+  void f10(this S s = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+  void f11(this S s = {}); // { dg-error "an explicit object parameter may not have a default argument" }
+  void f12(this S s);
+};
+
+void S::f1(this S) {}
+void S::f2(this S = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+
+void S::f11(this S s) {}
+void S::f12(this S s = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C
new file mode 100644
index 00000000000..1744b3f2299
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C
@@ -0,0 +1,16 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// location diagnostic text when an error is emitted from an xobj member function
+// this does not test for specific ill-formed code, just the additional diagnostic message
+
+// { dg-message "In explicit object member function" "" { target *-*-* } 0 }
+
+struct S {
+  void f(this S s) {
+    // The specific diagnosis issued here does not matter
+    // we just need to force an error to be emitted
+    +s; // { dg-error "" }
+  }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C
new file mode 100644
index 00000000000..f3b01d3c8ae
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C
@@ -0,0 +1,23 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of invalid uses of 'this' in body of xobj member functions
+
+// { dg-message "In explicit object member function" "" { target *-*-* } 0 }
+
+struct S0 {
+  int _n;
+  void f(this S0& s) { // { dg-note "use explicit object parameter 's' instead" } 
+    this->_n = 10; // { dg-error "'this' is unavailable for explicit object member functions" }
+    // suppress unused variable warning
+    static_cast<void>(s);
+  }
+};
+
+struct S1 {
+  int _n;
+  void f(this S1&) { // { dg-note "name and use the explicit object parameter instead" }
+    this->_n = 10; // { dg-error "'this' is unavailable for explicit object member functions" }
+  }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C
new file mode 100644
index 00000000000..ea53609a1e0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C
@@ -0,0 +1,17 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis when taking address of an unqualified xobj member function
+
+struct S {
+  void f(this S&) {}
+
+  void g(this S&) {}
+  void g(this S&, int) {}
+
+  void test() {
+    void (*fp)(S&) = &f; // { dg-error "taking the address of an explicit object member function must be qualified" }
+    void (*gp)(S&) = &g; // { dg-error "taking the address of an explicit object member function must be qualified" }
+  }
+};
+
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..86e0471eb7f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda1.C
@@ -0,0 +1,25 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// lambda declaration with xobj parameter
+
+struct S{};
+
+void test()
+{
+  (void)[](this auto&& self){};
+  (void)[](this auto& self){};
+  (void)[](this auto const& self){};
+  (void)[](this auto self){};
+
+  (void)[](this S&& self){};
+  (void)[](this S& self){};
+  (void)[](this S const& self){};
+  (void)[](this S self){};
+
+  (void)[x = 0](this auto&& self){};
+  (void)[x = 0](this auto& self){};
+  (void)[x = 0](this auto const& self){};
+  (void)[x = 0](this auto self){};
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C
new file mode 100644
index 00000000000..827197a6667
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C
@@ -0,0 +1,23 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// recursive lambdas
+
+inline constexpr int correct_result = 5 + 4 + 3 + 2 + 1; 
+
+int main()
+{
+  auto cl0 = [](this auto&& self, int n)      -> int { return n ? self(n - 1) + n : 0; };
+  auto cl1 = [](this auto const& self, int n) -> int { return n ? self(n - 1) + n : 0; };
+  auto cl2 = [](this auto self, int n)        -> int { return n ? self(n - 1) + n : 0; };
+  auto cl3 = [](this auto&& self, int n)     { if (!n) return 0; else return self(n - 1) + n; };
+  auto cl4 = [](this auto const& self, int n){ if (!n) return 0; else return self(n - 1) + n; };
+  auto cl5 = [](this auto self, int n)       { if (!n) return 0; else return self(n - 1) + n; };
+  if (cl0(5) != correct_result) __builtin_abort ();
+  if (cl1(5) != correct_result) __builtin_abort ();
+  if (cl2(5) != correct_result) __builtin_abort ();
+  if (cl3(5) != correct_result) __builtin_abort ();
+  if (cl4(5) != correct_result) __builtin_abort ();
+  if (cl5(5) != correct_result) __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda3.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda3.C
new file mode 100644
index 00000000000..9d222b0e547
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda3.C
@@ -0,0 +1,64 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// an adaptation of one of the examples in P0847R7
+
+struct Leaf { };
+struct Node;
+
+struct Tree {
+  enum class stored {leaf, node};
+  stored _discriminator;
+  union {
+    Leaf _leaf;
+    Node* _node;
+  };
+  Tree(Leaf) : _discriminator(stored::leaf), _leaf() {}
+  Tree(Node& node) : _discriminator(stored::node), _node(&node) {}
+};
+
+struct Node {
+    Tree left;
+    Tree right;
+};
+
+template<typename Visitor>
+auto visit_tree(Visitor&& visitor, Tree const& tree)
+{
+  switch (tree._discriminator)
+  {
+    case Tree::stored::leaf:
+      return visitor (tree._leaf);
+    case Tree::stored::node:
+      return visitor (tree._node);
+    default:
+      __builtin_abort (); 
+  }
+}
+
+template<typename... Ts>
+struct overload : Ts... { using Ts::operator()...; };
+
+int main()
+{
+  static constexpr int true_num_leaves = 8;
+  Node branch0{.left = Leaf{}, .right = Leaf{}};
+  Node branch1{.left = Leaf{}, .right = branch0};
+  Node branch2{.left = Leaf{}, .right = Leaf{}};
+  Node branch3{.left = branch1, .right = branch2};
+  Node branch4{.left = branch3, .right = Leaf{}};
+  Node branch5{.left = Leaf{}, .right = Leaf{}};
+  Node branch6{.left = branch4, .right = branch5};
+
+  Tree root (branch6);
+
+  int num_leaves = visit_tree (overload{
+    [](Leaf const&) { return 1; },
+    [](this auto const& self, Node* n) -> int {
+      return visit_tree (self, n->left) + visit_tree (self, n->right);
+    }},
+    root);
+  if (num_leaves != true_num_leaves)
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX0.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX0.C
new file mode 100644
index 00000000000..ff4da01bcce
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX0.C
@@ -0,0 +1,30 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// lambda invalid decl specs
+
+// very incomplete, but I wanted to demonstrate that each case does indeed error
+
+void test()
+{
+  auto f0 = [](this auto) mutable {}; // { dg-error {} }
+  auto f1 = [](this auto&) mutable {}; // { dg-error {} }
+  auto f2 = [](this auto const&) mutable {}; // { dg-error {} }
+  auto f3 = [](this auto&&) mutable {}; // { dg-error {} }
+
+  auto g0 = [](this auto) static {}; // { dg-error {} }
+  auto g1 = [](this auto&) static {}; // { dg-error {} }
+  auto g2 = [](this auto const&) static {}; // { dg-error {} }
+  auto g3 = [](this auto&&) static {}; // { dg-error {} }
+
+  auto fc0 = [n = 0](this auto) mutable {}; // { dg-error {} }
+  auto fc1 = [n = 0](this auto&) mutable {}; // { dg-error {} }
+  auto fc2 = [n = 0](this auto const&) mutable {}; // { dg-error {} }
+  auto fc3 = [n = 0](this auto&&) mutable {}; // { dg-error {} }
+
+  auto gc0 = [n = 0](this auto) static {}; // { dg-error {} }
+  auto gc1 = [n = 0](this auto&) static {}; // { dg-error {} }
+  auto gc2 = [n = 0](this auto const&) static {}; // { dg-error {} }
+  auto gc3 = [n = 0](this auto&&) static {}; // { dg-error {} }
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX20.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX20.C
new file mode 100644
index 00000000000..a808019e28e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX20.C
@@ -0,0 +1,46 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// calling captureless lambda call operator with unrelated explicit object parameter
+// through function pointer
+
+int main()
+{
+  auto f0 = [](this auto self) { return self; };
+  auto fp0_value     = static_cast<int(*)(int)        >(&decltype(f0)::operator());
+  auto fp0_lref      = static_cast<int(*)(int&)       >(&decltype(f0)::operator());
+  auto fp0_rref      = static_cast<int(*)(int&&)      >(&decltype(f0)::operator());
+  auto fp0_constlref = static_cast<int(*)(int const&) >(&decltype(f0)::operator());
+  auto fp0_constrref = static_cast<int(*)(int const&&)>(&decltype(f0)::operator());
+
+  auto f1 = [](this auto&& self) { return self; };
+  auto fp1_lref      = static_cast<int(*)(int&)       >(&decltype(f1)::operator());
+  auto fp1_rref      = static_cast<int(*)(int&&)      >(&decltype(f1)::operator());
+  auto fp1_constlref = static_cast<int(*)(int const&) >(&decltype(f1)::operator());
+  auto fp1_constrref = static_cast<int(*)(int const&&)>(&decltype(f1)::operator());
+
+  // both are needed for lvalue/rvalue overloads
+  #define MAGIC 42
+  int magic = MAGIC;
+
+  if (fp0_value (magic) != magic)
+    __builtin_abort ();
+  if (fp0_lref (magic) != magic)
+    __builtin_abort ();
+  if (fp0_rref (MAGIC) != magic)
+    __builtin_abort ();
+  if (fp0_constlref (magic) != magic)
+    __builtin_abort ();
+  if (fp0_constrref (MAGIC) != magic)
+    __builtin_abort ();
+
+  if (fp1_lref (magic) != magic)
+    __builtin_abort ();
+  if (fp1_rref (MAGIC) != magic)
+    __builtin_abort ();
+  if (fp1_constlref (magic) != magic)
+    __builtin_abort ();
+  if (fp1_constrref (MAGIC) != magic)
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX21.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX21.C
new file mode 100644
index 00000000000..715a2457945
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX21.C
@@ -0,0 +1,39 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// instantiating captureless lambda call operator with unrelated explicit object parameter
+
+void test0()
+{
+  auto f0 = [](this auto self) { return self; };
+  auto fp0_value     = static_cast<int(*)(int)        >(&decltype(f0)::operator());
+  auto fp0_lref      = static_cast<int(*)(int&)       >(&decltype(f0)::operator());
+  auto fp0_rref      = static_cast<int(*)(int&&)      >(&decltype(f0)::operator());
+  auto fp0_constlref = static_cast<int(*)(int const&) >(&decltype(f0)::operator());
+  auto fp0_constrref = static_cast<int(*)(int const&&)>(&decltype(f0)::operator());
+
+  auto f1 = [](this auto&& self) { return self; };
+  auto fp1_value      = static_cast<int(*)(int)        >(&decltype(f1)::operator()); // { dg-error {invalid 'static_cast' from type} }
+  auto fp1_lref       = static_cast<int(*)(int&)       >(&decltype(f1)::operator());
+  auto fp1_rref       = static_cast<int(*)(int&&)      >(&decltype(f1)::operator());
+  auto fp1_constlref  = static_cast<int(*)(int const&) >(&decltype(f1)::operator());
+  auto fp1_constrref  = static_cast<int(*)(int const&&)>(&decltype(f1)::operator());
+}
+
+void test1()
+{
+  auto f0 = [](this auto self) { return self; };
+  int (*fp0_value)(int)             = &decltype(f0)::operator();
+  int (*fp0_lref)(int&)             = &decltype(f0)::operator();
+  int (*fp0_rref)(int&&)            = &decltype(f0)::operator();
+  int (*fp0_constlref)(int const&)  = &decltype(f0)::operator();
+  int (*fp0_constrref)(int const&&) = &decltype(f0)::operator();
+
+  auto f1 = [](this auto&& self) { return self; };
+  int (*fp1_value)(int)              = &decltype(f1)::operator(); // { dg-error {no matches converting function} }
+  int (*fp1_lref)(int&)              = &decltype(f1)::operator();
+  int (*fp1_rref)(int&&)             = &decltype(f1)::operator();
+  int (*fp1_constlref)(int const&)   = &decltype(f1)::operator();
+  int (*fp1_constrref)(int const&&)  = &decltype(f1)::operator();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX25.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX25.C
new file mode 100644
index 00000000000..e23d9e16a07
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX25.C
@@ -0,0 +1,102 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// SFINAE when the call operator for a lambda with captures is instantiated
+// with an unrelated type.
+
+/* [expr.prim.lambda.general-5]
+
+   Given a lambda with a lambda-capture, the type of the explicit object
+   parameter, if any, of the lambda's function call operator (possibly
+   instantiated from a function call operator template) shall be either:
+
+   --(5.1) the closure type,
+   --(5.2) a class type derived from the closure type, or
+   --(5.3) a reference to a possibly cv-qualified such type.  */
+
+// The above wording is similar to [dcl.fct-15] which is handled by SFINAE,
+// thus we also handle the following cases the same way.
+
+// We need the 2 overloads to be ambiguous to observe substitution failure
+// for the lambda's call operator when instantiated with an unrelated type.
+// We accomplish this by introducing both overloads through using declarations.
+
+// (there is also a bug that makes these ambiguous without this trick,
+//  but it's unclear if it's my bug or not.)
+
+struct B0 {
+  void operator()(this auto) {}
+};
+template<typename T>
+struct S0 : T, B0 {
+  using B0::operator();
+  using T::operator();
+};
+
+void test0()
+{
+  auto s0 = S0{[](this auto){}};
+  void (*p0)(int) = &decltype(s0)::operator(); // { dg-error {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+
+  auto s1 = S0{[x = 42](this auto){}};
+  void (*p1)(int) = &decltype(s1)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} {Substitution failure for a captureful lambda with an unrelated xobj parameter type failed!} }
+}
+
+struct B1 {
+  void operator()(this auto&&) {}
+};
+template<typename T>
+struct S1 : T, B1 {
+  using B1::operator();
+  using T::operator();
+};
+
+void test1()
+{
+  auto s0 = S1{[](this auto&&){}};
+  void (*p0)(int&) = &decltype(s0)::operator(); // { dg-error {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+
+  auto s1 = S1{[x = 42](this auto&&){}};
+  void (*p1)(int&) = &decltype(s1)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} {Substitution failure for a captureful lambda with an unrelated xobj parameter type failed!} }
+}
+
+
+struct B2 {
+  // not a template, should be taken over the lambda's call operator
+  void operator()(this int&) {}
+};
+template<typename T>
+struct S2 : T, B2 {
+  using T::operator();
+  using B2::operator();
+};
+
+void test2()
+{
+  auto s0 = S2{[](this auto&&){}};
+  void (*p0)(int&) = &decltype(s0)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+
+  auto s1 = S2{[x = 42](this auto&&){}};
+  void (*p1)(int&) = &decltype(s1)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+}
+
+struct B3 {
+  // must be a template so it is not taken over the lambda's call operator
+  template<typename U = void>
+  void operator()(this int&) {}
+};
+template<typename T>
+struct S3 : T, B3 {
+  using B3::operator();
+  using T::operator();
+};
+
+void test3()
+{
+  auto s0 = S3{[](this auto&&){}};
+  void (*p0)(int&) = &decltype(s0)::operator(); // { dg-error {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+
+  auto s1 = S3{[x = 42](this auto&&){}};
+  void (*p1)(int&) = &decltype(s1)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} {Substitution failure for a captureful lambda with an unrelated xobj parameter type failed!} }
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX4.C
new file mode 100644
index 00000000000..6ce42ebefa8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX4.C
@@ -0,0 +1,23 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// calls to call operator of a lambda with captures with an implicit object argument
+// that derives from the lambda closure object
+
+template<typename T>
+struct S : T {
+    using T::operator();
+};
+
+template<typename T>
+S(T) -> S<T>; 
+
+int main()
+{
+  static constexpr int magic = 42;
+  int n = magic;
+  S s{[n](this auto&&){return n;}};
+  if (s () != magic)
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX40.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX40.C
new file mode 100644
index 00000000000..e6cd4568de5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX40.C
@@ -0,0 +1,18 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// diagnose mutation of lambda capture when called with a deduced as const explicit object parameter
+
+void test()
+{
+  auto f0 = [n = 5](this auto){ n = 10; }; // { dg-bogus {assignment of read-only variable} }
+  auto f1 = [n = 5](this auto&){ n = 10; };  // { dg-error {assignment of read-only variable} }
+  auto f2 = [n = 5](this auto const&){ n = 10; }; // { dg-error {assignment of read-only variable} }
+  auto f3 = [n = 5](this auto&&){ n = 10; };  // { dg-error {assignment of read-only variable} }
+
+  static_cast<decltype(f0) const&>(f0)();
+  static_cast<decltype(f1) const&>(f1)();
+  // f2 not needed
+  static_cast<decltype(f3) const&>(f3)();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX5.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX5.C
new file mode 100644
index 00000000000..88d45d90db1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX5.C
@@ -0,0 +1,21 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// calls to (captureless) lambda with explicit object parameter of unrelated type
+// with an appropriate converting constructor
+
+inline constexpr int magic = 42;
+
+struct S {
+  int _v;  
+  template<typename T>
+  S(T) : _v(magic) {}
+};
+
+int main()
+{
+  auto f = [](this S self){ return self._v; };
+  if (f () != magic)
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX6.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX6.C
new file mode 100644
index 00000000000..991edfe156a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX6.C
@@ -0,0 +1,48 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// unrelated xobj parameter type in captureless lambdas and lambdas with captures
+
+// declared with unrelated type
+
+struct S0{};
+
+void test0()
+{
+  auto f0 = [](this S0){ return 5; }; // { dg-bogus "a lambda with captures may not have an explicit object parameter of an unrelated type" }
+  auto f1 = [x = 42](this S0){ return 5; }; // { dg-error "a lambda with captures may not have an explicit object parameter of an unrelated type" }
+}
+
+// instantiation by calling with explicit template arguments
+
+template<typename T>
+struct S1 : T {
+  using T::operator();
+  operator int() const {return {};}
+};
+
+void test1()
+{
+  auto s0 = S1{[](this auto&& self) { return self; }}; // { dg-bogus {a lambda with captures may not have an explicit object parameter of an unrelated type} }
+  s0.operator()<int>(); // { dg-bogus {no matching function for call to} }
+
+  auto s1 = S1{[x = 0](this auto&& self) { return self; }}; // { dg-line t1_s1 }
+  s1.operator()<int>(); // { dg-error {no matching function for call to} }
+}
+// { dg-note {candidate:} {} { target *-*-* } t1_s1 }
+// { dg-note {template argument deduction/substitution failed} {} { target *-*-* } t1_s1 }
+// { dg-error {a lambda with captures may not have an explicit object parameter of an unrelated type} {} { target *-*-* } t1_s1 }
+
+// instantiation from overload resolution when taking address of call operator
+
+void test2()
+{
+  auto f = [x = 42](this auto&&){ return x; }; // { dg-line t2_f }
+
+  int (*fp0)(decltype(f)&) = &decltype(f)::operator();
+  int (*fp1)(int&) = &decltype(f)::operator(); // { dg-error {no matches converting function} }
+}
+
+// { dg-error "a lambda with captures may not have an explicit object parameter of an unrelated type" {depends on PR***** (tbd, low quality candidate diagnostics when taking the address of an overloaded function)} { xfail *-*-* } t2_f }
+// { dg-note "candidate is" "" { target *-*-* } t2_f }
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX7.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX7.C
new file mode 100644
index 00000000000..a068941413a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX7.C
@@ -0,0 +1,87 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// lambda capture mutability with explicit object parameter
+
+void capture_by_value()
+{
+  static constexpr int magic = 42;
+  auto f0 = [n = 0](this auto self){
+    n += magic;
+    return n;
+  };
+  auto f1 = [n = 0](this auto& self){
+    n += magic;
+    return n;
+  };
+  auto f2 = [n = 0](this auto&& self){
+    n += magic;
+    return n;
+  };
+
+  // passed by value, should still return a value equal to magic regardless
+  // of how many times it is called
+  if (f0 () != magic)
+    __builtin_abort ();
+  if (f0 () != magic)
+    __builtin_abort ();
+  // passed by reference, the returned value should increase by magic
+  // each time it is called
+  if (f1 () != magic)
+    __builtin_abort ();
+  if (f1 () != magic + magic)
+    __builtin_abort ();
+  if (f2 () != magic)
+    __builtin_abort ();
+  if (f2 () != magic + magic)
+    __builtin_abort ();
+}
+
+void capture_by_ref()
+{
+  static constexpr int magic = 42;
+  int n0 = 0;
+  auto f0 = [&n0](this auto self){
+    n0 += magic;
+  };
+  int n1 = 0;
+  auto f1 = [&n1](this auto& self){
+    n1 += magic;
+  };
+  int n2 = 0;
+  auto f2 = [&n2](this auto&& self){
+    n2 += magic;
+  };
+  int n3 = 0;
+  auto f3 = [&n3](this auto const& self){
+    n3 += magic;
+  };
+
+  // all calls should mutate their capture, the capture is by reference
+  if (f0 (); n0 != magic)
+    __builtin_abort ();
+  if (f0 (); n0 != magic + magic)
+    __builtin_abort ();
+
+  if (f1 (); n1 != magic)
+    __builtin_abort ();
+  if (f1 (); n1 != magic + magic)
+    __builtin_abort ();
+
+  if (f2 (); n2 != magic)
+    __builtin_abort ();
+  if (f2 (); n2 != magic + magic)
+    __builtin_abort ();
+
+  if (f3 (); n3 != magic)
+    __builtin_abort ();
+  if (f3 (); n3 != magic + magic)
+    __builtin_abort ();
+}
+
+int main()
+{
+  capture_by_value ();
+  capture_by_ref ();
+}
+
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..eb8607781bf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-arrow.C
@@ -0,0 +1,28 @@
+// 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..bb43a0af913
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-assignment.C
@@ -0,0 +1,27 @@
+// 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..ecd6bdfd44c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-call.C
@@ -0,0 +1,40 @@
+// 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..3eb003062c0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-subscript.C
@@ -0,0 +1,40 @@
+// 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..f38615e2b98
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C
@@ -0,0 +1,58 @@
+// 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_OPS(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..634e878c93e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C
@@ -0,0 +1,57 @@
+// 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_OPS(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..b897a4bac71
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem.h
@@ -0,0 +1,210 @@
+// tests for ops that must be member functions are seperate
+
+// the name of the class refers to the type of it's member functions xobj parameter
+
+#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, int) { return self; }	\
+  TYPE operator*(this TYPE self) { return self; }		\
+  TYPE operator->*(this TYPE self, int) { 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, 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) { 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) <=> 0;	\
+			\
+  *(OPERAND);		\
+  (OPERAND) ->* 0;	\
+  &(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) <=> 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))));			\
+  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..d08e938c98d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-mem.C
@@ -0,0 +1,171 @@
+// 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" }
+
+// 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_VALID(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&>; }, "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 const&>; }, "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..865b1f57d4a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C
@@ -0,0 +1,237 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// well-formed and ill-formed uses of non-member capable operators in a requires expression
+
+#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.
+
+template<typename T, typename U>
+concept same_as = __is_same(T, U);
+
+#define TEST_INVALID(OPERAND, CORRECT_TYPE) \
+  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) <=> 0; }, "Unexpected success calling operator <=> with " #OPERAND);	\
+														\
+  static_assert(!requires{ *(OPERAND); }, "Unexpected success calling operator unary* with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) ->* 0; }, "Unexpected success calling operator ->* with " #OPERAND);	\
+  /* We need to check the return type to confirm the built-in operator was not selected.  */			\
+  static_assert(!requires{ {&(OPERAND)} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected success calling operator unary& with " #OPERAND);					\
+  static_assert(!requires{ {(OPERAND), 0} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected success calling operator , with " #OPERAND);
+
+#define TEST_VALID(OPERAND, CORRECT_TYPE) \
+  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) <=> 0; }, "Unexpected failure calling operator <=> with " #OPERAND);	\
+														\
+  static_assert(requires{ *(OPERAND); }, "Unexpected failure calling operator unary* with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) ->* 0; }, "Unexpected failure calling operator ->* with " #OPERAND);	\
+  /* We need to check the return type to confirm we selected our overload, not the built-in operator.  */	\
+  static_assert(requires{ {&(OPERAND)} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected failure calling operator unary& with " #OPERAND);					\
+  static_assert(requires{ {(OPERAND), 0} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected failure calling operator , with " #OPERAND);
+
+// Return types need to be tested for the deduced case
+
+#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) <=> 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)} -> 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, DepValue)
+  TEST_VALID(static_cast<DepValue&&>(value), DepValue)
+  TEST_VALID(static_cast<DepValue const&>(value), DepValue)
+  TEST_VALID(static_cast<DepValue const&&>(value), DepValue)
+}
+
+template<typename DepLRef = LRef>
+void test_l_ref()
+{
+  DepLRef l_ref{};
+  TEST_VALID(l_ref, DepLRef&)
+  TEST_INVALID(static_cast<DepLRef&&>(l_ref), DepLRef&)
+  TEST_INVALID(static_cast<DepLRef const&>(l_ref), DepLRef&)
+  TEST_INVALID(static_cast<DepLRef const&&>(l_ref), DepLRef&)
+}
+
+template<typename DepRRef = RRef>
+void test_r_ref()
+{
+  DepRRef r_ref{};
+  TEST_INVALID(r_ref, DepRRef&&)
+  TEST_VALID(static_cast<DepRRef&&>(r_ref), DepRRef&&)
+  TEST_INVALID(static_cast<DepRRef const&>(r_ref), DepRRef&&)
+  TEST_INVALID(static_cast<DepRRef const&&>(r_ref), DepRRef&&)
+}
+
+template<typename DepConstLRef = ConstLRef>
+void test_const_l_ref()
+{
+  DepConstLRef const_l_ref{};
+  TEST_VALID(const_l_ref, DepConstLRef const&)
+  TEST_VALID(static_cast<DepConstLRef&&>(const_l_ref), DepConstLRef const&)
+  TEST_VALID(static_cast<DepConstLRef const&>(const_l_ref), DepConstLRef const&)
+  TEST_VALID(static_cast<DepConstLRef const&&>(const_l_ref), DepConstLRef const&)
+}
+
+template<typename DepConstRRef = ConstRRef>
+void test_const_r_ref()
+{
+  DepConstRRef const_r_ref{};
+  TEST_INVALID(const_r_ref, DepConstRRef const&&)
+  TEST_VALID(static_cast<DepConstRRef&&>(const_r_ref), DepConstRRef const&&)
+  TEST_INVALID(static_cast<DepConstRRef const&>(const_r_ref), DepConstRRef const&&)
+  TEST_VALID(static_cast<DepConstRRef const&&>(const_r_ref), DepConstRRef const&&)
+}
+
+template<typename DepDeduced = Deduced>
+void test_deduced()
+{
+  DepDeduced 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();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C
new file mode 100644
index 00000000000..7fcfcc31c30
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C
@@ -0,0 +1,246 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// redeclarations of xobj/iobj member functions where the iobj member function
+// is not ref qualified
+
+// it does not make sense to check for the inverse in this test (7 iobj, 1 xobj)
+// because you are not allowed to overload iobj member functions without ref qualifiers
+// with iobj member functions that do (and vice versa)
+
+// iobj first
+
+struct S0 {
+  void f0(); // { dg-note "previous declaration" }
+  void f0(this S0 &&); // { dg-error "cannot be overloaded with" }
+  void f0(this S0 const&); // { dg-bogus "" }
+  void f0(this S0 const&&); // { dg-bogus "" }
+  void f0(this S0 volatile&); // { dg-bogus "" }
+  void f0(this S0 volatile&&); // { dg-bogus "" }
+  void f0(this S0 const volatile&); // { dg-bogus "" }
+  void f0(this S0 const volatile&&); // { dg-bogus "" }
+
+  void f1(); // { dg-note "previous declaration" }
+  void f1(this S0 &); // { dg-error "cannot be overloaded with" }
+  void f1(this S0 const&); // { dg-bogus "" }
+  void f1(this S0 const&&); // { dg-bogus "" }
+  void f1(this S0 volatile&); // { dg-bogus "" }
+  void f1(this S0 volatile&&); // { dg-bogus "" }
+  void f1(this S0 const volatile&); // { dg-bogus "" }
+  void f1(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fc0() const; // { dg-note "previous declaration" }
+  void fc0(this S0 &); // { dg-bogus "" }
+  void fc0(this S0 &&); // { dg-bogus "" }
+  void fc0(this S0 const&&); // { dg-error "cannot be overloaded with" }
+  void fc0(this S0 volatile&); // { dg-bogus "" }
+  void fc0(this S0 volatile&&); // { dg-bogus "" }
+  void fc0(this S0 const volatile&); // { dg-bogus "" }
+  void fc0(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fc1() const; // { dg-note "previous declaration" }
+  void fc1(this S0 &); // { dg-bogus "" }
+  void fc1(this S0 &&); // { dg-bogus "" }
+  void fc1(this S0 const&); // { dg-error "cannot be overloaded with" }
+  void fc1(this S0 volatile&); // { dg-bogus "" }
+  void fc1(this S0 volatile&&); // { dg-bogus "" }
+  void fc1(this S0 const volatile&); // { dg-bogus "" }
+  void fc1(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fv0() volatile; // { dg-note "previous declaration" }
+  void fv0(this S0 &); // { dg-bogus "" }
+  void fv0(this S0 &&); // { dg-bogus "" }
+  void fv0(this S0 const&); // { dg-bogus "" }
+  void fv0(this S0 const&&); // { dg-bogus "" }
+  void fv0(this S0 volatile&&); // { dg-error "cannot be overloaded with" }
+  void fv0(this S0 const volatile&); // { dg-bogus "" }
+  void fv0(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fv1() volatile; // { dg-note "previous declaration" }
+  void fv1(this S0 &); // { dg-bogus "" }
+  void fv1(this S0 &&); // { dg-bogus "" }
+  void fv1(this S0 const&); // { dg-bogus "" }
+  void fv1(this S0 const&&); // { dg-bogus "" }
+  void fv1(this S0 volatile&); // { dg-error "cannot be overloaded with" }
+  void fv1(this S0 const volatile&); // { dg-bogus "" }
+  void fv1(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fcv0() const volatile; // { dg-note "previous declaration" }
+  void fcv0(this S0 &); // { dg-bogus "" }
+  void fcv0(this S0 &&); // { dg-bogus "" }
+  void fcv0(this S0 const&); // { dg-bogus "" }
+  void fcv0(this S0 const&&); // { dg-bogus "" }
+  void fcv0(this S0 volatile&); // { dg-bogus "" }
+  void fcv0(this S0 volatile&&); // { dg-bogus "" }
+  void fcv0(this S0 const volatile&&); // { dg-error "cannot be overloaded with" }
+
+  void fcv1() const volatile; // { dg-note "previous declaration" }
+  void fcv1(this S0 &); // { dg-bogus "" }
+  void fcv1(this S0 &&); // { dg-bogus "" }
+  void fcv1(this S0 const&); // { dg-bogus "" }
+  void fcv1(this S0 const&&); // { dg-bogus "" }
+  void fcv1(this S0 volatile&); // { dg-bogus "" }
+  void fcv1(this S0 volatile&&); // { dg-bogus "" }
+  void fcv1(this S0 const volatile&); // { dg-error "cannot be overloaded with" }
+};
+
+// iobj last
+
+struct S1 {
+  void f0(this S1 &&); // { dg-note "previous declaration" }
+  void f0(this S1 const&); // { dg-bogus "" }
+  void f0(this S1 const&&); // { dg-bogus "" }
+  void f0(this S1 volatile&); // { dg-bogus "" }
+  void f0(this S1 volatile&&); // { dg-bogus "" }
+  void f0(this S1 const volatile&); // { dg-bogus "" }
+  void f0(this S1 const volatile&&); // { dg-bogus "" }
+  void f0(); // { dg-error "cannot be overloaded with" }
+
+  void f1(this S1 &); // { dg-note "previous declaration" }
+  void f1(this S1 const&); // { dg-bogus "" }
+  void f1(this S1 const&&); // { dg-bogus "" }
+  void f1(this S1 volatile&); // { dg-bogus "" }
+  void f1(this S1 volatile&&); // { dg-bogus "" }
+  void f1(this S1 const volatile&); // { dg-bogus "" }
+  void f1(this S1 const volatile&&); // { dg-bogus "" }
+  void f1(); // { dg-error "cannot be overloaded with" }
+
+  void fc0(this S1 &); // { dg-bogus "" }
+  void fc0(this S1 &&); // { dg-bogus "" }
+  void fc0(this S1 const&&); // { dg-note "previous declaration" }
+  void fc0(this S1 volatile&); // { dg-bogus "" }
+  void fc0(this S1 volatile&&); // { dg-bogus "" }
+  void fc0(this S1 const volatile&); // { dg-bogus "" }
+  void fc0(this S1 const volatile&&); // { dg-bogus "" }
+  void fc0() const; // { dg-error "cannot be overloaded with" }
+
+  void fc1(this S1 &); // { dg-bogus "" }
+  void fc1(this S1 &&); // { dg-bogus "" }
+  void fc1(this S1 const&); // { dg-note "previous declaration" }
+  void fc1(this S1 volatile&); // { dg-bogus "" }
+  void fc1(this S1 volatile&&); // { dg-bogus "" }
+  void fc1(this S1 const volatile&); // { dg-bogus "" }
+  void fc1(this S1 const volatile&&); // { dg-bogus "" }
+  void fc1() const; // { dg-error "cannot be overloaded with" }
+
+  void fv0(this S1 &); // { dg-bogus "" }
+  void fv0(this S1 &&); // { dg-bogus "" }
+  void fv0(this S1 const&); // { dg-bogus "" }
+  void fv0(this S1 const&&); // { dg-bogus "" }
+  void fv0(this S1 volatile&&); // { dg-note "previous declaration" }
+  void fv0(this S1 const volatile&); // { dg-bogus "" }
+  void fv0(this S1 const volatile&&); // { dg-bogus "" }
+  void fv0() volatile; // { dg-error "cannot be overloaded with" }
+
+  void fv1(this S1 &); // { dg-bogus "" }
+  void fv1(this S1 &&); // { dg-bogus "" }
+  void fv1(this S1 const&); // { dg-bogus "" }
+  void fv1(this S1 const&&); // { dg-bogus "" }
+  void fv1(this S1 volatile&); // { dg-note "previous declaration" }
+  void fv1(this S1 const volatile&); // { dg-bogus "" }
+  void fv1(this S1 const volatile&&); // { dg-bogus "" }
+  void fv1() volatile; // { dg-error "cannot be overloaded with" }
+
+  void fcv0(this S1 &); // { dg-bogus "" }
+  void fcv0(this S1 &&); // { dg-bogus "" }
+  void fcv0(this S1 const&); // { dg-bogus "" }
+  void fcv0(this S1 const&&); // { dg-bogus "" }
+  void fcv0(this S1 volatile&); // { dg-bogus "" }
+  void fcv0(this S1 volatile&&); // { dg-bogus "" }
+  void fcv0(this S1 const volatile&&); // { dg-note "previous declaration" }
+  void fcv0() const volatile; // { dg-error "cannot be overloaded with" }
+
+  void fcv1(this S1 &); // { dg-bogus "" }
+  void fcv1(this S1 &&); // { dg-bogus "" }
+  void fcv1(this S1 const&); // { dg-bogus "" }
+  void fcv1(this S1 const&&); // { dg-bogus "" }
+  void fcv1(this S1 volatile&); // { dg-bogus "" }
+  void fcv1(this S1 volatile&&); // { dg-bogus "" }
+  void fcv1(this S1 const volatile&); // { dg-note "previous declaration" }
+  void fcv1() const volatile; // { dg-error "cannot be overloaded with" }
+};
+
+// in order (iobj replacing one of the following in each group)
+// lvalue ref to S
+// rvalue ref to S
+// lvalue c ref to S
+// rvalue c ref to S
+// lvalue v ref to S
+// rvalue v ref to S
+// lvalue cv ref to S
+// rvalue cv ref to S
+
+struct S2 {
+  void f0(); // { dg-note "previous declaration" }
+  void f0(this S2 &&); // { dg-error "cannot be overloaded with" }
+  void f0(this S2 const&); // { dg-bogus "" }
+  void f0(this S2 const&&); // { dg-bogus "" }
+  void f0(this S2 volatile&); // { dg-bogus "" }
+  void f0(this S2 volatile&&); // { dg-bogus "" }
+  void f0(this S2 const volatile&); // { dg-bogus "" }
+  void f0(this S2 const volatile&&); // { dg-bogus "" }
+
+  void f1(this S2 &); // { dg-note "previous declaration" }
+  void f1(); // { dg-error "cannot be overloaded with" }
+  void f1(this S2 const&); // { dg-bogus "" }
+  void f1(this S2 const&&); // { dg-bogus "" }
+  void f1(this S2 volatile&); // { dg-bogus "" }
+  void f1(this S2 volatile&&); // { dg-bogus "" }
+  void f1(this S2 const volatile&); // { dg-bogus "" }
+  void f1(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fc0(this S2 &); // { dg-bogus "" }
+  void fc0(this S2 &&); // { dg-bogus "" }
+  void fc0() const; // { dg-note "previous declaration" }
+  void fc0(this S2 const&&); // { dg-error "cannot be overloaded with" }
+  void fc0(this S2 volatile&); // { dg-bogus "" }
+  void fc0(this S2 volatile&&); // { dg-bogus "" }
+  void fc0(this S2 const volatile&); // { dg-bogus "" }
+  void fc0(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fc1(this S2 &); // { dg-bogus "" }
+  void fc1(this S2 &&); // { dg-bogus "" }
+  void fc1(this S2 const&); // { dg-note "previous declaration" }
+  void fc1() const; // { dg-error "cannot be overloaded with" }
+  void fc1(this S2 volatile&); // { dg-bogus "" }
+  void fc1(this S2 volatile&&); // { dg-bogus "" }
+  void fc1(this S2 const volatile&); // { dg-bogus "" }
+  void fc1(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fv0(this S2 &); // { dg-bogus "" }
+  void fv0(this S2 &&); // { dg-bogus "" }
+  void fv0(this S2 const&); // { dg-bogus "" }
+  void fv0(this S2 const&&); // { dg-bogus "" }
+  void fv0() volatile; // { dg-note "previous declaration" }
+  void fv0(this S2 volatile&&); // { dg-error "cannot be overloaded with" }
+  void fv0(this S2 const volatile&); // { dg-bogus "" }
+  void fv0(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fv1(this S2 &); // { dg-bogus "" }
+  void fv1(this S2 &&); // { dg-bogus "" }
+  void fv1(this S2 const&); // { dg-bogus "" }
+  void fv1(this S2 const&&); // { dg-bogus "" }
+  void fv1(this S2 volatile&); // { dg-note "previous declaration" }
+  void fv1() volatile; // { dg-error "cannot be overloaded with" }
+  void fv1(this S2 const volatile&); // { dg-bogus "" }
+  void fv1(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fcv0(this S2 &); // { dg-bogus "" }
+  void fcv0(this S2 &&); // { dg-bogus "" }
+  void fcv0(this S2 const&); // { dg-bogus "" }
+  void fcv0(this S2 const&&); // { dg-bogus "" }
+  void fcv0(this S2 volatile&); // { dg-bogus "" }
+  void fcv0(this S2 volatile&&); // { dg-bogus "" }
+  void fcv0() const volatile; // { dg-note "previous declaration" }
+  void fcv0(this S2 const volatile&&); // { dg-error "cannot be overloaded with" }
+
+  void fcv1(this S2 &); // { dg-bogus "" }
+  void fcv1(this S2 &&); // { dg-bogus "" }
+  void fcv1(this S2 const&); // { dg-bogus "" }
+  void fcv1(this S2 const&&); // { dg-bogus "" }
+  void fcv1(this S2 volatile&); // { dg-bogus "" }
+  void fcv1(this S2 volatile&&); // { dg-bogus "" }
+  void fcv1(this S2 const volatile&); // { dg-note "previous declaration" }
+  void fcv1() const volatile; // { dg-error "cannot be overloaded with" }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C
new file mode 100644
index 00000000000..adb6ae5a380
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C
@@ -0,0 +1,161 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+// { dg-options "-pedantic-errors -Wno-volatile" }
+
+// rejecting redeclarations of by-value xobj member functions
+// as iobj member functions that are not ref qualified (and vice-versa)
+// also check that valid overloads are accepted without diagnostic
+
+//  iobj |  xobj  |  MSVC | clang | ISOC++
+//  none |  none  |   X   |   X   |   X
+//  none |     c  |   X   |   O   |   X
+//  none |     v  |   X   |   O   |   X
+//  none |    cv  |   X   |   O   |   X
+//     c |  none  |   O   |   O   |   O
+//     c |     c  |   O   |   X   |   O
+//     c |     v  |   O   |   O   |   O
+//     c |    cv  |   O   |   O   |   O
+//     v |  none  |   O   |   O   |   O
+//     v |     c  |   O   |   O   |   O
+//     v |     v  |   O   |   X   |   O
+//     v |    cv  |   O   |   O   |   O
+//    cv |  none  |   O   |   O   |   O
+//    cv |     c  |   O   |   O   |   O
+//    cv |     v  |   O   |   O   |   O
+//    cv |    cv  |   O   |   X   |   O
+
+/* Top-level cv qualifiers are supposed to be discarded from
+   the parameters of a function declaration.
+     
+   [dcl.fct.5]
+   After producing the list of parameter types, any top-level
+   cv-qualifiers modifying a parameter type are deleted when forming
+   the function type.
+
+   According to the standard, the type of an implicit object parameter
+   is always a reference. This isn't reflected in GCC but we still need
+   to take this rule into account here.
+
+   [over.match.funcs.general.4]
+   For implicit object member functions, the type of the implicit
+   object parameter is
+   -- “lvalue reference to cv X” for functions declared
+      without a ref-qualifier or with the & ref-qualifier
+   -- “rvalue reference to cv X” for functions declared with
+      the && ref-qualifier
+    
+   When comparing an iobj and xobj member function to see if they are
+   redeclarations we treat them differently depending on if the iobj
+   member function has a ref qualifier or not.
+   If the iobj member function does not have a ref qualifier, we need to
+   strip the top-level references before comparing them.
+
+   [basic.scope.scope.3]
+   Two non-static member functions have corresponding object
+   parameters if:
+   -- exactly one is an implicit object member function with no
+      ref-qualifier and the types of their object parameters
+      ([dcl.fct]), after removing top-level references, are the
+      same, or
+   -- their object parameters have the same type. */
+
+struct S {
+  void f0(); // { dg-note "previous declaration" }
+  void f0(this S); // { dg-error "cannot be overloaded with" }
+
+  void f1(); // { dg-note "previous declaration" }
+  void f1(this S const); // { dg-error "cannot be overloaded with" }
+
+  void f2(); // { dg-note "previous declaration" }
+  void f2(this S volatile); // { dg-error "cannot be overloaded with" }
+
+  void f3(); // { dg-note "previous declaration" }
+  void f3(this S const volatile); // { dg-error "cannot be overloaded with" }
+
+  void fc0() const; // { dg-bogus "" }
+  void fc0(this S); // { dg-bogus "" }
+
+  void fc1() const; // { dg-bogus "" }
+  void fc1(this S const); // { dg-bogus "" }
+
+  void fc2() const; // { dg-bogus "" }
+  void fc2(this S volatile); // { dg-bogus "" }
+
+  void fc3() const; // { dg-bogus "" }
+  void fc3(this S const volatile); // { dg-bogus "" }
+
+  void fv0() volatile; // { dg-bogus "" }
+  void fv0(this S); // { dg-bogus "" }
+
+  void fv1() volatile; // { dg-bogus "" }
+  void fv1(this S const); // { dg-bogus "" }
+
+  void fv2() volatile; // { dg-bogus "" }
+  void fv2(this S volatile); // { dg-bogus "" }
+
+  void fv3() volatile; // { dg-bogus "" }
+  void fv3(this S const volatile); // { dg-bogus "" }
+
+  void fcv0() const volatile; // { dg-bogus "" }
+  void fcv0(this S); // { dg-bogus "" }
+
+  void fcv1() const volatile; // { dg-bogus "" }
+  void fcv1(this S const); // { dg-bogus "" }
+
+  void fcv2() const volatile; // { dg-bogus "" }
+  void fcv2(this S volatile); // { dg-bogus "" }
+
+  void fcv3() const volatile; // { dg-bogus "" }
+  void fcv3(this S const volatile); // { dg-bogus "" }
+
+  // same as the above f cases except reversed
+
+  void g0(this S); // { dg-note "previous declaration" }
+  void g0(); // { dg-error "cannot be overloaded with" }
+
+  void g1(this S const); // { dg-note "previous declaration" }
+  void g1(); // { dg-error "cannot be overloaded with" }
+
+  void g2(this S volatile); // { dg-note "previous declaration" }
+  void g2(); // { dg-error "cannot be overloaded with" }
+
+  void g3(this S const volatile); // { dg-note "previous declaration" }
+  void g3(); // { dg-error "cannot be overloaded with" }
+
+  void gc0(this S); // { dg-bogus "" }
+  void gc0() const; // { dg-bogus "" }
+
+  void gc1(this S const); // { dg-bogus "" }
+  void gc1() const; // { dg-bogus "" }
+
+  void gc2(this S volatile); // { dg-bogus "" }
+  void gc2() const; // { dg-bogus "" }
+
+  void gc3(this S const volatile); // { dg-bogus "" }
+  void gc3() const; // { dg-bogus "" }
+
+  void gv0(this S); // { dg-bogus "" }
+  void gv0() volatile; // { dg-bogus "" }
+
+  void gv1(this S const); // { dg-bogus "" }
+  void gv1() volatile; // { dg-bogus "" }
+
+  void gv2(this S volatile); // { dg-bogus "" }
+  void gv2() volatile; // { dg-bogus "" }
+
+  void gv3(this S const volatile); // { dg-bogus "" }
+  void gv3() volatile; // { dg-bogus "" }
+
+  void gcv0(this S); // { dg-bogus "" }
+  void gcv0() const volatile; // { dg-bogus "" }
+
+  void gcv1(this S const); // { dg-bogus "" }
+  void gcv1() const volatile; // { dg-bogus "" }
+
+  void gcv2(this S volatile); // { dg-bogus "" }
+  void gcv2() const volatile; // { dg-bogus "" }
+
+  void gcv3(this S const volatile); // { dg-bogus "" }
+  void gcv3() const volatile; // { dg-bogus "" }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-virtual.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-virtual.C
new file mode 100644
index 00000000000..023cdc2e0fe
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-virtual.C
@@ -0,0 +1,95 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// diagnose xobj member functions that override
+// or are declared as virtual, override, or final
+
+struct B {
+  virtual void f0() {} // { dg-note {virtual function declared here} }
+  virtual void f1() {} // { dg-note {virtual function declared here} }
+  virtual void f2() {} // { dg-note {virtual function declared here} }
+  virtual void f3() {} // { dg-note {virtual function declared here} }
+  virtual void f4() {} // { dg-note {virtual function declared here} }
+  virtual void f5() {} // { dg-note {virtual function declared here} }
+  virtual void f6() {} // { dg-note {virtual function declared here} }
+  virtual void f7() {} // { dg-note {virtual function declared here} }
+  virtual ~B() {}
+};
+
+struct S : B {
+  virtual void f0(this S&) {}		     // { dg-line line_f0 }
+  virtual void f1(this S&) override {}	     // { dg-line line_f1 }
+  virtual void f2(this S&) final {}	     // { dg-line line_f2 }
+  virtual void f3(this S&) override final {} // { dg-line line_f3 }
+  void f4(this S&) {}			     // { dg-line line_f4 }
+  void f5(this S&) override {}		     // { dg-line line_f5 }
+  void f6(this S&) final {}		     // { dg-line line_f6 }
+  void f7(this S&) override final {}	     // { dg-line line_f7 }
+};
+
+// { dg-error {an explicit object member function cannot be 'virtual'} "" { target *-*-* } line_f0 }
+// { dg-error {an explicit object member function cannot be 'virtual'} "" { target *-*-* } line_f1 }
+// { dg-error {an explicit object member function cannot be 'virtual'} "" { target *-*-* } line_f2 }
+// { dg-error {an explicit object member function cannot be 'virtual'} "" { target *-*-* } line_f3 }
+
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f0 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f1 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f2 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f3 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f4 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f5 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f6 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f7 }
+
+// these should be suppressed, the wording conflicts with the error
+// the issue is not that they don't override, it's that they do override, and that isn't allowed
+// { dg-bogus "marked 'override', but does not override" "" { xfail *-*-* } line_f1 }
+// { dg-bogus "marked 'final', but is not virtual"	 "" { xfail *-*-* } line_f2 }
+// { dg-bogus "marked '(override|final)'"		 "" { xfail *-*-* } line_f3 }
+
+// { dg-bogus "marked 'override', but does not override" "" { xfail *-*-* } line_f5 }
+// { dg-bogus "marked 'final', but is not virtual"	 "" { xfail *-*-* } line_f6 }
+// { dg-bogus "marked '(override|final)'"		 "" { xfail *-*-* } line_f7 }
+
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_f0 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_f1 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_f2 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_f3 }
+// { dg-note "explicit object parameter declared here" "" { xfail *-*-* } line_f4 }
+// { dg-note "explicit object parameter declared here" "" { xfail *-*-* } line_f5 }
+// { dg-note "explicit object parameter declared here" "" { xfail *-*-* } line_f6 }
+// { dg-note "explicit object parameter declared here" "" { xfail *-*-* } line_f7 }
+
+struct S1 {
+  virtual void f0(this S&) {}		     // { dg-line line_S1_f0 }
+  virtual void f1(this S&) override {}	     // { dg-line line_S1_f1 }
+  virtual void f2(this S&) final {}	     // { dg-line line_S1_f2 }
+  virtual void f3(this S&) override final {} // { dg-line line_S1_f3 }
+  void f4(this S&) {}
+  void f5(this S&) override {}		     // { dg-line line_S1_f5 }
+  void f6(this S&) final {}		     // { dg-line line_S1_f6 }
+  void f7(this S&) override final {}	     // { dg-line line_S1_f7 }
+};
+
+// { dg-error "an explicit object member function cannot be 'virtual'" "" { target *-*-* } line_S1_f0 }
+// { dg-error "an explicit object member function cannot be 'virtual'" "" { target *-*-* } line_S1_f1 }
+// { dg-error "an explicit object member function cannot be 'virtual'" "" { target *-*-* } line_S1_f2 }
+// { dg-error "an explicit object member function cannot be 'virtual'" "" { target *-*-* } line_S1_f3 }
+
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_S1_f0 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_S1_f1 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_S1_f2 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_S1_f3 }
+
+// I think I want these suppressed, but theres a decent argument that they should stay
+// theres arguably no reason the error about virtual should suppress these
+// { dg-bogus "marked 'override', but does not override" "" { xfail *-*-* } line_S1_f1 }
+// { dg-bogus "marked 'final', but is not virtual"	 "" { xfail *-*-* } line_S1_f2 }
+// { dg-bogus "marked '(override|final)'"		 "" { xfail *-*-* } line_S1_f3 }
+
+// I don't want to suppress these, there is nothing that could possibly be overridden
+// even if the xobj param was removed
+// { dg-error "marked 'override', but does not override" "" { target *-*-* } line_S1_f5 }
+// { dg-error "marked 'final', but is not virtual"	 "" { target *-*-* } line_S1_f6 }
+// { dg-error "marked '(override|final)'"		 "" { target *-*-* } line_S1_f7 }
+
-- 
2.42.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-27  5:35                                                                                                           ` [PATCH v6 " waffl3x
@ 2023-11-28  3:31                                                                                                             ` waffl3x
  2023-11-28 10:00                                                                                                               ` waffl3x
  2023-11-30  5:00                                                                                                             ` Jason Merrill
  1 sibling, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-28  3:31 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

On Sunday, November 26th, 2023 at 7:40 PM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 11/26/23 20:44, waffl3x wrote:
> 
> > > > > > The other problem I'm having is
> > > > > > 
> > > > > > auto f0 = [n = 5, &m](this auto const&){ n = 10; };
> > > > > > This errors just fine, the lambda is unconditionally const so
> > > > > > LAMBDA_EXPR_MUTABLE_P flag is set for the closure.
> > > > > > 
> > > > > > This on the other hand does not. The constness of the captures depends
> > > > > > on (I assume) LAMBDA_EXPR_MUTABLE_P so all the captures are non-const
> > > > > > here.
> > > > > > auto f1 = [n = 5](this auto&& self){ n = 10; };
> > > > > > as_const(f1)();
> > > > > 
> > > > > That sounds correct, why would this be an error?
> > > > > 
> > > > > The constness of the captures doesn't depend on LAMBDA_EXPR_MUTABLE_P,
> > > > > it depends on the type of the object parameter, which in this case is
> > > > > non-const, so so are the captures.
> > > > 
> > > > Oops, I should have just made a less terse example, you missed the
> > > > "as_const" in the call to the lambda. The object parameter should be
> > > > deduced as const here. I definitely should have made that example
> > > > better, my bad.
> > > 
> > > Ah, yes, I see it now.
> > 
> > I don't remember if I relayed my planned fix for this to you. My
> > current idea is to modify the tree during instantiation of the lambda's
> > body somewhere near tsubst and apply const to all it's members. This is
> > unfortunately the best idea I have so far and it feels like an awful
> > hack. I am open to better ideas, but I don't think we can do anything
> > until the template is instantiated so I think it has to be there.
> 
> 
> I think the answer should be in lambda_proxy_type. The case where we
> build a DECLTYPE_TYPE may need to be expanded to cover this situation.
> 
> > Should I wait until I fix the issue in tsubst_lambda_expr before
> > submitting the patch? I'm fine to do it either way, just whatever you
> > prefer. If I finish cleaning up these tests before I hear back I'll go
> > ahead and submit it and then start looking at different solutions in
> > there.
> 
> 
> Go ahead and submit.
> 
> Jason

I'm going to need to sit down and set up a proper e-mail application
once I'm done with all this, I missed your reply because it went off to
another thread. Luckily, I decided to send the patch anyway, and when I
noticed that my patch was not under the same thread I came looking for
it. Ah well, what a pain, I guess getting used to these mail list
things is just going to take time.

> > It doesn't. The issue is messing with the type of (potentially) a lot
> > of different functions. Even if it doesn't actually break anything, it
> > seems like the kind of hidden mutation that you were objecting to.
> 
> 
> Oh... yeah..., I see the issue now. I still don't think the solution
> used for static lambdas will work, or be painless anyhow, but if I
> can't find something better I will try to use that one.
> 
> > > Well, even so, I can just clear it after it gets used as we just need
> > > it to pass the closure type down. Perhaps I should have led with this,
> > > but as it stands the version that uses TYPE_METHOD_BASETYPE bootstraps
> > > with no regressions. I'll still look deeper but I'm pretty confident in
> > > my decision here, I really don't want to try to unravel what
> > > build_memfn_type does, I would rather find a whole different way of
> > > passing that information down.
> > 
> > But the existing code already works fine, it's just a question of
> > changing the conditions so we handle xob lambdas the same way as static.
> 
> 
> I'm still concerned it wont cooperate with xobj parameters of unrelated
> type, but like I said, you've indicated my solution is definitely wrong
> so I'll look at fixing it.

I spent some time looking at it, I've decided you're probably right
that handling this the same way as the static lambda case is the best
in the short term. I still don't like it, but I've gone ahead and made
that change, and it seems to work just fine. I still find it icky, but
once I realized we do in fact need lambda_fntype since it might have
been substituted into in tsubst_lambda_expr, I don't see any better way
of doing this at the moment.

Since the added parameter just gets popped off by static_fn_type, and
tsubst_lambda_expr doesn't touch the xobj parameter, I'm pretty sure it
should behave properly. So no problems I guess, moving on to the
captures bug.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-28  3:31                                                                                                             ` waffl3x
@ 2023-11-28 10:00                                                                                                               ` waffl3x
  0 siblings, 0 replies; 100+ messages in thread
From: waffl3x @ 2023-11-28 10:00 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

This fixes the const bug. I haven't bootstrapped and tested beyond my
own tests yet but this does it. I don't know if this is the right way
to fix this yet, but I think it's pretty close. I'll see if I can make
a better write up tomorrow, but it seems to me that since we never
cared about substituting captures at function template instantiation
time there was never a special case added in here to add the const over
to the var_decl's type. I assume in cases where there is some kind of
type mismatch that it's usually handled before here, but this special
case throws a wrench into the works.

@@ -15402,6 +15450,8 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain,
                  gcc_checking_assert (TYPE_MAIN_VARIANT (TREE_TYPE (ve))
                                       == TYPE_MAIN_VARIANT (type));
                SET_DECL_VALUE_EXPR (r, ve);
+               if (is_capture_proxy (t))
+                 type = TREE_TYPE (ve);
              }
            if (CP_DECL_THREAD_LOCAL_P (r)
                && !processing_template_decl)

Sigh, that was really tough to track down, but it's honestly pretty
relieving that it was MOSTLY using the closure type and not totally
ignoring it. It just misses a step here for some reason, idk if this is
a larger bug that was benign before, or if this case just invalidates
some assumptions, I'm not really sure. I don't really have the energy
to fully understand everything that's going on in here tonight. This
fix is just good enough to bootstrap and run the tests overnight and
hopefully when I check in the morning there won't be any regressions.

Going to get some rest now, as long as the other maybe bugs I found are
not related to xobj parameters we can probably dust off this patch this
week. Although, maybe I should have learned by now that I'm terrible at
estimating timelines.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-27  5:35                                                                                                           ` [PATCH v6 " waffl3x
  2023-11-28  3:31                                                                                                             ` waffl3x
@ 2023-11-30  5:00                                                                                                             ` Jason Merrill
  2023-11-30  6:36                                                                                                               ` waffl3x
  1 sibling, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-30  5:00 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 5782 bytes --]

On 11/27/23 00:35, waffl3x wrote:
> I think this is cleaned up enough to be presentable. Bootstrapped but
> not tested but I don't think I changed anything substantial. I am
> running tests right now and will report if anything fails. I have not
> fixed the problem in tsubst_lambda_expr that we talked about, that will
> be first on my list tomorrow. While writing/cleaning up tests I had to
> investigate some things, one of which is calling an overloaded
> function, where one of the candidates are introduced by a using
> declaration, is considered ambiguous. I haven't narrowed down the case
> for this yet so I don't know if it's related to xobj member
> functions/lambda with xobj parameters or not. I had to pull a few tests
> because of it though.
> 
> I did not get as much done as I would have hoped today. This really
> just serves as a small progress update. Once again, todo is the issue
> you raised in tsubst_lambda_expr, and fixing handling of captures when
> a const xobj parameter is deduced in a lamdba call operator.

> +#define DECL_IOBJ_MEMBER_FUNC_P(NODE) \
> +#define DECL_XOBJ_MEMBER_FUNC_P(NODE)                  \
> +#define DECL_OBJECT_MEMBER_FUNC_P(NODE) \

Let's use the full word FUNCTION in these macros for consistency with 
DECL_STATIC_FUNCTION_P.

> @@ -6544,7 +6544,7 @@ 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))
> +      if (DECL_OBJECT_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

Hmm, that this function explicitly pulls out the object argument into 
first_arg strengthens your earlier argument that we shouldn't bother 
trying to handle null first_arg.  But let's not mess with that in this 
patch.

> +      val = handle_arg(TREE_VALUE (parm),

Missing space.

>           /* We know an iobj parameter must be a reference. If our xobj                                                             
>              parameter is a pointer, we know this is not a redeclaration.                                                           
>              This also catches array parameters, those are pointers too.  */
>           if (TYPE_PTR_P (xobj_param))
>             continue;

Handling pointers specifically here seems unnecessary, they should be 
rare and will be handled by the next check for unrelated type.

>              dealing with a by-value xobj parameter we can procede following                                                        

"proceed"

>    /* 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))
> +  /* This can just be DECL_STATIC_FUNCTION_P (decl) I think?  */
> +  if ((!methodp && !DECL_XOBJ_MEMBER_FUNC_P (decl))
> +      || DECL_STATIC_FUNCTION_P (decl))

No, it also needs to be true for non-members; rather, I think it can 
just be if (!DECL_OBJECT_FUNCTION_P (decl))

> +  if (xobj_param)
> +    {
> +      quals = TYPE_UNQUALIFIED;
> +      if (TYPE_REF_P (xobj_param)
> +         && !(cp_type_quals (TREE_TYPE (xobj_param)) & TYPE_QUAL_CONST))
> +        LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
> +    }

I don't think we want to mess with MUTABLE_P here.  But then 
capture_decltype needs to be fixed to refer to the type of the object 
parameter rather than MUTABLE_P.

Actually, I think we can do away with the MUTABLE_P flag entirely.  I'm 
going to push a patch to do that.

> -       if (!LAMBDA_EXPR_STATIC_P (lambda_expr))
> +       if (!LAMBDA_EXPR_STATIC_P (lambda_expr)
> +           && !DECL_XOBJ_MEMBER_FUNC_P (fco))

This could be if (DECL_IOBJ_MEMBER_FUNCTION_P (fco))

> -  if (closure && !DECL_STATIC_FUNCTION_P (t))
> +  if (closure && DECL_IOBJ_MEMBER_FUNC_P (t) && !DECL_STATIC_FUNCTION_P (t))

This shouldn't need to still check DECL_STATIC_FUNCTION_P.

> +  /* We don't touch a lambda's func when it's just trying to create the
> +     closure type.  */

We need to check it somewhere, currently this crashes:

template <class T> void f()
{
   int i;
   [=](this T&& self){ return i; }(); // error, unrelated
}
int main() { f<int>(); }

> @@ -3691,18 +3691,7 @@ 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)
> -    {
> -      fn = overload;
> -      if (op == ARRAY_REF)
> -       obj = va_arg (p, tree);
> -      for (int i = 0; i < nargs; i++)
> -       {
> -         tree arg = va_arg (p, tree);
> -         vec_safe_push (args, arg);
> -       }
> -    }

Maybe change the test to !DECL_OBJECT_MEMBER_FUNC_P to avoid reordering 
the cases?

> @@ -15402,6 +15450,8 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain,
>                   gcc_checking_assert (TYPE_MAIN_VARIANT (TREE_TYPE (ve))
>                                        == TYPE_MAIN_VARIANT (type));
>                 SET_DECL_VALUE_EXPR (r, ve);
> +               if (is_capture_proxy (t))
> +                 type = TREE_TYPE (ve);

That should have close to the same effect as the lambda_proxy_type 
adjustment I was talking about, since that function basically returns 
the TREE_TYPE of the COMPONENT_REF.  But the underlying problem is that 
finish_non_static_data_member assumes that 'object' is '*this', for 
which you can trust the cv-quals; for auto&&, you can't. 
capture_decltype has the same problem.  I'm attaching a patch to address 
this in both places.

Jason

[-- Attachment #2: wildcard.patch --]
[-- Type: text/x-patch, Size: 2113 bytes --]

commit 520217cc15315fe1cc58dff23c65bb3cdc28dc56
Author: Jason Merrill <jason@redhat.com>
Date:   Wed Nov 29 21:51:36 2023 -0500

    wildcard

diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 7946f0be77b..e0cb8a1b04f 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -2265,6 +2265,10 @@ finish_non_static_data_member (tree decl, tree object, tree qualifying_scope,
       else if (PACK_EXPANSION_P (type))
 	/* Don't bother trying to represent this.  */
 	type = NULL_TREE;
+      else if (WILDCARD_TYPE_P (TREE_TYPE (object)))
+	/* We don't know what the eventual quals will be.  This can happen when
+	   called from build_capture_proxy for an explicit object lambda.  */
+	type = NULL_TREE;
       else
 	{
 	  /* Set the cv qualifiers.  */
@@ -11709,6 +11713,7 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p,
      A<decltype(sizeof(T))>::U doesn't require 'typename'.  */
   if (instantiation_dependent_uneval_expression_p (expr))
     {
+    dependent:
       type = cxx_make_type (DECLTYPE_TYPE);
       DECLTYPE_TYPE_EXPR (type) = expr;
       DECLTYPE_TYPE_ID_EXPR_OR_MEMBER_ACCESS_P (type)
@@ -11883,7 +11888,11 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p,
       if (outer_automatic_var_p (STRIP_REFERENCE_REF (expr))
 	  && current_function_decl
 	  && LAMBDA_FUNCTION_P (current_function_decl))
-	type = capture_decltype (STRIP_REFERENCE_REF (expr));
+	{
+	  type = capture_decltype (STRIP_REFERENCE_REF (expr));
+	  if (!type)
+	    goto dependent;
+	}
       else if (error_operand_p (expr))
 	type = error_mark_node;
       else if (expr == current_class_ptr)
@@ -12821,7 +12830,8 @@ capture_decltype (tree decl)
     {
       int quals = cp_type_quals (type);
       tree obtype = TREE_TYPE (DECL_ARGUMENTS (current_function_decl));
-      gcc_checking_assert (!WILDCARD_TYPE_P (non_reference (obtype)));
+      if (WILDCARD_TYPE_P (non_reference (obtype)))
+	return NULL_TREE;
       if (INDIRECT_TYPE_P (obtype))
 	quals |= cp_type_quals (TREE_TYPE (obtype));
       type = cp_build_qualified_type (type, quals);

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-30  5:00                                                                                                             ` Jason Merrill
@ 2023-11-30  6:36                                                                                                               ` waffl3x
  2023-11-30 14:55                                                                                                                 ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-11-30  6:36 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches






On Wednesday, November 29th, 2023 at 10:00 PM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 11/27/23 00:35, waffl3x wrote:
> 
> > I think this is cleaned up enough to be presentable. Bootstrapped but
> > not tested but I don't think I changed anything substantial. I am
> > running tests right now and will report if anything fails. I have not
> > fixed the problem in tsubst_lambda_expr that we talked about, that will
> > be first on my list tomorrow. While writing/cleaning up tests I had to
> > investigate some things, one of which is calling an overloaded
> > function, where one of the candidates are introduced by a using
> > declaration, is considered ambiguous. I haven't narrowed down the case
> > for this yet so I don't know if it's related to xobj member
> > functions/lambda with xobj parameters or not. I had to pull a few tests
> > because of it though.
> > 
> > I did not get as much done as I would have hoped today. This really
> > just serves as a small progress update. Once again, todo is the issue
> > you raised in tsubst_lambda_expr, and fixing handling of captures when
> > a const xobj parameter is deduced in a lamdba call operator.
> 
> > +#define DECL_IOBJ_MEMBER_FUNC_P(NODE) \
> > +#define DECL_XOBJ_MEMBER_FUNC_P(NODE) \
> > +#define DECL_OBJECT_MEMBER_FUNC_P(NODE) \
> 
> 
> Let's use the full word FUNCTION in these macros for consistency with
> DECL_STATIC_FUNCTION_P.

Okay.

> > @@ -6544,7 +6544,7 @@ 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))
> > + if (DECL_OBJECT_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
> 
> 
> Hmm, that this function explicitly pulls out the object argument into
> first_arg strengthens your earlier argument that we shouldn't bother
> trying to handle null first_arg. But let's not mess with that in this
> patch.

Maybe I'll take some time to look into it when this patch is done, I
came across another block that seems to guarantee that first_arg gets
passed in a bit ago.

> > + val = handle_arg(TREE_VALUE (parm),
> 
> 
> Missing space.

Is there a script I can use for this so I'm not wasting your time on
little typos like this one?

> > /* We know an iobj parameter must be a reference. If our xobj
> > parameter is a pointer, we know this is not a redeclaration.
> > This also catches array parameters, those are pointers too. */
> > if (TYPE_PTR_P (xobj_param))
> > continue;
> 
> 
> Handling pointers specifically here seems unnecessary, they should be
> rare and will be handled by the next check for unrelated type.

Ah, it took me a second but I see it now, yeah I think I'll make this
change, with a comment that notes that it also handles the pointer case.

> > dealing with a by-value xobj parameter we can procede following
> 
> 
> "proceed"
> 
> > /* 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))
> > + / This can just be DECL_STATIC_FUNCTION_P (decl) I think? */
> > + if ((!methodp && !DECL_XOBJ_MEMBER_FUNC_P (decl))
> > + || DECL_STATIC_FUNCTION_P (decl))
> 
> 
> No, it also needs to be true for non-members; rather, I think it can
> just be if (!DECL_OBJECT_FUNCTION_P (decl))

Yeah that seems to make sense, I'll try that.

> > + if (xobj_param)
> > + {
> > + quals = TYPE_UNQUALIFIED;
> > + if (TYPE_REF_P (xobj_param)
> > + && !(cp_type_quals (TREE_TYPE (xobj_param)) & TYPE_QUAL_CONST))
> > + LAMBDA_EXPR_MUTABLE_P (lambda_expr) = 1;
> > + }
> 
> 
> I don't think we want to mess with MUTABLE_P here. But then
> capture_decltype needs to be fixed to refer to the type of the object
> parameter rather than MUTABLE_P.
> 
> Actually, I think we can do away with the MUTABLE_P flag entirely. I'm
> going to push a patch to do that.

I've been working on code that concerns it today and yesterday and I
was beginning to think the same thing. My current version doesn't set
it at all. I'm happy to see it removed.

When I looked into whether I should or should not be setting it I found
a new bug with decltype((capture)) in lambdas with default capture. I
am not going to try to fix the value capture default case but I was
able to fix the ref capture default case. Basically decltype((x)) is
not dependent right now as far as I can tell. Both MSVC and clang have
the same bug so I'm not worried about it tbh.

> > - if (!LAMBDA_EXPR_STATIC_P (lambda_expr))
> > + if (!LAMBDA_EXPR_STATIC_P (lambda_expr)
> > + && !DECL_XOBJ_MEMBER_FUNC_P (fco))
> 
> 
> This could be if (DECL_IOBJ_MEMBER_FUNCTION_P (fco))
> 
> > - if (closure && !DECL_STATIC_FUNCTION_P (t))
> > + if (closure && DECL_IOBJ_MEMBER_FUNC_P (t) && !DECL_STATIC_FUNCTION_P (t))
> 
> 
> This shouldn't need to still check DECL_STATIC_FUNCTION_P.

Yeah... not sure why I left that.

> > + /* We don't touch a lambda's func when it's just trying to create the
> > + closure type. */
> 
> 
> We need to check it somewhere, currently this crashes:
> 
> template <class T> void f()
> 
> {
> int i;
> [=](this T&& self){ return i; }(); // error, unrelated
> }
> int main() { f<int>(); }

Ah, yep you're right, how silly of me to forget that case. I'll make
sure to add it as a test case as well.

> > @@ -3691,18 +3691,7 @@ 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)
> > - {
> > - fn = overload;
> > - if (op == ARRAY_REF)
> > - obj = va_arg (p, tree);
> > - for (int i = 0; i < nargs; i++)
> > - {
> > - tree arg = va_arg (p, tree);
> > - vec_safe_push (args, arg);
> > - }
> > - }
> 
> 
> Maybe change the test to !DECL_OBJECT_MEMBER_FUNC_P to avoid reordering
> the cases?

I thought you had already given the thumbs up to this, but I'll take
another look at it and see what feels good.

> > @@ -15402,6 +15450,8 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain,
> > gcc_checking_assert (TYPE_MAIN_VARIANT (TREE_TYPE (ve))
> > == TYPE_MAIN_VARIANT (type));
> > SET_DECL_VALUE_EXPR (r, ve);
> > + if (is_capture_proxy (t))
> > + type = TREE_TYPE (ve);
> 
> 
> That should have close to the same effect as the lambda_proxy_type
> adjustment I was talking about, since that function basically returns
> the TREE_TYPE of the COMPONENT_REF. But the underlying problem is that
> finish_non_static_data_member assumes that 'object' is '*this', for
> which you can trust the cv-quals; for auto&&, you can't.
> capture_decltype has the same problem. I'm attaching a patch to address
> this in both places.
> 
> Jason

Ah perfect, you also noticed the decltype problem. I don't think I
could have fixed this one on my own so thanks for addressing it.

I started replacing old uses of DECL_NONSTATIC_MEMBER_FUNCTION_P in the
cases where I could tell for sure what the correct thing to do is and I
ended up finding a few bugs.

template<typename T>
concept is_int = __is_same(T, int);

struct S {
  static int f(is_int auto a) { return 5; };
  int f(this S&, auto a) { return 10; };
};

int main()
{
  S s{};
  return s.f(0);
}

This program returns 10 instead of 5.

https://godbolt.org/z/jMfbbaczr
Actually, I found that this bug exists in MSVC as well. Here it is in
godbolt with some extra things to make it more obvious where the bug
is. I've already found the cause of it, cand_parms_match doesn't ignore
the xobj parameter. It's an easy fix thankfully. I feel like these can
be unified because I'm pretty sure I've seen this in 2 or 3 places, but
I'll concern myself with trying to refactor it later.

I still have a case that I haven't fully narrowed down yet that
considers a call ambiguous when one of the candidates were introduce by
a using declaration. That's next on my list I believe.

I also found that xobj conversion operators were not working. I have
already fixed it but I need to write more robust tests for it to make
sure all the cases work. There's also a case where an xobj conversion
operator has an unrelated type for it's parameter and I haven't been
able to decide if it's ill-formed or not. I believe it would break
conversion sequence rules for a conversion operator to be selected.
Once I finish everything else I'll get a good robust test case for it.

This ended up longer than I thought. To recap, I have the bug with
constraints which should be fixed but needs tests, I have the wrongly
ambiguous overload bug that I need to narrow down and find, and I have
xobj conversion operators to write tests for. So after I address what
you said that's whats on my list. There's also integrating your patch
which I'm not totally sure how I'm supposed to do but I think I have a
good idea of it so I should be fine.

I feel like I'm forgetting something, but that often happens when I
stack up like 4 different things on my mind at once so it's probably
nothing. I'll get back to work. At this rate it almost feels like we
will be able to get this finished by the end of the week.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-30  6:36                                                                                                               ` waffl3x
@ 2023-11-30 14:55                                                                                                                 ` Jason Merrill
  2023-12-01  6:02                                                                                                                   ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-11-30 14:55 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 11/30/23 01:36, waffl3x wrote:
> On Wednesday, November 29th, 2023 at 10:00 PM, Jason Merrill <jason@redhat.com> wrote:
>> On 11/27/23 00:35, waffl3x wrote:
>>
>>> + val = handle_arg(TREE_VALUE (parm),
>>
>> Missing space.
> 
> Is there a script I can use for this so I'm not wasting your time on
> little typos like this one?

contrib/check_GNU_style.py seems like what you're looking for.

> This ended up longer than I thought. To recap, I have the bug with
> constraints which should be fixed but needs tests, I have the wrongly
> ambiguous overload bug that I need to narrow down and find, and I have
> xobj conversion operators to write tests for. So after I address what
> you said that's whats on my list. There's also integrating your patch
> which I'm not totally sure how I'm supposed to do but I think I have a
> good idea of it so I should be fine.

I think I'll push that patch as well so you can just rebase.

> I feel like I'm forgetting something, but that often happens when I
> stack up like 4 different things on my mind at once so it's probably
> nothing. I'll get back to work. At this rate it almost feels like we
> will be able to get this finished by the end of the week.

Sounds good.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-11-30 14:55                                                                                                                 ` Jason Merrill
@ 2023-12-01  6:02                                                                                                                   ` waffl3x
  2023-12-01 16:52                                                                                                                     ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-12-01  6:02 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

I ran into another issue while devising tests for redeclarations of
xobj member functions as static member functions and vice versa. I am
pretty sure by the literal wording of the standard, this is well formed.

template<typename T>
concept Constrain = true;

struct S {
  void f(this auto, Constrain auto) {};
  static void f(Constrain auto) {};

  void g(this auto const&, Constrain auto) {};
  static void g(Constrain auto) {};

  void h(this auto&&, Constrain auto) {};
  static void h(Constrain auto) {};
};

And also,

struct S{
  void f(this auto) {};
  static void f() {};

  void g(this auto const&) {};
  static void g() {};

  void h(this auto&&) {};
  static void h() {};
};

I wrote these tests expecting them to be ill-formed, and found what I
thought was a bug when they were not diagnosed as redecelarations.
However, given how the code for resolving overloads and determining
redeclarations looks, I believe this is actually well formed on a
technicality. I can't find the passages in the standard that specify
this so I can't be sure.

Anyway, the template parameter list differs because of the deduced
object parameter. Now here is the question, you are required to ignore
the object parameter when determining if these are redeclarations or
not, but what about the template parameters associated with the object
parameter? Am I just missing the passage that specifies this or is this
an actual defect in the standard?

The annoying thing is, even if this was brought up, I think the only
solution is to ratify these examples as well formed.

struct S {
  template<typename T>
  void f(this T, T) {}
  template<typename T>
  static void f(T) {}
};

Like what about this? If we ignore the template parameters associated
with the explicit object parameter, then the template parameter lists
don't match.

struct S {
  template<template<typename> typename Templ, typename T>
  void f(this Templ<T>, T) {}
  template<typename T>
  void f(T) {}
};

However, after writing them out and thinking about it a little, maybe
it really is just that simple. If after eliminating the template
parameters the explicit object parameter depends on the template
parameter lists are the same, then it's a redeclaration. Maybe this
works?

Am I overthinking this? Is there actually something specifying this
properly already? Hopefully that's the case but at the very least I
managed to write out this e-mail fairly quick for once so I didn't
waste too much time on this even if it does turn out to be nothing. The
majority of my time here was spent on the test case, which needed to be
written anyway.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-01  6:02                                                                                                                   ` waffl3x
@ 2023-12-01 16:52                                                                                                                     ` Jason Merrill
  2023-12-02  1:31                                                                                                                       ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-12-01 16:52 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 12/1/23 01:02, waffl3x wrote:
> I ran into another issue while devising tests for redeclarations of
> xobj member functions as static member functions and vice versa. I am
> pretty sure by the literal wording of the standard, this is well formed.
> 
> template<typename T>
> concept Constrain = true;
> 
> struct S {
>    void f(this auto, Constrain auto) {};
>    static void f(Constrain auto) {};
> 
>    void g(this auto const&, Constrain auto) {};
>    static void g(Constrain auto) {};
> 
>    void h(this auto&&, Constrain auto) {};
>    static void h(Constrain auto) {};
> };
> 
> And also,
> 
> struct S{
>    void f(this auto) {};
>    static void f() {};
> 
>    void g(this auto const&) {};
>    static void g() {};
> 
>    void h(this auto&&) {};
>    static void h() {};
> };
> 
> I wrote these tests expecting them to be ill-formed, and found what I
> thought was a bug when they were not diagnosed as redecelarations.
> However, given how the code for resolving overloads and determining
> redeclarations looks, I believe this is actually well formed on a
> technicality. I can't find the passages in the standard that specify
> this so I can't be sure.

I think the relevant section is
https://eel.is/c++draft/basic.scope.scope

> Anyway, the template parameter list differs because of the deduced
> object parameter. Now here is the question, you are required to ignore
> the object parameter when determining if these are redeclarations or
> not, but what about the template parameters associated with the object
> parameter? Am I just missing the passage that specifies this or is this
> an actual defect in the standard?

I think that since they differ in template parameters, they don't 
correspond under https://eel.is/c++draft/basic.scope.scope#4.5 so they 
can be overloaded.

This is specified in terms of the template-head grammar non-terminal, 
but elsewhere we say that abbreviated templates are equivalent to 
writing out the template parameters explicitly.

> The annoying thing is, even if this was brought up, I think the only
> solution is to ratify these examples as well formed.

Yes.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-01 16:52                                                                                                                     ` Jason Merrill
@ 2023-12-02  1:31                                                                                                                       ` waffl3x
  2023-12-02 15:02                                                                                                                         ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-12-02  1:31 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches






On Friday, December 1st, 2023 at 9:52 AM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 12/1/23 01:02, waffl3x wrote:
> 
> > I ran into another issue while devising tests for redeclarations of
> > xobj member functions as static member functions and vice versa. I am
> > pretty sure by the literal wording of the standard, this is well formed.
> > 
> > template<typename T>
> > concept Constrain = true;
> > 
> > struct S {
> > void f(this auto, Constrain auto) {};
> > static void f(Constrain auto) {};
> > 
> > void g(this auto const&, Constrain auto) {};
> > static void g(Constrain auto) {};
> > 
> > void h(this auto&&, Constrain auto) {};
> > static void h(Constrain auto) {};
> > };
> > 
> > And also,
> > 
> > struct S{
> > void f(this auto) {};
> > static void f() {};
> > 
> > void g(this auto const&) {};
> > static void g() {};
> > 
> > void h(this auto&&) {};
> > static void h() {};
> > };
> > 
> > I wrote these tests expecting them to be ill-formed, and found what I
> > thought was a bug when they were not diagnosed as redecelarations.
> > However, given how the code for resolving overloads and determining
> > redeclarations looks, I believe this is actually well formed on a
> > technicality. I can't find the passages in the standard that specify
> > this so I can't be sure.
> 
> 
> I think the relevant section is
> https://eel.is/c++draft/basic.scope.scope
> 
> > Anyway, the template parameter list differs because of the deduced
> > object parameter. Now here is the question, you are required to ignore
> > the object parameter when determining if these are redeclarations or
> > not, but what about the template parameters associated with the object
> > parameter? Am I just missing the passage that specifies this or is this
> > an actual defect in the standard?
> 
> 
> I think that since they differ in template parameters, they don't
> correspond under https://eel.is/c++draft/basic.scope.scope#4.5 so they
> can be overloaded.
> 
> This is specified in terms of the template-head grammar non-terminal,
> but elsewhere we say that abbreviated templates are equivalent to
> writing out the template parameters explicitly.
> 
> > The annoying thing is, even if this was brought up, I think the only
> > solution is to ratify these examples as well formed.
> 
> 
> Yes.
> 
> Jason

I can't get over that I feel like this goes against the spirit of the
specification. Just because an object argument is deduced should not
suddenly mean we take it into account. Too bad there's no good solution.

I especially don't like that that the following case is ambiguous. I
understand why, but I don't like it.

template<typename T>
concept Constrain = true;

struct S {
  int f(this auto, Constrain auto) {};
  static f(auto) {};
};
main() {
  S{}.f(0);
}

I would like to see this changed honestly. When an ambiguity is
encountered, the more constrained function should be taken into account
even if they normally can't be considered. Is there some pitfall with
this line of thinking that kept it out of the standard? Is it just a
case of "too hard to specify" or is there some reason it's impossible
to do in all but the simplest of cases?

Anyway while I do think this behavior is bad (not wrong according to
the standard, but bad imo), I recognize I don't have time to think
about it right now so I'll go back to working on the patch for the time
being.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-02  1:31                                                                                                                       ` waffl3x
@ 2023-12-02 15:02                                                                                                                         ` Jason Merrill
       [not found]                                                                                                                           ` <KQegse=5FguOyql4Ok1lrAgS7gasZJd1pOoPbCTdGxcHh-G4A9Tlf5zCnGJmqtshMt72edmcXdIapaZNPp4VJp5Ar9PHZbUrbwDsPjTSUrnOI=3D@protonmail.com>
  2023-12-05  4:35                                                                                                                           ` waffl3x
  0 siblings, 2 replies; 100+ messages in thread
From: Jason Merrill @ 2023-12-02 15:02 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 12/1/23 20:31, waffl3x wrote:
> On Friday, December 1st, 2023 at 9:52 AM, Jason Merrill <jason@redhat.com> wrote:
>> On 12/1/23 01:02, waffl3x wrote:
>>
>>> I ran into another issue while devising tests for redeclarations of
>>> xobj member functions as static member functions and vice versa. I am
>>> pretty sure by the literal wording of the standard, this is well formed.
>>>
>>> template<typename T>
>>> concept Constrain = true;
>>>
>>> struct S {
>>> void f(this auto, Constrain auto) {};
>>> static void f(Constrain auto) {};
>>>
>>> void g(this auto const&, Constrain auto) {};
>>> static void g(Constrain auto) {};
>>>
>>> void h(this auto&&, Constrain auto) {};
>>> static void h(Constrain auto) {};
>>> };
>>>
>>> And also,
>>>
>>> struct S{
>>> void f(this auto) {};
>>> static void f() {};
>>>
>>> void g(this auto const&) {};
>>> static void g() {};
>>>
>>> void h(this auto&&) {};
>>> static void h() {};
>>> };
>>>
>>> I wrote these tests expecting them to be ill-formed, and found what I
>>> thought was a bug when they were not diagnosed as redecelarations.
>>> However, given how the code for resolving overloads and determining
>>> redeclarations looks, I believe this is actually well formed on a
>>> technicality. I can't find the passages in the standard that specify
>>> this so I can't be sure.
>>
>>
>> I think the relevant section is
>> https://eel.is/c++draft/basic.scope.scope
>>
>>> Anyway, the template parameter list differs because of the deduced
>>> object parameter. Now here is the question, you are required to ignore
>>> the object parameter when determining if these are redeclarations or
>>> not, but what about the template parameters associated with the object
>>> parameter? Am I just missing the passage that specifies this or is this
>>> an actual defect in the standard?
>>
>>
>> I think that since they differ in template parameters, they don't
>> correspond under https://eel.is/c++draft/basic.scope.scope#4.5 so they
>> can be overloaded.
>>
>> This is specified in terms of the template-head grammar non-terminal,
>> but elsewhere we say that abbreviated templates are equivalent to
>> writing out the template parameters explicitly.
>>
>>> The annoying thing is, even if this was brought up, I think the only
>>> solution is to ratify these examples as well formed.
>>
>> Yes.
> 
> I can't get over that I feel like this goes against the spirit of the
> specification. Just because an object argument is deduced should not
> suddenly mean we take it into account. Too bad there's no good solution.

Yep.  Note that it's normal for a template to overload with a non-template:

struct A
{
   void f();
   template <class T> void f();  // OK
};

> I especially don't like that that the following case is ambiguous. I
> understand why, but I don't like it.
> 
> template<typename T>
> concept Constrain = true;
> 
> struct S {
>    int f(this auto, Constrain auto) {};
>    static f(auto) {};
> };
> main() {
>    S{}.f(0);
> }
>
> I would like to see this changed honestly. When an ambiguity is
> encountered, the more constrained function should be taken into account
> even if they normally can't be considered. Is there some pitfall with
> this line of thinking that kept it out of the standard? Is it just a
> case of "too hard to specify" or is there some reason it's impossible
> to do in all but the simplest of cases?

I would actually expect the static function to be chosen as more 
specialized before we get to considering constraints, just as with

void f(auto, Constrain auto) = delete;
void f(const S&, auto) {}
int main() { f(S{},0); } // OK

Though it looks like [temp.func.order] needs to be adjusted for explicit 
object parameters.  And more_specialized_fn in gcc still has an outdated 
handling of object parameters that just skips them, from before the 
clearer specification in C++11 and later; this is PR53499.  No need to 
address that preexisting bug in this patch.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-02 15:02                                                                                                                         ` Jason Merrill
       [not found]                                                                                                                           ` <KQegse=5FguOyql4Ok1lrAgS7gasZJd1pOoPbCTdGxcHh-G4A9Tlf5zCnGJmqtshMt72edmcXdIapaZNPp4VJp5Ar9PHZbUrbwDsPjTSUrnOI=3D@protonmail.com>
@ 2023-12-05  4:35                                                                                                                           ` waffl3x
  2023-12-05  4:39                                                                                                                             ` waffl3x
  1 sibling, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-12-05  4:35 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

>> @@ -15402,6 +15450,8 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain,
>>                   gcc_checking_assert (TYPE_MAIN_VARIANT (TREE_TYPE (ve))
>>                                        == TYPE_MAIN_VARIANT (type));
>>                 SET_DECL_VALUE_EXPR (r, ve);
>> +               if (is_capture_proxy (t))
>> +                 type = TREE_TYPE (ve);

>That should have close to the same effect as the lambda_proxy_type
>adjustment I was talking about, since that function basically returns
>the TREE_TYPE of the COMPONENT_REF.  But the underlying problem is that
>finish_non_static_data_member assumes that 'object' is '*this', for
>which you can trust the cv-quals; for auto&&, you can't.
>capture_decltype has the same problem.  I'm attaching a patch to address
>this in both places.

Regarding this, was my change actually okay, and was your change
supposed to address it? I applied my patch to the latest commit in
master yesterday and started tests and whatnot with this change
commented out as I wasn't sure. It seems like my tests for constness of
captures no longer works with or without this change commented out.

If you wish I can go over everything again and figure out a new
solution with your changes but stepping through all this code was quite
a task that I'm weary of doing again. Even if the second time through
won't be so arduous I would like to avoid it.

You know what, I'll give it a go anyway but I don't want to spend too
much time on it, I still have a few tests to clean up and this crash to
fix.

template <class T> void f()
{
   int i;
   [=](this T&& self){ return i; }(); // error, unrelated
}
int main() { f<int>(); }

If this crash doesn't take too long (I don't think it will, it seems
straightforward enough) then I'll look at fixing the captures with a
const xobject parameter bug the correct way.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-05  4:35                                                                                                                           ` waffl3x
@ 2023-12-05  4:39                                                                                                                             ` waffl3x
  2023-12-05  5:54                                                                                                                               ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-12-05  4:39 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches






On Monday, December 4th, 2023 at 9:35 PM, waffl3x <waffl3x@protonmail.com> wrote:


> 
> 
> >> @@ -15402,6 +15450,8 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain,
> 
> > > gcc_checking_assert (TYPE_MAIN_VARIANT (TREE_TYPE (ve))
> > > == TYPE_MAIN_VARIANT (type));
> > > SET_DECL_VALUE_EXPR (r, ve);
> > > + if (is_capture_proxy (t))
> > > + type = TREE_TYPE (ve);
> 
> > That should have close to the same effect as the lambda_proxy_type
> > adjustment I was talking about, since that function basically returns
> > the TREE_TYPE of the COMPONENT_REF. But the underlying problem is that
> > finish_non_static_data_member assumes that 'object' is '*this', for
> > which you can trust the cv-quals; for auto&&, you can't.
> > capture_decltype has the same problem. I'm attaching a patch to address
> > this in both places.
> 
> 
> Regarding this, was my change actually okay, and was your change
> supposed to address it? I applied my patch to the latest commit in
> master yesterday and started tests and whatnot with this change
> commented out as I wasn't sure. It seems like my tests for constness of
> captures no longer works with or without this change commented out.
> 
> If you wish I can go over everything again and figure out a new
> solution with your changes but stepping through all this code was quite
> a task that I'm weary of doing again. Even if the second time through
> won't be so arduous I would like to avoid it.
> 
> You know what, I'll give it a go anyway but I don't want to spend too
> much time on it, I still have a few tests to clean up and this crash to
> fix.
> 
> template <class T> void f()
> 
> {
> int i;
> [=](this T&& self){ return i; }(); // error, unrelated
> }
> int main() { f<int>(); }
> 
> 
> If this crash doesn't take too long (I don't think it will, it seems
> straightforward enough) then I'll look at fixing the captures with a
> const xobject parameter bug the correct way.
> 
> Alex

WAIT Scratch that, I made a mistake, there's only a single case that is
broken, I read the test log wrong. Ah, I swear I'm cursed to realize
things the moment I hit the send button.

I have to take a closer look, I'll get back to you when I know more,
just trying to make sure you don't waste your time on this due to my
mistake.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v6 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-05  4:39                                                                                                                             ` waffl3x
@ 2023-12-05  5:54                                                                                                                               ` waffl3x
  2023-12-06  7:33                                                                                                                                 ` [PATCH v7 " waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-12-05  5:54 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

On Monday, December 4th, 2023 at 9:39 PM, waffl3x <waffl3x@protonmail.com> wrote:

> On Monday, December 4th, 2023 at 9:35 PM, waffl3x waffl3x@protonmail.com wrote:
>
>
>
> > > > @@ -15402,6 +15450,8 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain,
> >
> > > > gcc_checking_assert (TYPE_MAIN_VARIANT (TREE_TYPE (ve))
> > > > == TYPE_MAIN_VARIANT (type));
> > > > SET_DECL_VALUE_EXPR (r, ve);
> > > > + if (is_capture_proxy (t))
> > > > + type = TREE_TYPE (ve);
> >
> > > That should have close to the same effect as the lambda_proxy_type
> > > adjustment I was talking about, since that function basically returns
> > > the TREE_TYPE of the COMPONENT_REF. But the underlying problem is that
> > > finish_non_static_data_member assumes that 'object' is '*this', for
> > > which you can trust the cv-quals; for auto&&, you can't.
> > > capture_decltype has the same problem. I'm attaching a patch to address
> > > this in both places.
> >
> > Regarding this, was my change actually okay, and was your change
> > supposed to address it? I applied my patch to the latest commit in
> > master yesterday and started tests and whatnot with this change
> > commented out as I wasn't sure. It seems like my tests for constness of
> > captures no longer works with or without this change commented out.
> >
> > If you wish I can go over everything again and figure out a new
> > solution with your changes but stepping through all this code was quite
> > a task that I'm weary of doing again. Even if the second time through
> > won't be so arduous I would like to avoid it.
> >
> > You know what, I'll give it a go anyway but I don't want to spend too
> > much time on it, I still have a few tests to clean up and this crash to
> > fix.
> >
> > template <class T> void f()
> >
> > {
> > int i;
> > [=](this T&& self){ return i; }(); // error, unrelated
> > }
> > int main() { f<int>(); }
> >
> > If this crash doesn't take too long (I don't think it will, it seems
> > straightforward enough) then I'll look at fixing the captures with a
> > const xobject parameter bug the correct way.
> >
> > Alex
>
>
> WAIT Scratch that, I made a mistake, there's only a single case that is
> broken, I read the test log wrong. Ah, I swear I'm cursed to realize
> things the moment I hit the send button.
>
> I have to take a closer look, I'll get back to you when I know more,
> just trying to make sure you don't waste your time on this due to my
> mistake.
>
> Alex

tl;dr it wasn't important, I just have to fix my test.

Okay that was faster than I anticipated, but unfortunately I don't know
how to handle it. I think your change in finish_non_static_data_member
might have been too heavy handed, but I don't know if there's a middle
ground. Or that's what I was going to say until I tested my assumption
on godbolt.

void f(auto const& a) { a = 5; }

Clang, MSVC and GCC all accept this until it is actually instantiated.

So, the true answer to my test failing is to just instantiate the
template. The test in question that was failing looks like this.

auto f2 = [n = 5](this auto const&){ n = 10; }; // { dg-error {} }

With the way things were before, this actually worked, so what my
assumption is now is that for us to actually diagnose this before a
template is instantiated would take some significant reworking of how
things are currently done. AND, I don't even know if it's legal for us
to make this diagnostic before instantiation for either of these cases.

Hah, come to think of it, we can't, there could be an overloaded
operator= that this is valid for... how disappointing.

We can for lambdas since the type is not dependent (on the lambda
instantiation) but it just isn't worth the effort I reckon.

Whatever, moving on, spending time on these things always drains me
because I think "oh boy I can do something better" and finding out it's
just not possible sucks. It's worse when it's because I overlooked
something that's obvious in hindsight.

Oh well, only that crash left I believe.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-05  5:54                                                                                                                               ` waffl3x
@ 2023-12-06  7:33                                                                                                                                 ` waffl3x
  2023-12-06  8:48                                                                                                                                   ` Jakub Jelinek
                                                                                                                                                     ` (2 more replies)
  0 siblings, 3 replies; 100+ messages in thread
From: waffl3x @ 2023-12-06  7:33 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

[-- Attachment #1: Type: text/plain, Size: 808 bytes --]

Here is the next version, it feels very close to finished. As before, I
haven't ran a bootstrap or the full testsuite yet but I did run the
explicit-obj tests which completed as expected.

There's a few test cases that still need to be written but more tests
can always be added. The behavior added by CWG2789 works in at least
one case, but I have not added tests for it yet. The test cases for
dependent lambda expressions need to be fleshed out more, but a few
temporary ones are included to demonstrate that they do work and that
the crash is fixed. Explicit object conversion functions work, but I
need to add fleshed out tests for them, explicit-obj-basic5.C has that
test.

I'll start the tests now and report back if anything fails, I'm
confident everything will be fine though.

Alex

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-P0847R7-Deducing-This-PR102609.patch --]
[-- Type: text/x-patch; name=0001-P0847R7-Deducing-This-PR102609.patch, Size: 277839 bytes --]

From 937e12c57145bfd878a0bc4cd9735c2d3c4fcf22 Mon Sep 17 00:00:00 2001
From: Waffl3x <waffl3x@protonmail.com>
Date: Tue, 5 Dec 2023 23:16:01 -0700
Subject: [PATCH] P0847R7 (Deducing This) [PR102609] Another quick and dirty
 patch for review, hopefully the last. gcc/cp/ChangeLog:

	* call.cc (build_this_conversion):
	(add_function_candidate):
	(add_template_candidate_real):
	(add_candidates):
	(build_over_call):
	(cand_parms_match):
	* class.cc (add_method):
	(resolve_address_of_overloaded_function):
	* cp-tree.h (struct lang_decl_fn):
	(DECL_IOBJ_MEMBER_FUNCTION_P):
	(DECL_FUNCTION_XOBJ_FLAG):
	(DECL_XOBJ_MEMBER_FUNCTION_P):
	(DECL_OBJECT_MEMBER_FUNCTION_P):
	(DECL_FUNCTION_MEMBER_P):
	(enum auto_deduction_context):
	(TFF_XOBJ_FUNC):
	(enum cp_decl_spec):
	* decl.cc (grokfndecl):
	(grokdeclarator):
	(grok_special_member_properties):
	(grok_op_properties):
	(start_preparsed_function):
	* error.cc (dump_aggr_type):
	(dump_lambda_function):
	(dump_function_decl):
	(dump_parameters):
	(function_category):
	* lambda.cc (build_capture_proxy):
	(lambda_expr_this_capture):
	(maybe_generic_this_capture):
	* mangle.cc (write_nested_name):
	* method.cc (skip_artificial_parms_for):
	* module.cc (trees_out::lang_decl_bools):
	(trees_in::lang_decl_bools):
	* parser.cc (cp_parser_lambda_declarator_opt):
	(cp_parser_decl_specifier_seq):
	(cp_parser_parameter_declaration):
	(set_and_check_decl_spec_loc):
	* pt.cc (tsubst_function_decl):
	(tsubst_lambda_expr):
	(more_specialized_fn):
	* search.cc (protected_accessible_p):
	(look_for_overrides_here):
	(look_for_overrides_r):
	* semantics.cc (finish_this_expr):
	(capture_decltype):
	* tree.cc (build_min_non_dep_op_overload):
	* typeck.cc (cxx_alignas_expr):
	(invalid_nonstatic_memfn_p):
	(cp_build_addr_expr_1):

gcc/testsuite/ChangeLog:

	* g++.dg/cpp23/explicit-obj-basic1.C: New test.
	* g++.dg/cpp23/explicit-obj-basic2.C: New test.
	* g++.dg/cpp23/explicit-obj-basic3.C: New test.
	* g++.dg/cpp23/explicit-obj-basic4.C: New test.
	* g++.dg/cpp23/explicit-obj-basic5.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-constraints.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-A.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-B.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-C.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-D.C: New test.
	* g++.dg/cpp23/explicit-obj-cxx-dialect-E.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics1.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics2.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics4.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics5.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics6.C: New test.
	* g++.dg/cpp23/explicit-obj-diagnostics7.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda-temp1.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda-temp2.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda1.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda2.C: New test.
	* g++.dg/cpp23/explicit-obj-lambda3.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX0.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX20.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX21.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX24.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX25.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX4.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX40.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX5.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX6.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX60.C: New test.
	* g++.dg/cpp23/explicit-obj-lambdaX7.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.
	* g++.dg/cpp23/explicit-obj-redecl-constraints.C: New test.
	* g++.dg/cpp23/explicit-obj-redecl.C: New test.
	* g++.dg/cpp23/explicit-obj-redecl2.C: New test.
	* g++.dg/cpp23/explicit-obj-redecl3.C: New test.
	* g++.dg/cpp23/explicit-obj-virtual.C: New test.

Signed-off-by: Waffl3x <waffl3x@protonmail.com>
---
 gcc/cp/call.cc                                | 187 ++++---
 gcc/cp/class.cc                               | 202 ++++++-
 gcc/cp/cp-tree.h                              |  39 +-
 gcc/cp/decl.cc                                | 184 ++++++-
 gcc/cp/error.cc                               |  24 +-
 gcc/cp/lambda.cc                              |   9 +-
 gcc/cp/mangle.cc                              |   4 +-
 gcc/cp/method.cc                              |   2 +-
 gcc/cp/module.cc                              |   2 +
 gcc/cp/parser.cc                              | 147 +++++-
 gcc/cp/pt.cc                                  |  78 ++-
 gcc/cp/search.cc                              |  16 +-
 gcc/cp/semantics.cc                           |  35 +-
 gcc/cp/tree.cc                                |  10 +-
 gcc/cp/typeck.cc                              |  36 +-
 .../g++.dg/cpp23/explicit-obj-basic1.C        | 114 ++++
 .../g++.dg/cpp23/explicit-obj-basic2.C        |  28 +
 .../g++.dg/cpp23/explicit-obj-basic3.C        | 496 ++++++++++++++++++
 .../g++.dg/cpp23/explicit-obj-basic4.C        | 113 ++++
 .../g++.dg/cpp23/explicit-obj-basic5.C        |  31 ++
 .../g++.dg/cpp23/explicit-obj-by-value1.C     |  48 ++
 .../g++.dg/cpp23/explicit-obj-by-value2.C     |  58 ++
 .../g++.dg/cpp23/explicit-obj-by-value3.C     |  41 ++
 .../g++.dg/cpp23/explicit-obj-by-value4.C     |  20 +
 .../g++.dg/cpp23/explicit-obj-constraints.C   | 470 +++++++++++++++++
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-A.C |   7 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-B.C |   7 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-C.C |   9 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-D.C |   8 +
 .../g++.dg/cpp23/explicit-obj-cxx-dialect-E.C |   8 +
 .../g++.dg/cpp23/explicit-obj-diagnostics1.C  | 139 +++++
 .../g++.dg/cpp23/explicit-obj-diagnostics2.C  |  26 +
 .../g++.dg/cpp23/explicit-obj-diagnostics4.C  |  20 +
 .../g++.dg/cpp23/explicit-obj-diagnostics5.C  |  16 +
 .../g++.dg/cpp23/explicit-obj-diagnostics6.C  |  23 +
 .../g++.dg/cpp23/explicit-obj-diagnostics7.C  |  17 +
 .../g++.dg/cpp23/explicit-obj-lambda-temp1.C  |  73 +++
 .../g++.dg/cpp23/explicit-obj-lambda-temp2.C  |  38 ++
 .../g++.dg/cpp23/explicit-obj-lambda1.C       |  25 +
 .../g++.dg/cpp23/explicit-obj-lambda2.C       |  23 +
 .../g++.dg/cpp23/explicit-obj-lambda3.C       |  64 +++
 .../g++.dg/cpp23/explicit-obj-lambdaX0.C      |  68 +++
 .../g++.dg/cpp23/explicit-obj-lambdaX20.C     |  46 ++
 .../g++.dg/cpp23/explicit-obj-lambdaX21.C     |  39 ++
 .../g++.dg/cpp23/explicit-obj-lambdaX24.C     |  98 ++++
 .../g++.dg/cpp23/explicit-obj-lambdaX25.C     | 101 ++++
 .../g++.dg/cpp23/explicit-obj-lambdaX4.C      |  23 +
 .../g++.dg/cpp23/explicit-obj-lambdaX40.C     |  20 +
 .../g++.dg/cpp23/explicit-obj-lambdaX5.C      |  21 +
 .../g++.dg/cpp23/explicit-obj-lambdaX6.C      |  48 ++
 .../g++.dg/cpp23/explicit-obj-lambdaX60.C     | 279 ++++++++++
 .../g++.dg/cpp23/explicit-obj-lambdaX7.C      |  87 +++
 .../g++.dg/cpp23/explicit-obj-ops-mem-arrow.C |  28 +
 .../cpp23/explicit-obj-ops-mem-assignment.C   |  27 +
 .../g++.dg/cpp23/explicit-obj-ops-mem-call.C  |  40 ++
 .../cpp23/explicit-obj-ops-mem-subscript.C    |  40 ++
 .../cpp23/explicit-obj-ops-non-mem-dep.C      |  58 ++
 .../cpp23/explicit-obj-ops-non-mem-non-dep.C  |  57 ++
 .../g++.dg/cpp23/explicit-obj-ops-non-mem.h   | 210 ++++++++
 .../cpp23/explicit-obj-ops-requires-mem.C     | 171 ++++++
 .../cpp23/explicit-obj-ops-requires-non-mem.C | 237 +++++++++
 .../cpp23/explicit-obj-redecl-constraints.C   | 115 ++++
 .../g++.dg/cpp23/explicit-obj-redecl.C        | 246 +++++++++
 .../g++.dg/cpp23/explicit-obj-redecl2.C       | 161 ++++++
 .../g++.dg/cpp23/explicit-obj-redecl3.C       | 263 ++++++++++
 .../g++.dg/cpp23/explicit-obj-virtual.C       |  95 ++++
 66 files changed, 5314 insertions(+), 161 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-basic3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-basic5.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-constraints.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-A.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda-temp1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda-temp2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX0.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX20.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX21.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX24.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX25.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX40.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX5.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX60.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX7.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
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl-constraints.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-virtual.C

diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index ae0decd87f1..fa772344ea9 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -2277,7 +2277,7 @@ build_this_conversion (tree fn, tree ctype,
 		       tree& parmtype, tree& argtype, tree& arg,
 		       int flags, tsubst_flags_t complain)
 {
-  gcc_assert (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+  gcc_assert (DECL_IOBJ_MEMBER_FUNCTION_P (fn)
 	      && !DECL_CONSTRUCTOR_P (fn));
 
   /* The type of the implicit object parameter ('this') for
@@ -2490,7 +2490,7 @@ add_function_candidate (struct z_candidate **candidates,
 	{
 	  tree parmtype = TREE_VALUE (parmnode);
 	  if (i == 0
-	      && DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+	      && DECL_IOBJ_MEMBER_FUNCTION_P (fn)
 	      && !DECL_CONSTRUCTOR_P (fn))
 	    t = build_this_conversion (fn, ctype, parmtype, argtype, arg,
 				       flags, complain);
@@ -3447,7 +3447,7 @@ add_template_candidate_real (struct z_candidate **candidates, tree tmpl,
 
   /* We don't do deduction on the in-charge parameter, the VTT
      parameter or 'this'.  */
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (tmpl))
+  if (DECL_IOBJ_MEMBER_FUNCTION_P (tmpl))
     {
       if (first_arg_without_in_chrg != NULL_TREE)
 	first_arg_without_in_chrg = NULL_TREE;
@@ -3557,7 +3557,7 @@ add_template_candidate_real (struct z_candidate **candidates, tree tmpl,
       convs = alloc_conversions (nargs);
 
       if (shortcut_bad_convs
-	  && DECL_NONSTATIC_MEMBER_FUNCTION_P (tmpl)
+	  && DECL_IOBJ_MEMBER_FUNCTION_P (tmpl)
 	  && !DECL_CONSTRUCTOR_P (tmpl))
 	{
 	  /* Check the 'this' conversion before proceeding with deduction.
@@ -6544,7 +6544,7 @@ 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))
+      if (DECL_OBJECT_MEMBER_FUNCTION_P (fn))
 	{
 	  /* Figure out where the object arg comes from.  If this
 	     function is a non-static member and we didn't get an
@@ -6586,6 +6586,17 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> *args,
 	      /* ??? This kludge excludes inline namespace members for the H
 		 test in spaceship-eq15.C, but I don't see why we would want
 		 that behavior.  Asked Core 2022-11-04.  Disabling for now.  */;
+	    /* FIXME: I believe this will be bugged for xobj member functions,
+	       leaving this comment here to make sure we look into it
+	       at some point.
+	       Seeing this makes me want correspondence checking to be unified
+	       in one place though, not sure if this one needs to be different
+	       from other ones though.
+	       This function is only used here, but maybe we can use it in add
+	       method and move some of the logic out of there?
+
+	       Side note: CWG2586 might be relevant for this area in
+	       particular, perhaps we wait to see if it gets accepted first?  */
 	    else if (fns_correspond (fn, *ne))
 	      {
 		found = true;
@@ -9762,14 +9773,9 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
   const vec<tree, va_gc> *args = cand->args;
   tree first_arg = cand->first_arg;
   conversion **convs = cand->convs;
-  conversion *conv;
   tree parm = TYPE_ARG_TYPES (TREE_TYPE (fn));
   int parmlen;
   tree val;
-  int i = 0;
-  int j = 0;
-  unsigned int arg_index = 0;
-  int is_method = 0;
   int nargs;
   tree *argarray;
   bool already_used = false;
@@ -9955,45 +9961,46 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
   if (immediate_invocation_p (STRIP_TEMPLATE (fn)))
     in_consteval_if_p = true;
 
+  int argarray_size = 0;
+  unsigned int arg_index = 0;
+  int conv_index = 0;
+  int param_index = 0;
+
+  auto consume_object_arg = [&arg_index, &first_arg, args]()
+    {
+      if (!first_arg)
+	return (*args)[arg_index++];
+      tree object_arg = first_arg;
+      first_arg = NULL_TREE;
+      return object_arg;
+    };
+
   /* The implicit parameters to a constructor are not considered by overload
      resolution, and must be of the proper type.  */
   if (DECL_CONSTRUCTOR_P (fn))
     {
-      tree object_arg;
-      if (first_arg != NULL_TREE)
-	{
-	  object_arg = first_arg;
-	  first_arg = NULL_TREE;
-	}
-      else
-	{
-	  object_arg = (*args)[arg_index];
-	  ++arg_index;
-	}
-      argarray[j++] = build_this (object_arg);
+      tree object_arg = consume_object_arg ();
+      argarray[argarray_size++] = build_this (object_arg);
       parm = TREE_CHAIN (parm);
       /* We should never try to call the abstract constructor.  */
       gcc_assert (!DECL_HAS_IN_CHARGE_PARM_P (fn));
 
       if (DECL_HAS_VTT_PARM_P (fn))
 	{
-	  argarray[j++] = (*args)[arg_index];
+	  argarray[argarray_size++] = (*args)[arg_index];
 	  ++arg_index;
 	  parm = TREE_CHAIN (parm);
 	}
     }
   /* Bypass access control for 'this' parameter.  */
-  else if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE)
+  else if (DECL_IOBJ_MEMBER_FUNCTION_P (fn))
     {
-      tree arg = build_this (first_arg != NULL_TREE
-			     ? first_arg
-			     : (*args)[arg_index]);
+      tree arg = build_this (consume_object_arg ());
       tree argtype = TREE_TYPE (arg);
 
       if (arg == error_mark_node)
 	return error_mark_node;
-
-      if (convs[i]->bad_p)
+      if (convs[conv_index++]->bad_p)
 	{
 	  if (complain & tf_error)
 	    {
@@ -10068,25 +10075,57 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
       tree converted_arg = build_base_path (PLUS_EXPR, arg,
 					    base_binfo, 1, complain);
 
-      argarray[j++] = converted_arg;
+      argarray[argarray_size++] = converted_arg;
       parm = TREE_CHAIN (parm);
-      if (first_arg != NULL_TREE)
-	first_arg = NULL_TREE;
+    }
+
+  auto handle_arg = [fn, flags, complain](tree type,
+  					  tree arg,
+					  int const param_index,
+					  conversion *conv,
+					  bool const conversion_warning)
+    {
+      /* Set user_conv_p on the argument conversions, so rvalue/base handling
+	 knows not to allow any more UDCs.  This needs to happen after we
+	 process cand->warnings.  */
+      if (flags & LOOKUP_NO_CONVERSION)
+	conv->user_conv_p = true;
+
+      tsubst_flags_t const arg_complain
+	= conversion_warning ? complain : complain & ~tf_warning;
+
+      if (arg_complain & tf_warning)
+	maybe_warn_pessimizing_move (arg, type, /*return_p=*/false);
+
+      tree val = convert_like_with_context (conv, arg, fn,
+      					    param_index, arg_complain);
+      val = convert_for_arg_passing (type, val, arg_complain);
+      return val;
+    };
+
+  if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
+    {
+      gcc_assert (cand->num_convs > 0);
+      static constexpr bool conversion_warning = true;
+      tree object_arg = consume_object_arg ();
+      val = handle_arg (TREE_VALUE (parm),
+			object_arg,
+			param_index++,
+			convs[conv_index++],
+			conversion_warning);
+
+      if (val == error_mark_node)
+	return error_mark_node;
       else
-	++arg_index;
-      ++i;
-      is_method = 1;
+	argarray[argarray_size++] = val;
+      parm = TREE_CHAIN (parm);
     }
 
   gcc_assert (first_arg == NULL_TREE);
   for (; arg_index < vec_safe_length (args) && parm;
-       parm = TREE_CHAIN (parm), ++arg_index, ++i)
+       parm = TREE_CHAIN (parm), ++arg_index, ++param_index, ++conv_index)
     {
-      tree type = TREE_VALUE (parm);
-      tree arg = (*args)[arg_index];
-      bool conversion_warning = true;
-
-      conv = convs[i];
+      tree current_arg = (*args)[arg_index];
 
       /* If the argument is NULL and used to (implicitly) instantiate a
          template function (and bind one of the template arguments to
@@ -10108,47 +10147,35 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
              func(NULL);
            }
       */
-      if (null_node_p (arg)
-          && DECL_TEMPLATE_INFO (fn)
-          && cand->template_decl
-	  && !cand->explicit_targs)
-        conversion_warning = false;
+      bool const conversion_warning = !(null_node_p (current_arg)
+					&& DECL_TEMPLATE_INFO (fn)
+					&& cand->template_decl
+					&& !cand->explicit_targs);
 
-      /* Set user_conv_p on the argument conversions, so rvalue/base handling
-	 knows not to allow any more UDCs.  This needs to happen after we
-	 process cand->warnings.  */
-      if (flags & LOOKUP_NO_CONVERSION)
-	conv->user_conv_p = true;
-
-      tsubst_flags_t arg_complain = complain;
-      if (!conversion_warning)
-	arg_complain &= ~tf_warning;
-
-      if (arg_complain & tf_warning)
-	maybe_warn_pessimizing_move (arg, type, /*return_p*/false);
-
-      val = convert_like_with_context (conv, arg, fn, i - is_method,
-				       arg_complain);
-      val = convert_for_arg_passing (type, val, arg_complain);
+      val = handle_arg (TREE_VALUE (parm),
+			current_arg,
+			param_index,
+			convs[conv_index],
+			conversion_warning);
 
       if (val == error_mark_node)
-        return error_mark_node;
+	return error_mark_node;
       else
-        argarray[j++] = val;
+	argarray[argarray_size++] = val;
     }
 
   /* Default arguments */
-  for (; parm && parm != void_list_node; parm = TREE_CHAIN (parm), i++)
+  for (; parm && parm != void_list_node; parm = TREE_CHAIN (parm), param_index++)
     {
       if (TREE_VALUE (parm) == error_mark_node)
 	return error_mark_node;
       val = convert_default_arg (TREE_VALUE (parm),
 				 TREE_PURPOSE (parm),
-				 fn, i - is_method,
+				 fn, param_index,
 				 complain);
       if (val == error_mark_node)
-        return error_mark_node;
-      argarray[j++] = val;
+	return error_mark_node;
+      argarray[argarray_size++] = val;
     }
 
   /* Ellipsis */
@@ -10185,11 +10212,11 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
 	a = convert_arg_to_ellipsis (a, complain);
       if (a == error_mark_node)
 	return error_mark_node;
-      argarray[j++] = a;
+      argarray[argarray_size++] = a;
     }
 
-  gcc_assert (j <= nargs);
-  nargs = j;
+  gcc_assert (argarray_size <= nargs);
+  nargs = argarray_size;
   icip.reset ();
 
   /* Avoid performing argument transformation if warnings are disabled.
@@ -10205,7 +10232,7 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
     {
       tree *fargs = (!nargs ? argarray
 			    : (tree *) alloca (nargs * sizeof (tree)));
-      for (j = 0; j < nargs; j++)
+      for (int j = 0; j < nargs; j++)
 	{
 	  /* For -Wformat undo the implicit passing by hidden reference
 	     done by convert_arg_to_ellipsis.  */
@@ -12574,17 +12601,25 @@ cand_parms_match (z_candidate *c1, z_candidate *c2)
       fn1 = DECL_TEMPLATE_RESULT (t1);
       fn2 = DECL_TEMPLATE_RESULT (t2);
     }
+  /* The changes I made here might be stuff I was told not to worry about?
+     I'm not really sure so I'm going to leave it in.  */
   tree parms1 = TYPE_ARG_TYPES (TREE_TYPE (fn1));
   tree parms2 = TYPE_ARG_TYPES (TREE_TYPE (fn2));
   if (DECL_FUNCTION_MEMBER_P (fn1)
       && DECL_FUNCTION_MEMBER_P (fn2)
-      && (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn1)
-	  != DECL_NONSTATIC_MEMBER_FUNCTION_P (fn2)))
+      && (DECL_STATIC_FUNCTION_P (fn1)
+	  != DECL_STATIC_FUNCTION_P (fn2)))
     {
       /* Ignore 'this' when comparing the parameters of a static member
 	 function with those of a non-static one.  */
-      parms1 = skip_artificial_parms_for (fn1, parms1);
-      parms2 = skip_artificial_parms_for (fn2, parms2);
+      auto skip_parms = [](tree fn, tree parms){
+	  if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
+	    return TREE_CHAIN (parms);
+	  else
+	    return skip_artificial_parms_for (fn, parms);
+	};
+      parms1 = skip_parms (fn1, parms1);
+      parms2 = skip_parms (fn2, parms2);
     }
   return compparms (parms1, parms2);
 }
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index 6fdb56abfb9..d7a4ad5ad51 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -1079,8 +1079,8 @@ add_method (tree type, tree method, bool via_using)
       /* Compare the quals on the 'this' parm.  Don't compare
 	 the whole types, as used functions are treated as
 	 coming from the using class in overload resolution.  */
-      if (! DECL_STATIC_FUNCTION_P (fn)
-	  && ! DECL_STATIC_FUNCTION_P (method)
+      if (DECL_IOBJ_MEMBER_FUNCTION_P (fn)
+	  && DECL_IOBJ_MEMBER_FUNCTION_P (method)
 	  /* Either both or neither need to be ref-qualified for
 	     differing quals to allow overloading.  */
 	  && (FUNCTION_REF_QUALIFIED (fn_type)
@@ -1089,6 +1089,161 @@ add_method (tree type, tree method, bool via_using)
 	      || type_memfn_rqual (fn_type) != type_memfn_rqual (method_type)))
 	  continue;
 
+      auto get_object_param = [](tree fn)
+	{
+	  gcc_assert (DECL_OBJECT_MEMBER_FUNCTION_P (fn));
+	  return TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (fn)));
+	};
+      auto reference_qual = [](tree ref)
+	{
+	  gcc_assert (TYPE_REF_P (ref));
+	  return TYPE_REF_IS_RVALUE (ref) ? REF_QUAL_RVALUE
+					  : REF_QUAL_LVALUE;
+	};
+
+      /* Handle special correspondence rules for xobj vs xobj and xobj vs iobj
+	 member function declarations.
+	 We don't worry about static member functions here.  */
+      if ((!DECL_XOBJ_MEMBER_FUNCTION_P (fn)
+	   && !DECL_XOBJ_MEMBER_FUNCTION_P (method))
+	  || DECL_STATIC_FUNCTION_P (fn) || DECL_STATIC_FUNCTION_P (method))
+	/* Early escape.  */;
+      else if (DECL_XOBJ_MEMBER_FUNCTION_P (fn)
+	       && DECL_XOBJ_MEMBER_FUNCTION_P (method))
+	{
+	  tree fn_param = get_object_param (fn);
+	  tree method_param = get_object_param (method);
+	  if (!same_type_p (fn_param, method_param))
+	    continue;
+	}
+      else if (DECL_XOBJ_MEMBER_FUNCTION_P (fn)
+	       || DECL_XOBJ_MEMBER_FUNCTION_P (method))
+	{
+	  tree xobj_fn = DECL_XOBJ_MEMBER_FUNCTION_P (fn) ? fn : method;
+	  /* A reference, pointer, or something else.  */
+	  tree xobj_param = get_object_param (xobj_fn);
+
+	  /* An iobj member function's object parameter can't be an unrelated
+	     type, we know this can't be a redeclaration if the xobj member
+	     function's object parameter is an unrelated type.
+	     If the iobj member function was introduced with a using
+	     declaration, then the type of its object parameter is still
+	     that of the class we are currently adding a member function to,
+	     so this assumption holds true in that case as well.
+
+	     [over.match.funcs.general.4]
+	     For non-conversion functions that are implicit object member
+	     functions nominated by a using-declaration in a derived class,
+	     the function is considered to be a member of the derived class
+	     for the purpose of defining the type of the implicit object
+	     parameter.
+
+	     We don't get to bail yet out even if the xobj parameter is
+	     by-value as elaborated on below.
+
+	     This also implicitly handles xobj parameters of type pointer.  */
+	  if (DECL_CONTEXT (xobj_fn)
+	      != TYPE_MAIN_VARIANT (non_reference (xobj_param)))
+	    continue;
+
+	  /* From this point on, we know we are dealing with an xobj parameter
+	     that has an object parameter of the same type as the class it
+	     was declared in.
+	     We still don't know if we have a reference or by-value parameter
+	     yet though.  */
+
+	  tree iobj_fn = DECL_IOBJ_MEMBER_FUNCTION_P (fn) ? fn : method;
+	  tree iobj_fn_type = TREE_TYPE (iobj_fn);
+	  cp_ref_qualifier const iobj_ref_qual
+	    = type_memfn_rqual (iobj_fn_type);
+	  /* I am concerned about the other qualifier bits, for now I will mask
+	     them off.  */
+	  static constexpr cp_cv_quals cv_bits = TYPE_QUAL_VOLATILE
+					       | TYPE_QUAL_CONST;
+	  cp_cv_quals const iobj_cv_quals
+	    = type_memfn_quals (iobj_fn_type) & cv_bits;
+	  /* We need to ignore the ref qualifier of the xobj parameter if the
+	     iobj member function lacks a ref qualifier.
+
+	     [basic.scope.scope.3]
+	     Two non-static member functions have corresponding object
+	     parameters if:
+	     -- exactly one is an implicit object member function with no
+		ref-qualifier and the types of their object parameters
+		([dcl.fct]), after removing top-level references, are the
+		same, or
+	     -- their object parameters have the same type.
+
+	     The cv qualifiers of a by-value parameter are supposed to be
+	     discarded, so we ignore them.
+
+	     [dcl.fct.5]
+	     After producing the list of parameter types, any top-level
+	     cv-qualifiers modifying a parameter type are deleted when
+	     forming the function type.
+
+	     They still need to be taken into account when our xobj parameter
+	     is a reference that is being ignored (according to
+	     [basic.scope.scope.3] quoted above), but when we are actually
+	     dealing with a by-value xobj parameter we can proceed following
+	     this table.
+	     | iobj | xobj | equal |
+	     | none | none |   X   |
+	     | none |    c |   X   |
+	     | none |    v |   X   |
+	     | none |   cv |   X   |
+	     |    c | none |   O   |
+	     |    c |    c |   O   |
+	     |    c |    v |   O   |
+	     |    c |   cv |   O   |
+	     |    v | none |   O   |
+	     |    v |    c |   O   |
+	     |    v |    v |   O   |
+	     |    v |   cv |   O   |
+	     |   cv | none |   O   |
+	     |   cv |    c |   O   |
+	     |   cv |    v |   O   |
+	     |   cv |   cv |   O   |
+
+	     Additionally, if the iobj member function is ref qualified,
+	     we aren't ignoring the ref qualifier of the iobj parameter,
+	     so we can't be dealing with a redeclaration in that case either.
+
+	     So to recap, if we have a by-value xobj parameter, we know for
+	     sure that we aren't dealing with a redeclaration if the iobj
+	     member function has any cv-ref qualifiers.  The only case where
+	     we might still be dealing with a redeclaration is when the iobj
+	     member function lacks any cv-ref qualification.  */
+	  if (!TYPE_REF_P (xobj_param))
+	    {
+	      if (iobj_ref_qual || iobj_cv_quals)
+		continue;
+	    }
+	  else
+	    {
+	      /* We are dealing with an xobj parameter that is a reference now,
+		 but due to [basic.scope.scope.3] we need to ignore its
+		 reference qualifier.  */
+	      cp_ref_qualifier const xobj_ref_qual
+		= !TYPE_REF_P (xobj_param) || !iobj_ref_qual
+		  ? REF_QUAL_NONE : reference_qual (xobj_param);
+
+	      /* Even if we are ignoring the reference qualifier, the xobj
+		 parameter was still a reference so we still take the cv
+		 qualifiers into account.  */
+	      cp_cv_quals const xobj_cv_quals
+		= cp_type_quals (TREE_TYPE (xobj_param)) & cv_bits;
+
+	      /* Finally, if the qualifications don't match exactly, this
+		 definitely isn't a redeclaration.  */
+	      if (iobj_ref_qual != xobj_ref_qual
+		  || iobj_cv_quals != xobj_cv_quals)
+		continue;
+	    }
+	}
+      else
+	gcc_unreachable ();
+
       tree real_fn = fn;
       tree real_method = method;
 
@@ -8727,21 +8882,42 @@ resolve_address_of_overloaded_function (tree target_type,
   /* Good, exactly one match.  Now, convert it to the correct type.  */
   fn = TREE_PURPOSE (matches);
 
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
-      && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
+  if (DECL_OBJECT_MEMBER_FUNCTION_P (fn)
+      && !(complain & tf_ptrmem_ok))
     {
-      static int explained;
-
-      if (!(complain & tf_error))
+      /* For iobj member functions, if if -fms_extensions was passed in, this
+	 is not an error, so we do nothing.  It is still an error regardless
+	 for xobj member functions though, as it is a new feature we
+	 (hopefully) don't need to support the behavior.  */
+      if (DECL_IOBJ_MEMBER_FUNCTION_P (fn) && flag_ms_extensions)
+	/* Early escape.  */;
+      else if (!(complain & tf_error))
 	return error_mark_node;
-
-      auto_diagnostic_group d;
-      if (permerror (input_location, "assuming pointer to member %qD", fn)
-	  && !explained)
+      else if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
 	{
-	  inform (input_location, "(a pointer to member can only be "
+	  auto_diagnostic_group d;
+	  /* Error message could be better, fixit would be ideal.
+	     Should also match the error in typeck.cc:cp_build_addr_expr_1.  */
+	  error_at (input_location,
+		    "taking the address of an explicit object member "
+		    "function must be qualified");
+	  inform (input_location,
+		  "(a pointer to explicit object member function can only be "
 		  "formed with %<&%E%>)", fn);
-	  explained = 1;
+	}
+      else
+	{
+	  static int explained;
+	  gcc_assert (DECL_IOBJ_MEMBER_FUNCTION_P (fn) && !flag_ms_extensions);
+
+	  auto_diagnostic_group d;
+	  if (permerror (input_location, "assuming pointer to member %qD", fn)
+	      && !explained)
+	    {
+	      inform (input_location, "(a pointer to member can only be "
+				      "formed with %<&%E%>)", fn);
+	      explained = 1;
+	    }
 	}
     }
 
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 9979c5da623..f118f16df0d 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2946,8 +2946,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.  */
 
@@ -3346,14 +3347,34 @@ 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, use DECL_IOBJ_MEMBER_FUNCTION_P instead.  */
 #define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
   (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
 
+/* Nonzero for FUNCTION_DECL means that this decl is an implicit object
+   member function.  */
+#define DECL_IOBJ_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 an xobj member function,
+   safely evaluates to false for all non FUNCTION_DECL nodes.  */
+#define DECL_XOBJ_MEMBER_FUNCTION_P(NODE)		\
+  (TREE_CODE (STRIP_TEMPLATE (NODE)) == FUNCTION_DECL	\
+   && DECL_FUNCTION_XOBJ_FLAG (NODE) == 1)
+
+/* Nonzero if NODE is a member function with an object argument,
+   in other words, a non-static member function.  */
+#define DECL_OBJECT_MEMBER_FUNCTION_P(NODE) \
+  (DECL_IOBJ_MEMBER_FUNCTION_P (NODE) || DECL_XOBJ_MEMBER_FUNCTION_P (NODE))
+
 /* 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_OBJECT_MEMBER_FUNCTION_P (NODE) || DECL_STATIC_FUNCTION_P (NODE)) \
 
 /* Nonzero for FUNCTION_DECL means that this member function
    has `this' as const X *const.  */
@@ -6137,7 +6158,9 @@ enum auto_deduction_context
        identical to their defaults.
    TFF_NO_TEMPLATE_BINDINGS: do not print information about the template
        arguments for a function template specialization.
-   TFF_POINTER: we are printing a pointer type.  */
+   TFF_POINTER: we are printing a pointer type.
+   TFF_XOBJ_FUNC: we are printing an explicit object member function's
+       parameters.  */
 
 #define TFF_PLAIN_IDENTIFIER			(0)
 #define TFF_SCOPE				(1)
@@ -6155,6 +6178,7 @@ enum auto_deduction_context
 #define TFF_NO_OMIT_DEFAULT_TEMPLATE_ARGUMENTS	(1 << 12)
 #define TFF_NO_TEMPLATE_BINDINGS		(1 << 13)
 #define TFF_POINTER		                (1 << 14)
+#define TFF_XOBJ_FUNC				(1 << 15)
 
 /* These constants can be used as bit flags to control strip_typedefs.
 
@@ -6297,11 +6321,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,
@@ -6318,6 +6344,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 4b685270097..e51ede5d043 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -10385,6 +10385,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,
@@ -10394,7 +10395,6 @@ grokfndecl (tree ctype,
 	    location_t location)
 {
   tree decl;
-  int staticp = ctype && TREE_CODE (type) == FUNCTION_TYPE;
   tree t;
 
   if (location == UNKNOWN_LOCATION)
@@ -10592,12 +10592,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;
@@ -10677,24 +10674,30 @@ grokfndecl (tree ctype,
 	TREE_TYPE (decl) = apply_memfn_quals (TREE_TYPE (decl),
 					      TYPE_UNQUALIFIED,
 					      REF_QUAL_NONE);
-
+      auto_diagnostic_group d;
       if (quals)
-	{
-	  error (ctype
+	error (!ctype
+	       ? G_("non-member function %qD cannot have cv-qualifier")
+	       : !xobj_func_p
 		 ? G_("static member function %qD cannot have cv-qualifier")
-		 : G_("non-member function %qD cannot have cv-qualifier"),
-		 decl);
-	  quals = TYPE_UNQUALIFIED;
-	}
-
+		 : G_("explicit object member function "
+		      "%qD cannot have cv-qualifier"),
+	       decl);
       if (rqual)
-	{
-	  error (ctype
+	error (!ctype
+	       ? G_("non-member function %qD cannot have ref-qualifier")
+	       : !xobj_func_p
 		 ? G_("static member function %qD cannot have ref-qualifier")
-		 : G_("non-member function %qD cannot have ref-qualifier"),
+		 : G_("explicit object member function "
+		      "%qD cannot have ref-qualifier"),
 		 decl);
-	  rqual = REF_QUAL_NONE;
-	}
+
+      if (xobj_func_p && (quals || rqual))
+	inform (DECL_SOURCE_LOCATION (DECL_ARGUMENTS (decl)),
+		"explicit object parameter declared here");
+      quals = TYPE_UNQUALIFIED;
+      rqual = REF_QUAL_NONE;
+
     }
 
   if (deduction_guide_p (decl))
@@ -13066,6 +13069,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)
@@ -13181,6 +13186,91 @@ 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 || TREE_PURPOSE (parm_list) != this_identifier)
+		  return NULL_TREE;
+		/* 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 TREE_VALUE (parm_list);
+	      };
+
+	    tree xobj_parm
+	      = find_xobj_parm (declarator->u.function.parameters);
+	    is_xobj_member_function = xobj_parm;
+
+	    if (xobj_parm && cxx_dialect < cxx23)
+	      pedwarn (DECL_SOURCE_LOCATION (xobj_parm), OPT_Wc__23_extensions,
+		       "explicit object member function only available "
+		       "with %<-std=c++23%> or %<-std=gnu++23%>");
+
+	    if (xobj_parm && decl_context == TYPENAME)
+	      {
+		/* We inform in every case, just differently depending on what
+		   case it is.  */
+		auto_diagnostic_group d;
+		bool ptr_type = true;
+		/* If declarator->kind is cdk_function and we are at the end of
+		   the declarator chain, we are looking at a function type.  */
+		if (!declarator->declarator)
+		  {
+		    error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			      "a function type cannot "
+			      "have an explicit object parameter");
+		    ptr_type = false;
+		  }
+		else if (declarator->declarator->kind == cdk_pointer)
+		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			    "a pointer to function type cannot "
+			    "have an explicit object parameter");
+		else if (declarator->declarator->kind == cdk_ptrmem)
+		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			    "a pointer to member function type "
+			    "cannot have an explicit object parameter");
+		else
+		  gcc_unreachable ();
+
+		/* The locations being used here are probably not correct.  */
+		if (ptr_type)
+		  inform (DECL_SOURCE_LOCATION (xobj_parm),
+			  "the type of a pointer to explicit object member "
+			  "function is a regular pointer to function type");
+		else
+		  inform (DECL_SOURCE_LOCATION (xobj_parm),
+			  "the type of an explicit object "
+			  "member function is a regular function type");
+		/* Ideally we should synthesize the correct syntax
+		   for the user, perhaps this could be added later.  */
+	      }
+	    /* Since a valid xobj parm has its purpose cleared in find_xobj_parm
+	       the first parm node will never erroneously be detected here.  */
+	    {
+	      auto_diagnostic_group d;
+	      bool bad_xobj_parm_encountered = false;
+	      for (tree parm = declarator->u.function.parameters;
+		   parm && parm != void_list_node;
+		   parm = TREE_CHAIN (parm))
+		{
+		  if (TREE_PURPOSE (parm) != this_identifier)
+		    continue;
+		  bad_xobj_parm_encountered = true;
+		  gcc_rich_location bad_xobj_parm
+		    (DECL_SOURCE_LOCATION (TREE_VALUE (parm)));
+		  /* I'm keeping it more basic for now.  */
+		  error_at (&bad_xobj_parm,
+			  "Only the first parameter of a member function "
+			  "can be declared as an explicit object parameter");
+		}
+	      if (bad_xobj_parm_encountered && xobj_parm)
+		inform (DECL_SOURCE_LOCATION (xobj_parm),
+			"Valid explicit object parameter declared here");
+	    }
+
 	    if (reqs)
 	      error_at (location_of (reqs), "requires-clause on return type");
 	    reqs = declarator->u.function.requires_clause;
@@ -13468,6 +13558,38 @@ grokdeclarator (const cp_declarator *declarator,
 		  explicitp = 2;
 	      }
 
+	    if (xobj_parm)
+	      {
+		if (!ctype
+		    && decl_context == NORMAL
+		    && (in_namespace
+			|| !declarator->declarator->u.id.qualifying_scope))
+		  error_at (DECL_SOURCE_LOCATION (xobj_parm),
+			    "a non-member function cannot have "
+			    "an explicit object parameter");
+		else
+		  {
+		    if (virtualp)
+		      {
+			auto_diagnostic_group d;
+			error_at (declspecs->locations[ds_virtual],
+				  "an explicit object member function cannot "
+				  "be %<virtual%>");
+			inform (DECL_SOURCE_LOCATION (xobj_parm),
+				"explicit object parameter declared here");
+			virtualp = false;
+		      }
+		    if (staticp >= 2)
+		      {
+			auto_diagnostic_group d;
+			error_at (declspecs->locations[ds_storage_class],
+				  "an explicit object member function cannot "
+				  "be %<static%>");
+			inform (DECL_SOURCE_LOCATION (xobj_parm),
+				"explicit object parameter declared here");
+		      }
+		  }
+	      }
 	    tree pushed_scope = NULL_TREE;
 	    if (funcdecl_p
 		&& decl_context != FIELD
@@ -14245,6 +14367,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)))
@@ -14466,7 +14590,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);
@@ -14801,8 +14926,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);
@@ -15502,9 +15627,10 @@ void
 grok_special_member_properties (tree decl)
 {
   tree class_type;
-
+  /* I believe we have to make some changes in here depending on the outcome
+     of CWG2586.  */
   if (TREE_CODE (decl) == USING_DECL
-      || !DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
+      || !DECL_OBJECT_MEMBER_FUNCTION_P (decl))
     return;
 
   class_type = DECL_CONTEXT (decl);
@@ -15603,7 +15729,7 @@ bool
 grok_op_properties (tree decl, bool complain)
 {
   tree argtypes = TYPE_ARG_TYPES (TREE_TYPE (decl));
-  bool methodp = TREE_CODE (TREE_TYPE (decl)) == METHOD_TYPE;
+  bool const methodp = DECL_IOBJ_MEMBER_FUNCTION_P (decl);
   tree name = DECL_NAME (decl);
   location_t loc = DECL_SOURCE_LOCATION (decl);
 
@@ -15696,7 +15822,7 @@ 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 (!DECL_OBJECT_MEMBER_FUNCTION_P (decl))
     {
       if (operator_code == TYPE_EXPR
 	  || operator_code == COMPONENT_REF
@@ -15785,7 +15911,7 @@ grok_op_properties (tree decl, bool complain)
 	}
       ++arity;
     }
-
+  /* FIXME: We need tests for these errors with xobj member functions.  */
   /* Verify correct number of arguments.  */
   switch (op_flags)
     {
@@ -17694,7 +17820,7 @@ start_preparsed_function (tree decl1, tree attrs, int flags)
   /* Start the statement-tree, start the tree now.  */
   DECL_SAVED_TREE (decl1) = push_stmt_list ();
 
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl1))
+  if (DECL_IOBJ_MEMBER_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
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index 785909c362a..b105eb6ddcf 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -838,10 +838,14 @@ dump_aggr_type (cxx_pretty_printer *pp, tree t, int flags)
     {
       /* A lambda's "type" is essentially its signature.  */
       pp_string (pp, M_("<lambda"));
-      if (lambda_function (t))
-	dump_parameters (pp,
-                         FUNCTION_FIRST_USER_PARMTYPE (lambda_function (t)),
-			 flags);
+      tree const fn = lambda_function (t);
+      if (fn)
+	{
+	  int const parm_flags
+	    = DECL_XOBJ_MEMBER_FUNCTION_P (fn) ? TFF_XOBJ_FUNC | flags
+					       : flags;
+	  dump_parameters (pp, FUNCTION_FIRST_USER_PARMTYPE (fn), parm_flags);
+	}
       pp_greater (pp);
     }
   else if (!decl || IDENTIFIER_ANON_P (DECL_NAME (decl)))
@@ -1710,7 +1714,9 @@ dump_lambda_function (cxx_pretty_printer *pp,
 {
   /* A lambda's signature is essentially its "type".  */
   dump_type (pp, DECL_CONTEXT (fn), flags);
-  if (TREE_CODE (TREE_TYPE (fn)) == FUNCTION_TYPE)
+  if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
+    /* Early escape.  */;
+  else if (TREE_CODE (TREE_TYPE (fn)) == FUNCTION_TYPE)
     {
       pp->padding = pp_before;
       pp_c_ws_string (pp, "static");
@@ -1831,7 +1837,9 @@ dump_function_decl (cxx_pretty_printer *pp, tree t, int flags)
 
   if (!(flags & TFF_NO_FUNCTION_ARGUMENTS))
     {
-      dump_parameters (pp, parmtypes, flags);
+      int const parm_flags
+	= DECL_XOBJ_MEMBER_FUNCTION_P (t) ? TFF_XOBJ_FUNC | flags : flags;
+      dump_parameters (pp, parmtypes, parm_flags);
 
       if (TREE_CODE (fntype) == METHOD_TYPE)
 	{
@@ -1910,6 +1918,8 @@ dump_parameters (cxx_pretty_printer *pp, tree parmtypes, int flags)
   for (first = 1; parmtypes != void_list_node;
        parmtypes = TREE_CHAIN (parmtypes))
     {
+      if (first && flags & TFF_XOBJ_FUNC)
+	pp_string (pp, "this ");
       if (!first)
 	pp_separate_with_comma (pp);
       first = 0;
@@ -3685,6 +3695,8 @@ function_category (tree fn)
 	return _("In destructor %qD");
       else if (LAMBDA_FUNCTION_P (fn))
 	return _("In lambda function");
+      else if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
+	return _("In explicit object member function %qD");
       else
 	return _("In member function %qD");
     }
diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
index 5990a6de736..8baf82578c7 100644
--- a/gcc/cp/lambda.cc
+++ b/gcc/cp/lambda.cc
@@ -404,8 +404,10 @@ build_capture_proxy (tree member, tree init)
   fn = lambda_function (closure);
   lam = CLASSTYPE_LAMBDA_EXPR (closure);
 
+  object = DECL_ARGUMENTS (fn);
   /* The proxy variable forwards to the capture field.  */
-  object = build_fold_indirect_ref (DECL_ARGUMENTS (fn));
+  if (INDIRECT_TYPE_P (TREE_TYPE (object)))
+    object = build_fold_indirect_ref (object);
   object = finish_non_static_data_member (member, object, NULL_TREE);
   if (REFERENCE_REF_P (object))
     object = TREE_OPERAND (object, 0);
@@ -835,7 +837,8 @@ lambda_expr_this_capture (tree lambda, int add_capture_p)
 	  if (!LAMBDA_FUNCTION_P (containing_function))
 	    {
 	      /* We found a non-lambda function.  */
-	      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (containing_function))
+	      /* There is no this pointer in xobj member functions.  */
+	      if (DECL_IOBJ_MEMBER_FUNCTION_P (containing_function))
 		/* First parameter is 'this'.  */
 		init = DECL_ARGUMENTS (containing_function);
 	      break;
@@ -969,7 +972,7 @@ maybe_generic_this_capture (tree object, tree fns)
 	for (lkp_iterator iter (fns); iter; ++iter)
 	  if (((!id_expr && TREE_CODE (*iter) != USING_DECL)
 	       || TREE_CODE (*iter) == TEMPLATE_DECL)
-	      && DECL_NONSTATIC_MEMBER_FUNCTION_P (*iter))
+	      && DECL_IOBJ_MEMBER_FUNCTION_P (*iter))
 	    {
 	      /* Found a non-static member.  Capture this.  */
 	      lambda_expr_this_capture (lam, /*maybe*/-1);
diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc
index 0684f0e6038..84294f10a42 100644
--- a/gcc/cp/mangle.cc
+++ b/gcc/cp/mangle.cc
@@ -1221,9 +1221,9 @@ write_nested_name (const tree decl)
 
   write_char ('N');
 
-  /* Write CV-qualifiers, if this is a member function.  */
+  /* Write CV-qualifiers, if this is an iobj member function.  */
   if (TREE_CODE (decl) == FUNCTION_DECL
-      && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
+      && DECL_IOBJ_MEMBER_FUNCTION_P (decl))
     {
       if (DECL_VOLATILE_MEMFUNC_P (decl))
 	write_char ('V');
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index a70dd5d6adc..ca3102a57a5 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -3607,7 +3607,7 @@ lazily_declare_fn (special_function_kind sfk, tree type)
 tree
 skip_artificial_parms_for (const_tree fn, tree list)
 {
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn))
+  if (DECL_IOBJ_MEMBER_FUNCTION_P (fn))
     list = TREE_CHAIN (list);
   else
     return list;
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 33fcf396875..bae96680291 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5683,6 +5683,7 @@ trees_out::lang_decl_bools (tree t)
       WB (lang->u.fn.has_dependent_explicit_spec_p);
       WB (lang->u.fn.immediate_fn_p);
       WB (lang->u.fn.maybe_deleted);
+      WB (lang->u.fn.xobj_func);
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
@@ -5751,6 +5752,7 @@ trees_in::lang_decl_bools (tree t)
       RB (lang->u.fn.has_dependent_explicit_spec_p);
       RB (lang->u.fn.immediate_fn_p);
       RB (lang->u.fn.maybe_deleted);
+      RB (lang->u.fn.xobj_func);
       goto lds_min;
 
     case lds_decomp:  /* lang_decl_decomp.  */
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 8d911b8cb02..93d4b1f7484 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -11754,8 +11754,16 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
   else if (cxx_dialect < cxx23)
     omitted_parms_loc = cp_lexer_peek_token (parser->lexer)->location;
 
+  /* Review note: I figured I might as well update the comments since I'm here.
+     There are also some additions to the below.  */
   /* In the decl-specifier-seq of the lambda-declarator, each
      decl-specifier shall either be mutable or constexpr.  */
+  /* [expr.prim.lambda.general]
+     lambda-specifier:
+	consteval, constexpr, mutable, static
+     [4] A lambda-specifier-seq shall contain at most one of each
+	 lambda-specifier and shall not contain both constexpr and consteval.
+	 The lambda-specifier-seq shall not contain both mutable and static.  */
   int declares_class_or_enum;
   if (cp_lexer_next_token_is_decl_specifier_keyword (parser->lexer))
     cp_parser_decl_specifier_seq (parser,
@@ -11770,19 +11778,88 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
 	       "%<-std=gnu++2b%>");
       omitted_parms_loc = UNKNOWN_LOCATION;
     }
-
-  if (lambda_specs.storage_class == sc_mutable)
+  /* Peek at the params, see if we have an xobj parameter.  */
+  if (param_list && TREE_PURPOSE (param_list) == this_identifier)
+    {
+      quals = TYPE_UNQUALIFIED;
+      /* We still need grokdeclarator to see that this is an xobj function
+	 and finish the rest of the work, don't mutate it.  */
+      tree const xobj_param = TREE_VALUE (param_list);
+      tree const param_type = TREE_TYPE (xobj_param);
+      /* [expr.prim.lambda.closure-5]
+	 Given a lambda with a lambda-capture, the type of the explicit object
+	 parameter, if any, of the lambda's function call operator (possibly
+	 instantiated from a function call operator template) shall be either:
+	 -- the closure type,
+	 -- a class type derived from the closure type, or
+	 -- a reference to a possibly cv-qualified such type.  */
+      if ((LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE
+	   || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr))
+	  /* Since a lambda's type is anonymous, we can assume an xobj
+	     parameter is unrelated to the closure if it is non-dependent.
+	     If it is dependent we handle it at instantiation time.  */
+	  && !dependent_type_p (non_reference (param_type)))
+	{
+	  error_at (DECL_SOURCE_LOCATION (xobj_param),
+		    "a lambda with captures may not have an explicit object "
+		    "parameter of an unrelated type");
+	  LAMBDA_EXPR_CAPTURE_LIST (lambda_expr) = NULL_TREE;
+	}
+
+      /* [expr.prim.lambda.general-4]
+	 If the lambda-declarator contains an explicit object parameter
+	 ([dcl.fct]), then no lambda-specifier in the lambda-specifier-seq
+	 shall be mutable or static.  */
+      if (lambda_specs.storage_class == sc_mutable)
+	{
+	  auto_diagnostic_group d;
+	  error_at (lambda_specs.locations[ds_storage_class],
+		    "%<mutable%> lambda specifier "
+		    "with explicit object parameter");
+	  /* Tell the user how to do what they probably meant, maybe fixits
+	     would be apropriate later?  */
+	  if (!dependent_type_p (non_reference (param_type)))
+	    /* If we are given a non-dependent type we will have already given
+	       a diagnosis that the following would contradict with.  */;
+	  else if (!TYPE_REF_P (param_type))
+	    inform (DECL_SOURCE_LOCATION (xobj_param),
+		    "the passed in closure object will not be mutated because "
+		    "it is taken by copy/move");
+	  else if (TYPE_READONLY (TREE_TYPE (param_type)))
+	    inform (DECL_SOURCE_LOCATION (xobj_param),
+		    "declare the explicit object parameter as non-const "
+		    "reference instead");
+	  else
+	    inform (DECL_SOURCE_LOCATION (xobj_param),
+		    "explicit object parameter is already a mutable "
+		    "reference");
+	}
+      else if (lambda_specs.storage_class == sc_static)
+	{
+	  auto_diagnostic_group d;
+	  error_at (lambda_specs.locations[ds_storage_class],
+		    "%<static%> lambda specifier "
+		    "with explicit object parameter");
+	  inform (DECL_SOURCE_LOCATION (xobj_param),
+		  "explicit object parameter declared here");
+	}
+    }
+  else if (lambda_specs.storage_class == sc_mutable)
     {
       quals = TYPE_UNQUALIFIED;
     }
   else if (lambda_specs.storage_class == sc_static)
     {
+      /* [expr.prim.lambda.general-4]
+	 If the lambda-specifier-seq contains static, there shall be no
+	 lambda-capture.  */
       if (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE
 	  || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr))
 	error_at (lambda_specs.locations[ds_storage_class],
 		  "%<static%> lambda specifier with lambda capture");
       else
 	{
+	  /* We can remove this too, I will address it in another patch.  */
 	  LAMBDA_EXPR_STATIC_P (lambda_expr) = 1;
 	  quals = TYPE_UNQUALIFIED;
 	}
@@ -11901,7 +11978,7 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr)
       {
 	DECL_INITIALIZED_IN_CLASS_P (fco) = 1;
 	DECL_ARTIFICIAL (fco) = 1;
-	if (!LAMBDA_EXPR_STATIC_P (lambda_expr))
+	if (DECL_IOBJ_MEMBER_FUNCTION_P (fco))
 	  /* Give the object parameter a different name.  */
 	  DECL_NAME (DECL_ARGUMENTS (fco)) = closure_identifier;
 	DECL_SET_LAMBDA_FUNCTION (fco, true);
@@ -16023,6 +16100,8 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
   /* Assume no class or enumeration type is declared.  */
   *declares_class_or_enum = 0;
 
+  /* Keep a token that additionally will be used for diagnostics.  */
+  cp_token *first_specifier = NULL;
   /* Keep reading specifiers until there are no more to read.  */
   while (true)
     {
@@ -16095,6 +16174,40 @@ cp_parser_decl_specifier_seq (cp_parser* parser,
 	    decl_specs->locations[ds_attribute] = token->location;
 	  continue;
 	}
+      /* We know by this point that the token is not part of an attribute.  */
+      if (!first_specifier)
+	first_specifier = token;
+      /* 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);
+	  if (token != first_specifier)
+	    {
+	      /* Don't emit diagnostics if we have already seen "this",
+		 leave it for set_and_check_decl_spec_loc.  */
+	      if (decl_specs->locations[ds_this] == 0)
+		{
+		  auto_diagnostic_group d;
+		  gcc_rich_location richloc (token->location);
+		  /* Ideally we synthesize a full rewrite, at the moment
+		     there are issues with it though.
+		     It rewrites "f(S this & s)" correctly, but fails
+		     to rewrite "f(const this S s)" correctly.  It also
+		     does not handle "f(S& this s)" correctly at all.  */
+		  richloc.add_fixit_remove ();
+		  richloc.add_fixit_insert_before (first_specifier->location,
+						   "this ");
+		  error_at (&richloc,
+			    "%<this%> must be the first specifier "
+			    "in a parameter declaration");
+		}
+	    }
+	  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
@@ -25463,12 +25576,14 @@ cp_parser_parameter_declaration (cp_parser *parser,
   /* The restriction on defining new types applies only to the type
      of the parameter, not to the default argument.  */
   parser->type_definition_forbidden_message = saved_message;
-
+  cp_token *eq_token = NULL;
   /* If the next token is `=', then process a default argument.  */
   if (cp_lexer_next_token_is (parser->lexer, CPP_EQ))
     {
       tree type = decl_specifiers.type;
       token = cp_lexer_peek_token (parser->lexer);
+      /* Used for diagnostics with an xobj parameter.  */
+      eq_token = token;
       if (declarator)
 	declarator->init_loc = token->location;
       /* If we are defining a class, then the tokens that make up the
@@ -25537,6 +25652,25 @@ 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))
+    {
+      if (default_argument)
+	{
+	  /* If there is a default_argument, eq_token should always be set.  */
+	  gcc_assert (eq_token);
+	  location_t param_with_init_loc
+	    = make_location (eq_token->location,
+			     decl_spec_token_start->location,
+			     input_location);
+	  error_at (param_with_init_loc,
+		    "an explicit object parameter "
+		    "may not have a default argument");
+	}
+      /* 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
@@ -33898,6 +34032,8 @@ set_and_check_decl_spec_loc (cp_decl_specifier_seq *decl_specs,
 	}
       else
 	{
+	  /* These correspond to cp-tree.h:cp_decl_spec,
+	     changes here should also be reflected there.  */
 	  static const char *const decl_spec_names[] = {
 	    "signed",
 	    "unsigned",
@@ -33915,7 +34051,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/pt.cc b/gcc/cp/pt.cc
index d12d57686f2..7473ed4a41f 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -14391,9 +14391,9 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
   tree ctx = closure ? closure : DECL_CONTEXT (t);
   bool member = ctx && TYPE_P (ctx);
 
-  /* If this is a static lambda, remove the 'this' pointer added in
+  /* If this is a static or xobj lambda, remove the 'this' pointer added in
      tsubst_lambda_expr now that we know the closure type.  */
-  if (lambda_fntype && DECL_STATIC_FUNCTION_P (t))
+  if (lambda_fntype && !DECL_IOBJ_MEMBER_FUNCTION_P (t))
     lambda_fntype = static_fn_type (lambda_fntype);
 
   if (member && !closure)
@@ -14468,12 +14468,12 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
     DECL_NAME (r) = make_conv_op_name (TREE_TYPE (type));
 
   tree parms = DECL_ARGUMENTS (t);
-  if (closure && !DECL_STATIC_FUNCTION_P (t))
+  if (closure && DECL_IOBJ_MEMBER_FUNCTION_P (t))
     parms = DECL_CHAIN (parms);
   parms = tsubst (parms, args, complain, t);
   for (tree parm = parms; parm; parm = DECL_CHAIN (parm))
     DECL_CONTEXT (parm) = r;
-  if (closure && !DECL_STATIC_FUNCTION_P (t))
+  if (closure && DECL_IOBJ_MEMBER_FUNCTION_P (t))
     {
       tree tparm = build_this_parm (r, closure, type_memfn_quals (type));
       DECL_NAME (tparm) = closure_identifier;
@@ -14509,6 +14509,66 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain,
       && !grok_op_properties (r, /*complain=*/false))
     return error_mark_node;
 
+  /* If we are looking at an xobj lambda, we might need to check the type of
+     its xobj parameter.  */
+  if (LAMBDA_FUNCTION_P (r) && DECL_XOBJ_MEMBER_FUNCTION_P (r))
+    {
+      tree closure_obj = DECL_CONTEXT (r);
+      tree lambda_expr = CLASSTYPE_LAMBDA_EXPR (closure_obj);
+      tree obj_param = TREE_TYPE (DECL_ARGUMENTS (r));
+
+      if (!(LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE
+	    || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr)))
+	/* If a lambda has an empty capture clause, an xobj parameter of
+	   unrelated type is not an error.  */;
+      else if (dependent_type_p (obj_param))
+	/* If we are coming from tsubst_lambda_expr we might not have
+	   substituted into our xobj parameter yet.  We can't error out until
+	   we know what the type really is so do nothing...
+	   ...but if we are instantiating the call op for real and we don't
+	   have a real type then something has gone incredibly wrong.  */
+	gcc_assert (lambda_fntype);
+      else
+	{
+	  /* We have a lambda with captures, and know the type of the xobj
+	     parameter, time to check it.  */
+	  tree obj_param_type = TYPE_MAIN_VARIANT (non_reference (obj_param));
+	  if (!same_or_base_type_p (closure_obj, obj_param_type))
+	    {
+	      /* This error does not emit when the lambda's call operator
+		 template is instantiated by taking its address, such as in
+		 the following case:
+
+		 auto f = [x = 0](this auto&&){};
+		 int (*fp)(int&) = &decltype(f)::operator();
+
+		 It only emits when explicitly calling the call operator with
+		 an explicit template parameter:
+
+		 template<typename T>
+		 struct S : T {
+		   using T::operator();
+		   operator int() const {return {};}
+		 };
+
+		 auto s = S{[x = 0](this auto&&) {}};
+		 s.operator()<int>();
+
+		 This is due to resolve_address_of_overloaded_function being
+		 deficient at reporting candidates when overload resolution
+		 fails.
+
+		 This diagnostic will be active in the first case if/when
+		 resolve_address_of_overloaded_function is fixed to properly
+		 emit candidates upon failure to resolve to an overload.  */
+	      if (complain & tf_error)
+		error ("a lambda with captures may not have an explicit "
+		       "object parameter of an unrelated type");
+	      return error_mark_node;
+	    }
+	}
+    }
+
   /* Associate the constraints directly with the instantiation. We
      don't substitute through the constraints; that's only done when
      they are checked.  */
@@ -19473,7 +19533,11 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	 which would be skipped if cp_unevaluated_operand.  */
       cp_evaluated ev;
 
-      /* Fix the type of 'this'.  */
+      /* Fix the type of 'this'.
+	 For static and xobj member functions we use this to transport the
+	 lambda's closure type.  It appears that in the regular case the
+	 object parameter is still pulled off, and then re-added again anyway.
+	 So perhaps we could do something better here?  */
       fntype = build_memfn_type (fntype, type,
 				 type_memfn_quals (fntype),
 				 type_memfn_rqual (fntype));
@@ -25197,14 +25261,14 @@ more_specialized_fn (tree pat1, tree pat2, int len)
      also.  This situation occurs for operator functions where we
      locate both a member function (with this pointer) and non-member
      operator (with explicit first operand).  */
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl1))
+  if (DECL_OBJECT_MEMBER_FUNCTION_P (decl1))
     {
       len--; /* LEN is the number of significant arguments for DECL1 */
       args1 = TREE_CHAIN (args1);
       if (!DECL_STATIC_FUNCTION_P (decl2))
 	args2 = TREE_CHAIN (args2);
     }
-  else if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl2))
+  else if (DECL_OBJECT_MEMBER_FUNCTION_P (decl2))
     {
       args2 = TREE_CHAIN (args2);
       if (!DECL_STATIC_FUNCTION_P (decl1))
diff --git a/gcc/cp/search.cc b/gcc/cp/search.cc
index ac79b625b6e..efea5078992 100644
--- a/gcc/cp/search.cc
+++ b/gcc/cp/search.cc
@@ -713,6 +713,8 @@ protected_accessible_p (tree decl, tree derived, tree type, tree otype)
      derived from that class) (_expr.ref_).  If the access is to form
      a pointer to member, the nested-name-specifier shall name the
      derived class (or any class derived from that class).  */
+  /* Will this cause problems for xobj member functions?
+     We definitely need tests for this case.  */
   if (DECL_NONSTATIC_MEMBER_P (decl)
       && !DERIVED_FROM_P (derived, otype))
     return 0;
@@ -2223,10 +2225,13 @@ look_for_overrides_here (tree type, tree fndecl)
 	/* Not a virtual.  */;
       else if (DECL_CONTEXT (fn) != type)
 	/* Introduced with a using declaration.  */;
-      else if (DECL_STATIC_FUNCTION_P (fndecl))
+      else if (DECL_STATIC_FUNCTION_P (fndecl)
+	       || DECL_XOBJ_MEMBER_FUNCTION_P (fndecl))
 	{
 	  tree btypes = TYPE_ARG_TYPES (TREE_TYPE (fn));
 	  tree dtypes = TYPE_ARG_TYPES (TREE_TYPE (fndecl));
+	  dtypes = DECL_XOBJ_MEMBER_FUNCTION_P (fndecl) ? TREE_CHAIN (dtypes)
+							: dtypes;
 	  if (compparms (TREE_CHAIN (btypes), dtypes))
 	    return fn;
 	}
@@ -2254,6 +2259,15 @@ look_for_overrides_r (tree type, tree fndecl)
 	  error ("%q+#D cannot be declared", fndecl);
 	  error ("  since %q+#D declared in base class", fn);
 	}
+      else if (DECL_XOBJ_MEMBER_FUNCTION_P (fndecl))
+	{
+	  auto_diagnostic_group d;
+	  error_at (DECL_SOURCE_LOCATION (fndecl),
+		    "explicit object member function "
+		    "overrides virtual function");
+	  inform (DECL_SOURCE_LOCATION (fn),
+		  "virtual function declared here");
+	}
       else
 	{
 	  /* It's definitely virtual, even if not explicitly set.  */
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index fbbc18336a0..9f4c5a91f56 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -45,6 +45,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gomp-constants.h"
 #include "predict.h"
 #include "memmodel.h"
+#include "gcc-rich-location.h"
 
 /* There routines provide a modular interface to perform many parsing
    operations.  They may therefore be used during actual parsing, or
@@ -3093,7 +3094,31 @@ finish_this_expr (void)
     return rvalue (result);
 
   tree fn = current_nonlambda_function ();
-  if (fn && DECL_STATIC_FUNCTION_P (fn))
+  if (fn && DECL_XOBJ_MEMBER_FUNCTION_P (fn))
+    {
+      auto_diagnostic_group d;
+      error ("%<this%> is unavailable for explicit object member "
+	     "functions");
+      /* Doing a fixit here is possible, but hard, might be worthwhile
+	 in the future.  */
+      tree xobj_parm = DECL_ARGUMENTS (fn);
+      gcc_assert (xobj_parm);
+      tree parm_name = DECL_NAME (xobj_parm);
+      if (parm_name)
+	inform (DECL_SOURCE_LOCATION (xobj_parm),
+		"use explicit object parameter %qD instead",
+		parm_name);
+      else
+	{
+	  gcc_rich_location xobj_loc (DECL_SOURCE_LOCATION (xobj_parm));
+	  /* This doesn't work and I don't know why.  I'll probably remove it
+	     before the final version.  */
+	  xobj_loc.add_fixit_insert_after (" self");
+	  inform (DECL_SOURCE_LOCATION (xobj_parm),
+		  "name and use the explicit object parameter instead");
+	}
+    }
+  else if (fn && DECL_STATIC_FUNCTION_P (fn))
     error ("%<this%> is unavailable for static member functions");
   else if (fn && processing_contract_condition && DECL_CONSTRUCTOR_P (fn))
     error ("invalid use of %<this%> before it is valid");
@@ -12812,9 +12837,11 @@ capture_decltype (tree decl)
       if (WILDCARD_TYPE_P (non_reference (obtype)))
 	/* We don't know what the eventual obtype quals will be.  */
 	return NULL_TREE;
-      int quals = cp_type_quals (type);
-      if (INDIRECT_TYPE_P (obtype))
-	quals |= cp_type_quals (TREE_TYPE (obtype));
+      /* This change possibly conflicts with another patch that is currently
+	 in flight. (Patrick Palka's PR83167 patch) I am just changing it for
+	 now to satisfy my tests.  */
+      int const quals = cp_type_quals (type)
+		      | cp_type_quals (TREE_TYPE (obtype));
       type = cp_build_qualified_type (type, quals);
       type = build_reference_type (type);
     }
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index e0b9d512adc..ac05bffea3f 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -3673,7 +3673,7 @@ build_min_non_dep_op_overload (enum tree_code op,
   nargs = call_expr_nargs (non_dep);
 
   expected_nargs = cp_tree_code_length (op);
-  if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE
+  if (DECL_OBJECT_MEMBER_FUNCTION_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
@@ -3691,7 +3691,7 @@ 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 (!DECL_OBJECT_MEMBER_FUNCTION_P (overload))
     {
       fn = overload;
       if (op == ARRAY_REF)
@@ -3702,7 +3702,7 @@ build_min_non_dep_op_overload (enum tree_code op,
 	  vec_safe_push (args, arg);
 	}
     }
-  else if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE)
+  else
     {
       tree object = va_arg (p, tree);
       tree binfo = TYPE_BINFO (TREE_TYPE (object));
@@ -3715,8 +3715,6 @@ build_min_non_dep_op_overload (enum tree_code op,
 	  vec_safe_push (args, arg);
 	}
     }
-  else
-    gcc_unreachable ();
 
   va_end (p);
   call = build_min_non_dep_call_vec (non_dep, fn, args);
@@ -3743,7 +3741,7 @@ 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 (DECL_OBJECT_MEMBER_FUNCTION_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 bf8ffaa7e75..73707624ce4 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -2339,7 +2339,10 @@ cxx_alignas_expr (tree e)
      used only as the operand for the function call operator ().
 
    by issuing an error message if appropriate.  Returns true iff EXPR
-   violates these rules.  */
+   violates these rules.
+
+   The name "nonstatic" is no longer accurate with the addition of
+   xobj member functions, should we change the name?  */
 
 bool
 invalid_nonstatic_memfn_p (location_t loc, tree expr, tsubst_flags_t complain)
@@ -2352,7 +2355,7 @@ invalid_nonstatic_memfn_p (location_t loc, tree expr, tsubst_flags_t complain)
   if (is_overloaded_fn (expr) && !really_overloaded_fn (expr))
     expr = get_first_fn (expr);
   if (TREE_TYPE (expr)
-      && DECL_NONSTATIC_MEMBER_FUNCTION_P (expr))
+      && DECL_IOBJ_MEMBER_FUNCTION_P (expr))
     {
       if (complain & tf_error)
 	{
@@ -7174,6 +7177,24 @@ cp_build_addr_expr_1 (tree arg, bool strict_lvalue, tsubst_flags_t complain)
 
     case BASELINK:
       arg = BASELINK_FUNCTIONS (arg);
+      /* If we are passed a BASELINK, the operand of the address-of operator
+	 was not qualified, taking the address of a non-static member function
+	 must be qualified by the class.
+	 It's possible we shouldn't be making this baselink optimization for
+	 xobj member functions at all, we should evaluate that later.  */
+      if (DECL_XOBJ_MEMBER_FUNCTION_P (arg))
+	{
+	  /* Error should match that in
+	     class.cc:resolve_address_of_overloaded_function.
+	     We really should do a fixit here.  */
+	  error_at (input_location,
+		    "taking the address of an explicit object member "
+		    "function must be qualified");
+	  inform (input_location,
+		  "(a pointer to explicit object member function can only be "
+		  "formed with %<&%E%>)", arg);
+	  return error_mark_node;
+	}
       /* Fall through.  */
 
     case OVERLOAD:
@@ -7205,6 +7226,17 @@ 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;
 
+	/* Pull out the function_decl for a single xobj member function, and
+	   let the rest of this function handle it.  This is handled the same
+	   way as the BASELINK case above, but we can't handle xobj member
+	   functions there as xobj member functions must be qualified, hence
+	   the error above.  */
+	if (DECL_XOBJ_MEMBER_FUNCTION_P (t))
+	  {
+	    arg = t;
+	    break;
+	  }
+
 	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..134182c7741
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic1.C
@@ -0,0 +1,114 @@
+// 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..6b2cad130fa
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic2.C
@@ -0,0 +1,28 @@
+// 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-basic3.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic3.C
new file mode 100644
index 00000000000..e466cc9761c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic3.C
@@ -0,0 +1,496 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// bogus diagnosis of valid declarations as redeclarations
+// tests for by-value are elsewhere (todo: add filename)
+
+// each group has 8 overloads that each take
+// lvalue ref to S
+// rvalue ref to S
+// lvalue c ref to S
+// rvalue c ref to S
+// lvalue v ref to S
+// rvalue v ref to S
+// lvalue cv ref to S
+// rvalue cv ref to S
+// where S is the struct the function is declared in
+
+// only xobj (the most basic case)
+
+struct S {
+  void f(this S &);
+  void f(this S &&);
+  void f(this S const&);
+  void f(this S const&&);
+  void f(this S volatile&);
+  void f(this S volatile&&);
+  void f(this S const volatile&);
+  void f(this S const volatile&&);
+};
+
+// I* has the 1 xobj 7 iobj cases
+// X* has the 7 xobj 1 iobj cases
+// *0 has the unique function first, the rest after
+// *1 has the unique function last, the rest after
+// *2 has the functions in the order stated above
+// xobj first, 1 xobj, 7 iobj
+
+// (yes there are some redundant cases)
+
+// unique first, 1 xobj 7 iobj
+
+struct I0 {
+  void f0(this I0&);
+  void f0() &&;
+  void f0() const&;
+  void f0() const&&;
+  void f0() volatile&;
+  void f0() volatile&&;
+  void f0() const volatile&;
+  void f0() const volatile&&;
+
+  void f1(this I0&&);
+  void f1() &;
+  void f1() const&;
+  void f1() const&&;
+  void f1() volatile&;
+  void f1() volatile&&;
+  void f1() const volatile&;
+  void f1() const volatile&&;
+
+  void fc0(this I0 const&);
+  void fc0() &;
+  void fc0() &&;
+  void fc0() const&&;
+  void fc0() volatile&;
+  void fc0() volatile&&;
+  void fc0() const volatile&;
+  void fc0() const volatile&&;
+
+  void fc1(this I0 const&&);
+  void fc1() &;
+  void fc1() &&;
+  void fc1() const&;
+  void fc1() volatile&;
+  void fc1() volatile&&;
+  void fc1() const volatile&;
+  void fc1() const volatile&&;
+
+  void fv0(this I0 volatile&);
+  void fv0() &;
+  void fv0() &&;
+  void fv0() const&;
+  void fv0() const&&;
+  void fv0() volatile&&;
+  void fv0() const volatile&;
+  void fv0() const volatile&&;
+
+  void fv1(this I0 volatile&&);
+  void fv1() &;
+  void fv1() &&;
+  void fv1() const&;
+  void fv1() const&&;
+  void fv1() volatile&;
+  void fv1() const volatile&;
+  void fv1() const volatile&&;
+
+  void fcv0(this I0 const volatile&);
+  void fcv0() &;
+  void fcv0() &&;
+  void fcv0() const&;
+  void fcv0() const&&;
+  void fcv0() volatile&;
+  void fcv0() volatile&&;
+  void fcv0() const volatile&&;
+
+  void fcv1(this I0 const volatile&&);
+  void fcv1() &;
+  void fcv1() &&;
+  void fcv1() const&;
+  void fcv1() const&&;
+  void fcv1() volatile&;
+  void fcv1() volatile&&;
+  void fcv1() const volatile&;
+};
+
+// unique last, 1 xobj 7 iobj
+
+struct I1 {
+  void f0() &&;
+  void f0() const&;
+  void f0() const&&;
+  void f0() volatile&;
+  void f0() volatile&&;
+  void f0() const volatile&;
+  void f0() const volatile&&;
+  void f0(this I1&);
+
+  void f1() &;
+  void f1() const&;
+  void f1() const&&;
+  void f1() volatile&;
+  void f1() volatile&&;
+  void f1() const volatile&;
+  void f1() const volatile&&;
+  void f1(this I1&&);
+
+  void fc0() &;
+  void fc0() &&;
+  void fc0() const&&;
+  void fc0() volatile&;
+  void fc0() volatile&&;
+  void fc0() const volatile&;
+  void fc0() const volatile&&;
+  void fc0(this I1 const&);
+
+  void fc1() &;
+  void fc1() &&;
+  void fc1() const&;
+  void fc1() volatile&;
+  void fc1() volatile&&;
+  void fc1() const volatile&;
+  void fc1() const volatile&&;
+  void fc1(this I1 const&&);
+
+  void fv0() &;
+  void fv0() &&;
+  void fv0() const&;
+  void fv0() const&&;
+  void fv0() volatile&&;
+  void fv0() const volatile&;
+  void fv0() const volatile&&;
+  void fv0(this I1 volatile&);
+
+  void fv1() &;
+  void fv1() &&;
+  void fv1() const&;
+  void fv1() const&&;
+  void fv1() volatile&;
+  void fv1() const volatile&;
+  void fv1() const volatile&&;
+  void fv1(this I1 volatile&&);
+
+  void fcv0() &;
+  void fcv0() &&;
+  void fcv0() const&;
+  void fcv0() const&&;
+  void fcv0() volatile&;
+  void fcv0() volatile&&;
+  void fcv0() const volatile&&;
+  void fcv0(this I1 const volatile&);
+
+  void fcv1() &;
+  void fcv1() &&;
+  void fcv1() const&;
+  void fcv1() const&&;
+  void fcv1() volatile&;
+  void fcv1() volatile&&;
+  void fcv1() const volatile&;
+  void fcv1(this I1 const volatile&&);
+};
+
+// ordered, 1 xobj 7 iobj
+
+struct I2 {
+  void f0(this I2&);
+  void f0() &&;
+  void f0() const&;
+  void f0() const&&;
+  void f0() volatile&;
+  void f0() volatile&&;
+  void f0() const volatile&;
+  void f0() const volatile&&;
+
+  void f1() &;
+  void f1(this I2&&);
+  void f1() const&;
+  void f1() const&&;
+  void f1() volatile&;
+  void f1() volatile&&;
+  void f1() const volatile&;
+  void f1() const volatile&&;
+
+  void fc0() &;
+  void fc0() &&;
+  void fc0(this I2 const&);
+  void fc0() const&&;
+  void fc0() volatile&;
+  void fc0() volatile&&;
+  void fc0() const volatile&;
+  void fc0() const volatile&&;
+
+  void fc1() &;
+  void fc1() &&;
+  void fc1() const&;
+  void fc1(this I2 const&&);
+  void fc1() volatile&;
+  void fc1() volatile&&;
+  void fc1() const volatile&;
+  void fc1() const volatile&&;
+
+  void fv0() &;
+  void fv0() &&;
+  void fv0() const&;
+  void fv0() const&&;
+  void fv0(this I2 volatile&);
+  void fv0() volatile&&;
+  void fv0() const volatile&;
+  void fv0() const volatile&&;
+
+  void fv1() &;
+  void fv1() &&;
+  void fv1() const&;
+  void fv1() const&&;
+  void fv1() volatile&;
+  void fv1(this I2 volatile&&);
+  void fv1() const volatile&;
+  void fv1() const volatile&&;
+
+  void fcv0() &;
+  void fcv0() &&;
+  void fcv0() const&;
+  void fcv0() const&&;
+  void fcv0() volatile&;
+  void fcv0() volatile&&;
+  void fcv0(this I2 const volatile&);
+  void fcv0() const volatile&&;
+
+  void fcv1() &;
+  void fcv1() &&;
+  void fcv1() const&;
+  void fcv1() const&&;
+  void fcv1() volatile&;
+  void fcv1() volatile&&;
+  void fcv1() const volatile&;
+  void fcv1(this I2 const volatile&&);
+};
+
+
+// iobj first, 7 xobj, 1 iobj
+
+struct X0 {
+  void f0() &;
+  void f0(this X0 &&);
+  void f0(this X0 const&);
+  void f0(this X0 const&&);
+  void f0(this X0 volatile&);
+  void f0(this X0 volatile&&);
+  void f0(this X0 const volatile&);
+  void f0(this X0 const volatile&&);
+
+  void f1() &&;
+  void f1(this X0 &);
+  void f1(this X0 const&);
+  void f1(this X0 const&&);
+  void f1(this X0 volatile&);
+  void f1(this X0 volatile&&);
+  void f1(this X0 const volatile&);
+  void f1(this X0 const volatile&&);
+
+  void fc0() const&;
+  void fc0(this X0 &);
+  void fc0(this X0 &&);
+  void fc0(this X0 const&&);
+  void fc0(this X0 volatile&);
+  void fc0(this X0 volatile&&);
+  void fc0(this X0 const volatile&);
+  void fc0(this X0 const volatile&&);
+
+  void fc1() const&&;
+  void fc1(this X0 &);
+  void fc1(this X0 &&);
+  void fc1(this X0 const&);
+  void fc1(this X0 volatile&);
+  void fc1(this X0 volatile&&);
+  void fc1(this X0 const volatile&);
+  void fc1(this X0 const volatile&&);
+
+  void fv0() volatile&;
+  void fv0(this X0 &);
+  void fv0(this X0 &&);
+  void fv0(this X0 const&);
+  void fv0(this X0 const&&);
+  void fv0(this X0 volatile&&);
+  void fv0(this X0 const volatile&);
+  void fv0(this X0 const volatile&&);
+
+  void fv1() volatile&&;
+  void fv1(this X0 &);
+  void fv1(this X0 &&);
+  void fv1(this X0 const&);
+  void fv1(this X0 const&&);
+  void fv1(this X0 volatile&);
+  void fv1(this X0 const volatile&);
+  void fv1(this X0 const volatile&&);
+
+  void fcv0() const volatile&;
+  void fcv0(this X0 &);
+  void fcv0(this X0 &&);
+  void fcv0(this X0 const&);
+  void fcv0(this X0 const&&);
+  void fcv0(this X0 volatile&);
+  void fcv0(this X0 volatile&&);
+  void fcv0(this X0 const volatile&&);
+
+  void fcv1() const volatile&&;
+  void fcv1(this X0 &);
+  void fcv1(this X0 &&);
+  void fcv1(this X0 const&);
+  void fcv1(this X0 const&&);
+  void fcv1(this X0 volatile&);
+  void fcv1(this X0 volatile&&);
+  void fcv1(this X0 const volatile&);
+};
+
+// iobj last, 7 xobj 1 iobj
+
+struct X1 {
+  void f0(this X1 &&);
+  void f0(this X1 const&);
+  void f0(this X1 const&&);
+  void f0(this X1 volatile&);
+  void f0(this X1 volatile&&);
+  void f0(this X1 const volatile&);
+  void f0(this X1 const volatile&&);
+  void f0() &;
+
+  void f1(this X1 &);
+  void f1(this X1 const&);
+  void f1(this X1 const&&);
+  void f1(this X1 volatile&);
+  void f1(this X1 volatile&&);
+  void f1(this X1 const volatile&);
+  void f1(this X1 const volatile&&);
+  void f1() &&;
+
+  void fc0(this X1 &);
+  void fc0(this X1 &&);
+  void fc0(this X1 const&&);
+  void fc0(this X1 volatile&);
+  void fc0(this X1 volatile&&);
+  void fc0(this X1 const volatile&);
+  void fc0(this X1 const volatile&&);
+  void fc0() const&;
+
+  void fc1(this X1 &);
+  void fc1(this X1 &&);
+  void fc1(this X1 const&);
+  void fc1(this X1 volatile&);
+  void fc1(this X1 volatile&&);
+  void fc1(this X1 const volatile&);
+  void fc1(this X1 const volatile&&);
+  void fc1() const&&;
+
+  void fv0(this X1 &);
+  void fv0(this X1 &&);
+  void fv0(this X1 const&);
+  void fv0(this X1 const&&);
+  void fv0(this X1 volatile&&);
+  void fv0(this X1 const volatile&);
+  void fv0(this X1 const volatile&&);
+  void fv0() volatile&;
+
+  void fv1(this X1 &);
+  void fv1(this X1 &&);
+  void fv1(this X1 const&);
+  void fv1(this X1 const&&);
+  void fv1(this X1 volatile&);
+  void fv1(this X1 const volatile&);
+  void fv1(this X1 const volatile&&);
+  void fv1() volatile&&;
+
+  void fcv0(this X1 &);
+  void fcv0(this X1 &&);
+  void fcv0(this X1 const&);
+  void fcv0(this X1 const&&);
+  void fcv0(this X1 volatile&);
+  void fcv0(this X1 volatile&&);
+  void fcv0(this X1 const volatile&&);
+  void fcv0() const volatile&;
+
+  void fcv1(this X1 &);
+  void fcv1(this X1 &&);
+  void fcv1(this X1 const&);
+  void fcv1(this X1 const&&);
+  void fcv1(this X1 volatile&);
+  void fcv1(this X1 volatile&&);
+  void fcv1(this X1 const volatile&);
+  void fcv1() const volatile&&;
+};
+
+// ordered, 7 xobj 1 iobj
+
+struct X2 {
+  void f0() &;
+  void f0(this X2 &&);
+  void f0(this X2 const&);
+  void f0(this X2 const&&);
+  void f0(this X2 volatile&);
+  void f0(this X2 volatile&&);
+  void f0(this X2 const volatile&);
+  void f0(this X2 const volatile&&);
+
+  void f1(this X2 &);
+  void f1() &&;
+  void f1(this X2 const&);
+  void f1(this X2 const&&);
+  void f1(this X2 volatile&);
+  void f1(this X2 volatile&&);
+  void f1(this X2 const volatile&);
+  void f1(this X2 const volatile&&);
+
+  void fc0(this X2 &);
+  void fc0(this X2 &&);
+  void fc0() const&;
+  void fc0(this X2 const&&);
+  void fc0(this X2 volatile&);
+  void fc0(this X2 volatile&&);
+  void fc0(this X2 const volatile&);
+  void fc0(this X2 const volatile&&);
+
+  void fc1(this X2 &);
+  void fc1(this X2 &&);
+  void fc1(this X2 const&);
+  void fc1() const&&;
+  void fc1(this X2 volatile&);
+  void fc1(this X2 volatile&&);
+  void fc1(this X2 const volatile&);
+  void fc1(this X2 const volatile&&);
+
+  void fv0(this X2 &);
+  void fv0(this X2 &&);
+  void fv0(this X2 const&);
+  void fv0(this X2 const&&);
+  void fv0() volatile&;
+  void fv0(this X2 volatile&&);
+  void fv0(this X2 const volatile&);
+  void fv0(this X2 const volatile&&);
+
+  void fv1(this X2 &);
+  void fv1(this X2 &&);
+  void fv1(this X2 const&);
+  void fv1(this X2 const&&);
+  void fv1(this X2 volatile&);
+  void fv1() volatile&&;
+  void fv1(this X2 const volatile&);
+  void fv1(this X2 const volatile&&);
+
+  void fcv0(this X2 &);
+  void fcv0(this X2 &&);
+  void fcv0(this X2 const&);
+  void fcv0(this X2 const&&);
+  void fcv0(this X2 volatile&);
+  void fcv0(this X2 volatile&&);
+  void fcv0() const volatile&;
+  void fcv0(this X2 const volatile&&);
+
+  void fcv1(this X2 &);
+  void fcv1(this X2 &&);
+  void fcv1(this X2 const&);
+  void fcv1(this X2 const&&);
+  void fcv1(this X2 volatile&);
+  void fcv1(this X2 volatile&&);
+  void fcv1(this X2 const volatile&);
+  void fcv1() const volatile&&;
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C
new file mode 100644
index 00000000000..691ef6804cf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C
@@ -0,0 +1,113 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// valid overloading of iobj member functions without ref qualifiers
+// with xobj member functions (and vice-versa)
+
+// this is the most you can mix these, it may look short but it does test
+// all allowed cases (other than by-value and unrelated types)
+
+// [over.match.funcs.general.4]
+// For implicit object member functions, the type of the implicit
+// object parameter is
+// -- “lvalue reference to cv X” for functions declared
+//    without a ref-qualifier or with the & ref-qualifier
+// -- “rvalue reference to cv X” for functions declared with
+//    the && ref-qualifier
+
+// [basic.scope.scope.3]
+// Two non-static member functions have corresponding object
+// parameters if:
+// -- exactly one is an implicit object member function with no
+//    ref-qualifier and the types of their object parameters
+//    ([dcl.fct]), after removing top-level references, are the
+//    same, or
+
+// in simpler terms, only the cv qualification of the explicit/implicit object
+// parameter matter for determining whether these are redeclarations or overloads
+// (when a ref qualifier is not present on the iobj member function)
+
+// xobj first, iobj last
+
+struct S0 {
+  void f(this S0 &);
+  void f(this S0 &&);
+  void f() const;
+  void f() volatile;
+  void f() const volatile;
+
+  void fc(this S0 const&);
+  void fc(this S0 const&&);
+  void fc();
+  void fc() volatile;
+  void fc() const volatile;
+
+  void fv(this S0 volatile&);
+  void fv(this S0 volatile&&);
+  void fv();
+  void fv() const;
+  void fv() const volatile;
+
+  void fcv(this S0 const volatile&);
+  void fcv(this S0 const volatile&&);
+  void fcv();
+  void fcv() const;
+  void fcv() volatile;
+};
+
+// iobj first, xobj last
+
+struct S1 {
+  void f() const;
+  void f() volatile;
+  void f() const volatile;
+  void f(this S1 &);
+  void f(this S1 &&);
+
+  void fc();
+  void fc() volatile;
+  void fc() const volatile;
+  void fc(this S1 const&);
+  void fc(this S1 const&&);
+
+  void fv();
+  void fv() const;
+  void fv() const volatile;
+  void fv(this S1 volatile&);
+  void fv(this S1 volatile&&);
+
+  void fcv();
+  void fcv() const;
+  void fcv() volatile;
+  void fcv(this S1 const volatile&);
+  void fcv(this S1 const volatile&&);
+};
+
+// in order
+
+struct S2 {
+  void f(this S2 &);
+  void f(this S2 &&);
+  void f() const;
+  void f() volatile;
+  void f() const volatile;
+
+  void fc();
+  void fc(this S2 const&);
+  void fc(this S2 const&&);
+  void fc() volatile;
+  void fc() const volatile;
+
+  void fv();
+  void fv() const;
+  void fv(this S2 volatile&);
+  void fv(this S2 volatile&&);
+  void fv() const volatile;
+
+  void fcv();
+  void fcv() const;
+  void fcv() volatile;
+  void fcv(this S2 const volatile&);
+  void fcv(this S2 const volatile&&);
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic5.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic5.C
new file mode 100644
index 00000000000..de3c4fa9a9a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic5.C
@@ -0,0 +1,31 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+inline constexpr int magic = 42;
+
+struct S0 {
+  operator int(this S0 const&) {
+    return magic;
+  }
+};
+
+struct S1 {
+  int _v;
+  int f(this int self) {
+    return self;
+  }
+  operator int(this S1 const& self) {
+    return self._v;
+  }
+};
+
+int main()
+{
+  if (S0{} != magic)
+    __builtin_abort ();
+
+  S1 s{42};
+  if (static_cast<int>(s) != magic)
+    __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..5ea5bcb7faa
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value1.C
@@ -0,0 +1,48 @@
+// 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.
+
+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..b8e8e73dfaf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value2.C
@@ -0,0 +1,58 @@
+// 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
+
+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..e6aff0190fb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value3.C
@@ -0,0 +1,41 @@
+// 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
+
+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..9b4e00582cf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-by-value4.C
@@ -0,0 +1,20 @@
+// 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-constraints.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-constraints.C
new file mode 100644
index 00000000000..7ef9877b67a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-constraints.C
@@ -0,0 +1,470 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// overload resolution of static/xobj and iobj/xobj member functions
+// with constraints
+
+template<typename T>
+concept Constrain = true;
+
+inline constexpr int iobj_fn = 5;
+inline constexpr int xobj_fn = 10;
+inline constexpr int static_fn = 20;
+
+// NOTE, Jason Merril doesn't think the static cases are neccesarily correct
+
+/* https://gcc.gnu.org/pipermail/gcc-patches/2023-December/639052.html
+   I would actually expect the static function to be chosen as more
+   specialized before we get to considering constraints, just as with
+
+   void f(auto, Constrain auto) = delete;
+   void f(const S&, auto) {}
+   int main() { f(S{},0); } // OK
+
+   Though it looks like [temp.func.order] needs to be adjusted for explicit
+   object parameters.  And more_specialized_fn in gcc still has an outdated
+   handling of object parameters that just skips them, from before the
+   clearer specification in C++11 and later; this is PR53499.  No need to
+   address that preexisting bug in this patch.  */
+
+// first 2 letters are the order of the definitions
+// the constraint applies to the first definition,
+// for *_r cases the constraint applies to the second
+
+struct S {
+  // xobj/static
+  int f_xs_v(this S, Constrain auto) { return xobj_fn; };
+  static int f_xs_v(auto) { return static_fn; };
+
+  int f_xs_ref(this S&, Constrain auto) { return xobj_fn; };
+  static int f_xs_ref(auto) { return static_fn; };
+
+  int f_xs_cref(this S const&, Constrain auto) { return xobj_fn; };
+  static int f_xs_cref(auto) { return static_fn; };
+
+  int f_xs_rref(this S&&, Constrain auto) { return xobj_fn; };
+  static int f_xs_rref(auto) { return static_fn; };
+
+  int f_xs_crref(this S const&&, Constrain auto) { return xobj_fn; };
+  static int f_xs_crref(auto) { return static_fn; };
+
+  int f_xs_dv(this auto, Constrain auto) { return xobj_fn; };
+  static int f_xs_dv(auto) { return static_fn; };
+
+  int f_xs_dcref(this auto const&, Constrain auto) { return xobj_fn; };
+  static int f_xs_dcref(auto) { return static_fn; };
+
+  int f_xs_dfwdref(this auto&&, Constrain auto) { return xobj_fn; };
+  static int f_xs_dfwdref(auto) { return static_fn; };
+
+  // _r
+  int f_xs_v_r(this S, auto) { return xobj_fn; };
+  static int f_xs_v_r(Constrain auto) { return static_fn; };
+
+  int f_xs_ref_r(this S&, auto) { return xobj_fn; };
+  static int f_xs_ref_r(Constrain auto) { return static_fn; };
+
+  int f_xs_cref_r(this S const&, auto) { return xobj_fn; };
+  static int f_xs_cref_r(Constrain auto) { return static_fn; };
+
+  int f_xs_rref_r(this S&&, auto) { return xobj_fn; };
+  static int f_xs_rref_r(Constrain auto) { return static_fn; };
+
+  int f_xs_crref_r(this S const&&, auto) { return xobj_fn; };
+  static int f_xs_crref_r(Constrain auto) { return static_fn; };
+
+  int f_xs_dv_r(this auto, auto) { return xobj_fn; };
+  static int f_xs_dv_r(Constrain auto) { return static_fn; };
+
+  int f_xs_dcref_r(this auto const&, auto) { return xobj_fn; };
+  static int f_xs_dcref_r(Constrain auto) { return static_fn; };
+
+  int f_xs_dfwdref_r(this auto&&, auto) { return xobj_fn; };
+  static int f_xs_dfwdref_r(Constrain auto) { return static_fn; };
+
+  // static/xobj
+  static int f_sx_v(Constrain auto) { return static_fn; };
+  int f_sx_v(this S, auto) { return xobj_fn; };
+
+  static int f_sx_ref(Constrain auto) { return static_fn; };
+  int f_sx_ref(this S&, auto) { return xobj_fn; };
+
+  static int f_sx_cref(Constrain auto) { return static_fn; };
+  int f_sx_cref(this S const&, auto) { return xobj_fn; };
+
+  static int f_sx_rref(Constrain auto) { return static_fn; };
+  int f_sx_rref(this S&&, auto) { return xobj_fn; };
+
+  static int f_sx_crref(Constrain auto) { return static_fn; };
+  int f_sx_crref(this S const&&, auto) { return xobj_fn; };
+
+  static int f_sx_dv(Constrain auto) { return static_fn; };
+  int f_sx_dv(this auto, auto) { return xobj_fn; };
+
+  static int f_sx_dcref(Constrain auto) { return static_fn; };
+  int f_sx_dcref(this auto const&, auto) { return xobj_fn; };
+
+  static int f_sx_dfwdref(Constrain auto) { return static_fn; };
+  int f_sx_dfwdref(this auto&&, auto) { return xobj_fn; };
+
+  // _r
+  static int f_sx_v_r(auto) { return static_fn; };
+  int f_sx_v_r(this S, Constrain auto) { return xobj_fn; };
+
+  static int f_sx_ref_r(auto) { return static_fn; };
+  int f_sx_ref_r(this S&, Constrain auto) { return xobj_fn; };
+
+  static int f_sx_cref_r(auto) { return static_fn; };
+  int f_sx_cref_r(this S const&, Constrain auto) { return xobj_fn; };
+
+  static int f_sx_rref_r(auto) { return static_fn; };
+  int f_sx_rref_r(this S&&, Constrain auto) { return xobj_fn; };
+
+  static int f_sx_crref_r(auto) { return static_fn; };
+  int f_sx_crref_r(this S const&&, Constrain auto) { return xobj_fn; };
+
+  static int f_sx_dv_r(auto) { return static_fn; };
+  int f_sx_dv_r(this auto, Constrain auto) { return xobj_fn; };
+
+  static int f_sx_dcref_r(auto) { return static_fn; };
+  int f_sx_dcref_r(this auto const&, Constrain auto) { return xobj_fn; };
+
+  static int f_sx_dfwdref_r(auto) { return static_fn; };
+  int f_sx_dfwdref_r(this auto&&, Constrain auto) { return xobj_fn; };
+
+  // xobj/iobj with matching object parameters
+
+  // We are only testing constraints here, so we need parameter lists
+  // to match, which means we need corresponding object parameters.
+  // Remember, the rules for object parameter correspondence are weird.
+  // ([basic.scope.scope-3.1])
+
+  // *_refqual the iobj member function has a reference qualifier
+  // *_r the constraint applies to the second definition
+
+  // ix
+  int f_ix_m0(Constrain auto) { return iobj_fn; };
+  int f_ix_m0(this S&, auto) { return xobj_fn; };
+  // See note
+  int f_ix_m1(Constrain auto) { return iobj_fn; };
+  int f_ix_m1(this S&&, auto) { return xobj_fn; };
+
+  int f_ix_c0(Constrain auto) const { return iobj_fn; };
+  int f_ix_c0(this S const&, auto) { return xobj_fn; };
+  // See note
+  int f_ix_c1(Constrain auto) const { return iobj_fn; };
+  int f_ix_c1(this S const&&, auto) { return xobj_fn; };
+  
+  // xi
+  int f_xi_m0(this S&, Constrain auto) { return xobj_fn; };
+  int f_xi_m0(auto) { return iobj_fn; };
+  // See note
+  int f_xi_m1(this S&&, Constrain auto) { return xobj_fn; };
+  int f_xi_m1(auto) { return iobj_fn; };
+
+  int f_xi_c0(this S const&, Constrain auto) { return xobj_fn; };
+  int f_xi_c0(auto) const { return iobj_fn; };
+  // See note
+  int f_xi_c1(this S const&&, Constrain auto) { return xobj_fn; };
+  int f_xi_c1(auto) const { return iobj_fn; };
+
+  // with ref qualifier
+
+  // ix
+  int f_ix_m0_refqual(Constrain auto) & { return iobj_fn; };
+  int f_ix_m0_refqual(this S&, auto) { return xobj_fn; };
+
+  int f_ix_m1_refqual(Constrain auto) && { return iobj_fn; };
+  int f_ix_m1_refqual(this S&&, auto) { return xobj_fn; };
+
+  int f_ix_c0_refqual(Constrain auto) const& { return iobj_fn; };
+  int f_ix_c0_refqual(this S const&, auto) { return xobj_fn; };
+
+  int f_ix_c1_refqual(Constrain auto) const&& { return iobj_fn; };
+  int f_ix_c1_refqual(this S const&&, auto) { return xobj_fn; };
+  
+  // xi 
+  int f_xi_m0_refqual(this S&, Constrain auto) { return xobj_fn; };
+  int f_xi_m0_refqual(auto) & { return iobj_fn; };
+
+  int f_xi_m1_refqual(this S&&, Constrain auto) { return xobj_fn; };
+  int f_xi_m1_refqual(auto) && { return iobj_fn; };
+
+  int f_xi_c0_refqual(this S const&, Constrain auto) { return xobj_fn; };
+  int f_xi_c0_refqual(auto) const& { return iobj_fn; };
+
+  int f_xi_c1_refqual(this S const&&, Constrain auto) { return xobj_fn; };
+  int f_xi_c1_refqual(auto) const&& { return iobj_fn; };
+
+  // _r without ref qualifier
+
+  // ix
+  int f_ix_m0_r(auto) { return iobj_fn; };
+  int f_ix_m0_r(this S&, Constrain auto) { return xobj_fn; };
+  // See note
+  int f_ix_m1_r(auto) { return iobj_fn; };
+  int f_ix_m1_r(this S&&, Constrain auto) { return xobj_fn; };
+
+  int f_ix_c0_r(auto) const { return iobj_fn; };
+  int f_ix_c0_r(this S const&, Constrain auto) { return xobj_fn; };
+  // See note
+  int f_ix_c1_r(auto) const { return iobj_fn; };
+  int f_ix_c1_r(this S const&&, Constrain auto) { return xobj_fn; };
+  
+  // xi
+  int f_xi_m0_r(this S&, auto) { return xobj_fn; };
+  int f_xi_m0_r(Constrain auto) { return iobj_fn; };
+  // See note
+  int f_xi_m1_r(this S&&, auto) { return xobj_fn; };
+  int f_xi_m1_r(Constrain auto) { return iobj_fn; };
+
+  int f_xi_c0_r(this S const&, auto) { return xobj_fn; };
+  int f_xi_c0_r(Constrain auto) const { return iobj_fn; };
+  // See note
+  int f_xi_c1_r(this S const&&, auto) { return xobj_fn; };
+  int f_xi_c1_r(Constrain auto) const { return iobj_fn; };
+
+  // _r with ref qualifier
+  // ix
+  int f_ix_m0_refqual_r(auto) & { return iobj_fn; };
+  int f_ix_m0_refqual_r(this S&, Constrain auto) { return xobj_fn; };
+
+  int f_ix_m1_refqual_r(auto) && { return iobj_fn; };
+  int f_ix_m1_refqual_r(this S&&, Constrain auto) { return xobj_fn; };
+
+  int f_ix_c0_refqual_r(auto) const& { return iobj_fn; };
+  int f_ix_c0_refqual_r(this S const&, Constrain auto) { return xobj_fn; };
+
+  int f_ix_c1_refqual_r(auto) const&& { return iobj_fn; };
+  int f_ix_c1_refqual_r(this S const&&, Constrain auto) { return xobj_fn; };
+  
+  // xi
+  int f_xi_m0_refqual_r(this S&, auto) { return xobj_fn; };
+  int f_xi_m0_refqual_r(Constrain auto) & { return iobj_fn; };
+
+  int f_xi_m1_refqual_r(this S&&, auto) { return xobj_fn; };
+  int f_xi_m1_refqual_r(Constrain auto) && { return iobj_fn; };
+
+  int f_xi_c0_refqual_r(this S const&, auto) { return xobj_fn; };
+  int f_xi_c0_refqual_r(Constrain auto) const& { return iobj_fn; };
+
+  int f_xi_c1_refqual_r(this S const&&, auto) { return xobj_fn; };
+  int f_xi_c1_refqual_r(Constrain auto) const&& { return iobj_fn; };
+};
+
+
+int main()
+{
+  // The commented out cases are ambiguous, which is most likely the correct
+  // behavior. It is something that I want to propose to change, and I want
+  // to leave them in as they are a little weird.
+  //
+  // Furthermore, as the comment at the top of this file indicates, I am not
+  // clear on the correct behavior of the static/xobj cases in general.
+
+  S s{};
+  if (s.f_xs_v (0) != xobj_fn)
+    __builtin_abort ();
+  if (s.f_xs_ref (0) != xobj_fn)
+    __builtin_abort ();
+  if (s.f_xs_cref (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xs_rref (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xs_crref (0) != xobj_fn)
+    __builtin_abort ();
+  // if (s.f_xs_dv (0) != xobj_fn)
+  //   __builtin_abort ();
+  // if (s.f_xs_dcref (0) != xobj_fn)
+  //   __builtin_abort ();
+  // if (s.f_xs_dfwdref (0) != xobj_fn)
+  //   __builtin_abort ();
+
+  if (s.f_xs_v_r (0) != static_fn)
+    __builtin_abort ();
+  if (s.f_xs_ref_r (0) != static_fn)
+    __builtin_abort ();
+  if (s.f_xs_cref_r (0) != static_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xs_rref_r (0) != static_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xs_crref_r (0) != static_fn)
+    __builtin_abort ();
+  // if (s.f_xs_dv_r (0) != static_fn)
+  //   __builtin_abort ();
+  // if (s.f_xs_dcref_r (0) != static_fn)
+  //   __builtin_abort ();
+  // if (s.f_xs_dfwdref_r (0) != static_fn)
+  //   __builtin_abort ();
+
+  if (s.f_sx_v (0) != static_fn)
+    __builtin_abort ();
+  if (s.f_sx_ref (0) != static_fn)
+    __builtin_abort ();
+  if (s.f_sx_cref (0) != static_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_sx_rref (0) != static_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_sx_crref (0) != static_fn)
+    __builtin_abort ();
+  // if (s.f_sx_dv (0) != static_fn)
+  //   __builtin_abort ();
+  // if (s.f_sx_dcref (0) != static_fn)
+  //   __builtin_abort ();
+  // if (s.f_sx_dfwdref (0) != static_fn)
+  //   __builtin_abort ();
+
+  if (s.f_sx_v_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (s.f_sx_ref_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (s.f_sx_cref_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_sx_rref_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_sx_crref_r (0) != xobj_fn)
+    __builtin_abort ();
+  // if (s.f_sx_dv_r (0) != xobj_fn)
+  //   __builtin_abort ();
+  // if (s.f_sx_dcref_r (0) != xobj_fn)
+  //   __builtin_abort ();
+  // if (s.f_sx_dfwdref_r (0) != xobj_fn)
+  //   __builtin_abort ();
+
+  // iobj/xobj
+
+  // The commented out cases are tested below as their correct behavior is
+  // unintuitive, see the note below for details.
+  
+  if (s.f_ix_m0 (0) != iobj_fn)
+    __builtin_abort ();
+  // s.f_ix_m1
+  if (s.f_ix_c0 (0) != iobj_fn)
+    __builtin_abort ();
+  // s.f_ix_c1
+  if (s.f_xi_m0 (0) != xobj_fn)
+    __builtin_abort ();
+  // s.f_xi_m1
+  if (s.f_xi_c0 (0) != xobj_fn)
+    __builtin_abort ();
+  // s.f_xi_c1
+  if (s.f_ix_m0_refqual (0) != iobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_ix_m1_refqual (0) != iobj_fn)
+    __builtin_abort ();
+  if (s.f_ix_c0_refqual (0) != iobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_ix_c1_refqual (0) != iobj_fn)
+    __builtin_abort ();
+  if (s.f_xi_m0_refqual (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xi_m1_refqual (0) != xobj_fn)
+    __builtin_abort ();
+  if (s.f_xi_c0_refqual (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xi_c1_refqual (0) != xobj_fn)
+    __builtin_abort ();
+  if (s.f_ix_m0_r (0) != xobj_fn)
+    __builtin_abort ();
+  // s.f_ix_m1_r
+  if (s.f_ix_c0_r (0) != xobj_fn)
+    __builtin_abort ();
+  // s.f_ix_c1_r
+  if (s.f_xi_m0_r (0) != iobj_fn)
+    __builtin_abort ();
+  // s.f_xi_m1_r
+  if (s.f_xi_c0_r (0) != iobj_fn)
+    __builtin_abort ();
+  // s.f_xi_c1_r
+  if (s.f_ix_m0_refqual_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_ix_m1_refqual_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (s.f_ix_c0_refqual_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_ix_c1_refqual_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (s.f_xi_m0_refqual_r (0) != iobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xi_m1_refqual_r (0) != iobj_fn)
+    __builtin_abort ();
+  if (s.f_xi_c0_refqual_r (0) != iobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xi_c1_refqual_r (0) != iobj_fn)
+    __builtin_abort ();
+
+
+/* Note:
+   These cases are weird, the object argument correspond, but are not the same
+   type ([basic.scope.scope-3.1]), so we get this funny edge case where the
+   constraint stops them from being considered redeclarations, but isn't taken
+   into account for the lvalue case. You can't bind an lvalue to an rvalue
+   reference so the iobj member function is always taken regardless of which
+   overload is constrained.
+
+   [over.match.funcs.general-4]
+   For implicit object member functions, the type of the implicit object
+   parameter is
+   (4.1) “lvalue reference to cv X” for functions declared without a
+	 ref-qualifier or with the & ref-qualifier
+
+   You would think that calling these functions with an rvalue would be the
+   same then, always taking the xobj member function. However, for backwards
+   compatibility reasons, an unqualified member function can be called on an
+   object that is an rvalue.
+
+   [over.match.funcs.general-5]
+   For implicit object member functions declared without a ref-qualifier, even
+   if the implicit object parameter is not const-qualified, an rvalue can be
+   bound to the parameter as long as in all other respects the argument can be
+   converted to the type of the implicit object parameter.
+
+   And finally, since the object parameters correspond ([basic.scope.scope-3.1])
+   the constraints are taken into account.
+
+   So in conclusion, calling these functions with an lvalue always resolves to
+   the iobj member function, and calling them with rvalues take the constraints
+   into account.
+
+   As wacky as this is, this is the correct behavior.  */
+
+  // Always takes the iobj member function, can't bind an lvalue to an rvalue
+  // reference.
+  if (s.f_ix_m1 (0) != iobj_fn)
+    __builtin_abort ();
+  if (s.f_ix_c1 (0) != iobj_fn)
+    __builtin_abort ();
+  if (s.f_xi_m1 (0) != iobj_fn)
+    __builtin_abort ();
+  if (s.f_xi_c1 (0) != iobj_fn)
+    __builtin_abort ();
+
+  if (s.f_ix_m1_r (0) != iobj_fn)
+    __builtin_abort ();
+  if (s.f_ix_c1_r (0) != iobj_fn)
+    __builtin_abort ();
+  if (s.f_xi_m1_r (0) != iobj_fn)
+    __builtin_abort ();
+  if (s.f_xi_c1_r (0) != iobj_fn)
+    __builtin_abort ();
+
+  // Constraints are taken into account here, see note for more information.
+  if (static_cast<S&&>(s).f_ix_m1 (0) != iobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_ix_c1 (0) != iobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xi_m1 (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xi_c1 (0) != xobj_fn)
+    __builtin_abort ();
+
+  if (static_cast<S&&>(s).f_ix_m1_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_ix_c1_r (0) != xobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xi_m1_r (0) != iobj_fn)
+    __builtin_abort ();
+  if (static_cast<S&&>(s).f_xi_c1_r (0) != iobj_fn)
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-A.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-A.C
new file mode 100644
index 00000000000..5043e91bb28
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-A.C
@@ -0,0 +1,7 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+struct S {
+    void f(this S); // { dg-bogus {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C
new file mode 100644
index 00000000000..fb2a6a0e41b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-B.C
@@ -0,0 +1,7 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+
+struct S {
+    void f(this S); // { dg-error {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C
new file mode 100644
index 00000000000..182e294c883
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-C.C
@@ -0,0 +1,9 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+// don't pass in -pedantic-errors
+// { dg-options "" }
+
+struct S {
+    void f(this S); // { dg-warning {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C
new file mode 100644
index 00000000000..49b7ea0df44
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-D.C
@@ -0,0 +1,8 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+// { dg-options "-Wno-c++23-extensions" }
+
+struct S {
+    void f(this S); // { dg-bogus {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C
new file mode 100644
index 00000000000..411b70c3d9d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-cxx-dialect-E.C
@@ -0,0 +1,8 @@
+// P0847R7
+// { dg-do compile { target c++20_down } }
+// { dg-options "-Wno-c++23-extensions -pedantic-errors" }
+
+struct S {
+    void f(this S); // { dg-bogus {explicit object member function only available with '-std=c\+\+23' or '-std=gnu\+\+23'} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C
new file mode 100644
index 00000000000..dfac1188fba
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics1.C
@@ -0,0 +1,139 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of xobj member functions that have member function qualifiers.
+
+struct S {
+    void f_value_0(this S) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_value_1(this S) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_value_2(this S) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_value_3(this S) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_value_4(this S) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_value_5(this S) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_6(this S) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_7(this S) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_8(this S) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_9(this S) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_value_A(this S) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_ref_0(this S&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_ref_1(this S&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_ref_2(this S&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_ref_3(this S&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_ref_4(this S&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_ref_5(this S&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_6(this S&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_7(this S&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_8(this S&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_9(this S&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_ref_A(this S&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_refref_0(this S&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_refref_1(this S&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_refref_2(this S&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_refref_3(this S&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_refref_4(this S&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_refref_5(this S&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_6(this S&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_7(this S&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_8(this S&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_9(this S&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_refref_A(this S&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_cref_0(this S const&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cref_1(this S const&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cref_2(this S const&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cref_3(this S const&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cref_4(this S const&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cref_5(this S const&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_6(this S const&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_7(this S const&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_8(this S const&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_9(this S const&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cref_A(this S const&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_crefref_0(this S const&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_crefref_1(this S const&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_crefref_2(this S const&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_crefref_3(this S const&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_crefref_4(this S const&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_crefref_5(this S const&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_6(this S const&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_7(this S const&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_8(this S const&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_9(this S const&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_crefref_A(this S const&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_vref_0(this S volatile&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vref_1(this S volatile&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vref_2(this S volatile&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vref_3(this S volatile&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vref_4(this S volatile&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vref_5(this S volatile&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_6(this S volatile&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_7(this S volatile&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_8(this S volatile&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_9(this S volatile&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vref_A(this S volatile&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_vrefref_0(this S volatile&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vrefref_1(this S volatile&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vrefref_2(this S volatile&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_vrefref_3(this S volatile&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vrefref_4(this S volatile&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_vrefref_5(this S volatile&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_6(this S volatile&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_7(this S volatile&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_8(this S volatile&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_9(this S volatile&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_vrefref_A(this S volatile&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_cvref_0(this S const volatile&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvref_1(this S const volatile&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvref_2(this S const volatile&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvref_3(this S const volatile&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvref_4(this S const volatile&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvref_5(this S const volatile&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_6(this S const volatile&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_7(this S const volatile&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_8(this S const volatile&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_9(this S const volatile&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvref_A(this S const volatile&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void f_cvrefref_0(this S const volatile&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvrefref_1(this S const volatile&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvrefref_2(this S const volatile&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void f_cvrefref_3(this S const volatile&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvrefref_4(this S const volatile&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void f_cvrefref_5(this S const volatile&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_6(this S const volatile&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_7(this S const volatile&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_8(this S const volatile&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_9(this S const volatile&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void f_cvrefref_A(this S const volatile&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    template<typename Self> void d_templ_0(this Self&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    template<typename Self> void d_templ_1(this Self&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    template<typename Self> void d_templ_2(this Self&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    template<typename Self> void d_templ_3(this Self&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    template<typename Self> void d_templ_4(this Self&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    template<typename Self> void d_templ_5(this Self&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_6(this Self&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_7(this Self&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_8(this Self&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_9(this Self&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    template<typename Self> void d_templ_A(this Self&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+
+    void d_auto_0(this auto&&) const;             // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void d_auto_1(this auto&&) volatile;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void d_auto_2(this auto&&) const volatile;    // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" }
+    void d_auto_3(this auto&&) &;                 // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void d_auto_4(this auto&&) &&;                // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" }
+    void d_auto_5(this auto&&) const &;           // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_6(this auto&&) const &&;          // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_7(this auto&&) volatile &;        // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_8(this auto&&) volatile &&;       // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_9(this auto&&) const volatile &;  // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+    void d_auto_A(this auto&&) const volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C
new file mode 100644
index 00000000000..771200b839e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics2.C
@@ -0,0 +1,26 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of incorrect uses of 'this' in declarations and definitions
+
+using func_type = void(this int); // { dg-line func_type_line }
+// { dg-error "a function type cannot have an explicit object parameter" "" { target *-*-* } func_type_line }
+// { dg-note "the type of an explicit object member function is a regular function type" "" { target *-*-* } func_type_line }
+
+using func_ptr_type = void(*)(this int); // { dg-line func_ptr_type_line }
+// { dg-error "a pointer to function type cannot have an explicit object parameter" "" { target *-*-* } func_ptr_type_line }
+// { dg-note "the type of a pointer to explicit object member function is a regular pointer to function type" "" { target *-*-* } func_ptr_type_line }
+
+struct S {
+    static void f(this S) {} // { dg-line static_member_func_line }
+};
+// { dg-error "an explicit object member function cannot be 'static'" "" { target *-*-* } static_member_func_line }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } static_member_func_line }
+
+using mem_func_type = void (S::*)(this S&); // { dg-line mem_func_type_line }
+// { dg-error "a pointer to member function type cannot have an explicit object parameter" "" { target *-*-* } mem_func_type_line }
+// { dg-note "the type of a pointer to explicit object member function is a regular pointer to function type" "" { target *-*-* } mem_func_type_line }
+
+void f(this int); // { dg-error "a non-member function cannot have an explicit object parameter" }
+void f(this int) {} // { dg-error "a non-member function cannot have an explicit object parameter" }
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C
new file mode 100644
index 00000000000..ec091d6ca67
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics4.C
@@ -0,0 +1,20 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of an xobj parameter declared with a default argument
+
+struct S {
+  void f0(this S = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+  void f1(this S = {}); // { dg-error "an explicit object parameter may not have a default argument" }
+  void f2(this S);
+  void f10(this S s = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+  void f11(this S s = {}); // { dg-error "an explicit object parameter may not have a default argument" }
+  void f12(this S s);
+};
+
+void S::f1(this S) {}
+void S::f2(this S = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+
+void S::f11(this S s) {}
+void S::f12(this S s = {}) {} // { dg-error "an explicit object parameter may not have a default argument" }
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C
new file mode 100644
index 00000000000..1744b3f2299
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics5.C
@@ -0,0 +1,16 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// location diagnostic text when an error is emitted from an xobj member function
+// this does not test for specific ill-formed code, just the additional diagnostic message
+
+// { dg-message "In explicit object member function" "" { target *-*-* } 0 }
+
+struct S {
+  void f(this S s) {
+    // The specific diagnosis issued here does not matter
+    // we just need to force an error to be emitted
+    +s; // { dg-error "" }
+  }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C
new file mode 100644
index 00000000000..f3b01d3c8ae
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics6.C
@@ -0,0 +1,23 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis of invalid uses of 'this' in body of xobj member functions
+
+// { dg-message "In explicit object member function" "" { target *-*-* } 0 }
+
+struct S0 {
+  int _n;
+  void f(this S0& s) { // { dg-note "use explicit object parameter 's' instead" } 
+    this->_n = 10; // { dg-error "'this' is unavailable for explicit object member functions" }
+    // suppress unused variable warning
+    static_cast<void>(s);
+  }
+};
+
+struct S1 {
+  int _n;
+  void f(this S1&) { // { dg-note "name and use the explicit object parameter instead" }
+    this->_n = 10; // { dg-error "'this' is unavailable for explicit object member functions" }
+  }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C
new file mode 100644
index 00000000000..ea53609a1e0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-diagnostics7.C
@@ -0,0 +1,17 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// rejection and diagnosis when taking address of an unqualified xobj member function
+
+struct S {
+  void f(this S&) {}
+
+  void g(this S&) {}
+  void g(this S&, int) {}
+
+  void test() {
+    void (*fp)(S&) = &f; // { dg-error "taking the address of an explicit object member function must be qualified" }
+    void (*gp)(S&) = &g; // { dg-error "taking the address of an explicit object member function must be qualified" }
+  }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda-temp1.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda-temp1.C
new file mode 100644
index 00000000000..e7b6785c94e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda-temp1.C
@@ -0,0 +1,73 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// various valid cases of lambdas with dependent xobj parameter
+
+// unfinished, I want to flesh this out more but I don't want to send the patch
+// without tests that demonstrate the fixed crash and behavior
+// basically I want to add more wacky edge cases
+
+template<typename... Ts>
+struct overload : Ts... {
+  using Ts::operator()...;
+};
+
+template<template<typename...> typename Overload>
+void templ()
+{
+  int i = 5;
+  auto f0 = Overload{[=]<typename... Ts>(this Overload<Ts...>&){ return i; }};
+  auto f1 = Overload{[&]<typename... Ts>(this Overload<Ts...>&){ return i; }};
+  auto f2 = Overload{[i]<typename... Ts>(this Overload<Ts...>&){ return i; }};
+  int f_return0 = f0();
+  int f_return1 = f1();
+  int f_return2 = f2();
+
+  auto g0 = Overload{[=]<typename... Ts>(this Overload<Ts...>&, auto){ return i; }};
+  auto g1 = Overload{[&]<typename... Ts>(this Overload<Ts...>&, auto){ return i; }};
+  auto g2 = Overload{[i]<typename... Ts>(this Overload<Ts...>&, auto){ return i; }};
+  int g_return0 = g0(0);
+  int g_return1 = g1(0);
+  int g_return2 = g2(0);
+}
+
+template<typename T>
+struct convertible_to_type {
+  constexpr operator T() { return {}; }
+};
+
+template<typename T, typename C>
+struct helper : T, C {
+  using T::operator();
+};
+
+template<typename StructT>
+struct S {
+  static constexpr auto member0 = [](this StructT const&){};
+  static constexpr auto member1 = helper{[](this StructT self){ return self; }, convertible_to_type<StructT>{}};
+
+  template<typename FuncT>
+  void member_f() {
+    auto f = [](){};
+  }
+};
+
+template<typename T>
+void type()
+{
+
+}
+
+
+void instantiate()
+{
+  templ<overload>();
+  type<int>();
+}
+
+int main()
+{
+  auto member1_copy = S<int>::member1;
+  return member1_copy();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda-temp2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda-temp2.C
new file mode 100644
index 00000000000..57ea1140d18
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda-temp2.C
@@ -0,0 +1,38 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// various invalid cases of lambdas with dependent xobj parameter
+
+// unfinished, I want to flesh this out more but I don't want to send the patch
+// without tests that demonstrate the fixed crash and behavior
+// basically I want to add more wacky edge cases
+
+template<typename T>
+void type()
+{
+  int i;
+  auto f0 = [=](this T& self){ return i; }; // { dg-error {a lambda with captures may not have an explicit object parameter of an unrelated type} }
+  auto f1 = [&](this T& self){ return i; }; // { dg-error {a lambda with captures may not have an explicit object parameter of an unrelated type} }
+  auto f2 = [i](this T& self){ return i; }; // { dg-error {a lambda with captures may not have an explicit object parameter of an unrelated type} }
+
+  auto g0 = [=](this T& self, auto){ return i; }; // { dg-error {a lambda with captures may not have an explicit object parameter of an unrelated type} }
+  auto g1 = [&](this T& self, auto){ return i; }; // { dg-error {a lambda with captures may not have an explicit object parameter of an unrelated type} }
+  auto g2 = [i](this T& self, auto){ return i; }; // { dg-error {a lambda with captures may not have an explicit object parameter of an unrelated type} }
+}
+
+template<typename StructT>
+struct S {
+  static constexpr auto member0 = [n = 5](this StructT const&){}; // { dg-error {a lambda with captures may not have an explicit object parameter of an unrelated type} }
+
+  template<typename FuncT>
+  void member_f() {
+    auto f = [](){};
+  }
+};
+
+void instantiate()
+{
+  type<int>();
+  auto s = S<int>{};
+}
+
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..86e0471eb7f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda1.C
@@ -0,0 +1,25 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// lambda declaration with xobj parameter
+
+struct S{};
+
+void test()
+{
+  (void)[](this auto&& self){};
+  (void)[](this auto& self){};
+  (void)[](this auto const& self){};
+  (void)[](this auto self){};
+
+  (void)[](this S&& self){};
+  (void)[](this S& self){};
+  (void)[](this S const& self){};
+  (void)[](this S self){};
+
+  (void)[x = 0](this auto&& self){};
+  (void)[x = 0](this auto& self){};
+  (void)[x = 0](this auto const& self){};
+  (void)[x = 0](this auto self){};
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C
new file mode 100644
index 00000000000..827197a6667
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda2.C
@@ -0,0 +1,23 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// recursive lambdas
+
+inline constexpr int correct_result = 5 + 4 + 3 + 2 + 1; 
+
+int main()
+{
+  auto cl0 = [](this auto&& self, int n)      -> int { return n ? self(n - 1) + n : 0; };
+  auto cl1 = [](this auto const& self, int n) -> int { return n ? self(n - 1) + n : 0; };
+  auto cl2 = [](this auto self, int n)        -> int { return n ? self(n - 1) + n : 0; };
+  auto cl3 = [](this auto&& self, int n)     { if (!n) return 0; else return self(n - 1) + n; };
+  auto cl4 = [](this auto const& self, int n){ if (!n) return 0; else return self(n - 1) + n; };
+  auto cl5 = [](this auto self, int n)       { if (!n) return 0; else return self(n - 1) + n; };
+  if (cl0(5) != correct_result) __builtin_abort ();
+  if (cl1(5) != correct_result) __builtin_abort ();
+  if (cl2(5) != correct_result) __builtin_abort ();
+  if (cl3(5) != correct_result) __builtin_abort ();
+  if (cl4(5) != correct_result) __builtin_abort ();
+  if (cl5(5) != correct_result) __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda3.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda3.C
new file mode 100644
index 00000000000..9d222b0e547
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda3.C
@@ -0,0 +1,64 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// an adaptation of one of the examples in P0847R7
+
+struct Leaf { };
+struct Node;
+
+struct Tree {
+  enum class stored {leaf, node};
+  stored _discriminator;
+  union {
+    Leaf _leaf;
+    Node* _node;
+  };
+  Tree(Leaf) : _discriminator(stored::leaf), _leaf() {}
+  Tree(Node& node) : _discriminator(stored::node), _node(&node) {}
+};
+
+struct Node {
+    Tree left;
+    Tree right;
+};
+
+template<typename Visitor>
+auto visit_tree(Visitor&& visitor, Tree const& tree)
+{
+  switch (tree._discriminator)
+  {
+    case Tree::stored::leaf:
+      return visitor (tree._leaf);
+    case Tree::stored::node:
+      return visitor (tree._node);
+    default:
+      __builtin_abort (); 
+  }
+}
+
+template<typename... Ts>
+struct overload : Ts... { using Ts::operator()...; };
+
+int main()
+{
+  static constexpr int true_num_leaves = 8;
+  Node branch0{.left = Leaf{}, .right = Leaf{}};
+  Node branch1{.left = Leaf{}, .right = branch0};
+  Node branch2{.left = Leaf{}, .right = Leaf{}};
+  Node branch3{.left = branch1, .right = branch2};
+  Node branch4{.left = branch3, .right = Leaf{}};
+  Node branch5{.left = Leaf{}, .right = Leaf{}};
+  Node branch6{.left = branch4, .right = branch5};
+
+  Tree root (branch6);
+
+  int num_leaves = visit_tree (overload{
+    [](Leaf const&) { return 1; },
+    [](this auto const& self, Node* n) -> int {
+      return visit_tree (self, n->left) + visit_tree (self, n->right);
+    }},
+    root);
+  if (num_leaves != true_num_leaves)
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX0.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX0.C
new file mode 100644
index 00000000000..c9776092978
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX0.C
@@ -0,0 +1,68 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// xobj lambda with invalid decl specs
+
+void test()
+{
+  auto f0 = [](this auto) mutable {}; // { dg-line line_f0 }
+  auto f1 = [](this auto&) mutable {}; // { dg-line line_f1 }
+  auto f2 = [](this auto const&) mutable {}; // { dg-line line_f2 }
+  auto f3 = [](this auto&&) mutable {}; // { dg-line line_f3 }
+
+  auto g0 = [](this auto) static {}; // { dg-line line_g0 }
+  auto g1 = [](this auto&) static {}; // { dg-line line_g1 }
+  auto g2 = [](this auto const&) static {}; // { dg-line line_g2 }
+  auto g3 = [](this auto&&) static {}; // { dg-line line_g3 }
+
+  auto fc0 = [n = 0](this auto) mutable {}; // { dg-line line_fc0 }
+  auto fc1 = [n = 0](this auto&) mutable {}; // { dg-line line_fc1 }
+  auto fc2 = [n = 0](this auto const&) mutable {}; // { dg-line line_fc2 }
+  auto fc3 = [n = 0](this auto&&) mutable {}; // { dg-line line_fc3 }
+
+  auto gc0 = [n = 0](this auto) static {}; // { dg-line line_gc0 }
+  auto gc1 = [n = 0](this auto&) static {}; // { dg-line line_gc1 }
+  auto gc2 = [n = 0](this auto const&) static {}; // { dg-line line_gc2 }
+  auto gc3 = [n = 0](this auto&&) static {}; // { dg-line line_gc3 }
+}
+
+// { dg-error {'mutable' lambda specifier with explicit object parameter} {} { target *-*-* } line_f0 }
+// { dg-error {'mutable' lambda specifier with explicit object parameter} {} { target *-*-* } line_f1 }
+// { dg-error {'mutable' lambda specifier with explicit object parameter} {} { target *-*-* } line_f2 }
+// { dg-error {'mutable' lambda specifier with explicit object parameter} {} { target *-*-* } line_f3 }
+
+// { dg-note {the passed in closure object will not be mutated because it is taken by copy/move} {} { target *-*-* } line_f0 }
+// { dg-note {explicit object parameter is already a mutable reference} {} { target *-*-* } line_f1 }
+// { dg-note {declare the explicit object parameter as non-const reference instead} {} { target *-*-* } line_f2 }
+// { dg-note {explicit object parameter is already a mutable reference} {} { target *-*-* } line_f3 }
+
+// { dg-error {'static' lambda specifier with explicit object parameter} {} { target *-*-* } line_g0 }
+// { dg-error {'static' lambda specifier with explicit object parameter} {} { target *-*-* } line_g1 }
+// { dg-error {'static' lambda specifier with explicit object parameter} {} { target *-*-* } line_g2 }
+// { dg-error {'static' lambda specifier with explicit object parameter} {} { target *-*-* } line_g3 }
+
+// { dg-note {explicit object parameter declared here} {} { target *-*-* } line_g0 }
+// { dg-note {explicit object parameter declared here} {} { target *-*-* } line_g1 }
+// { dg-note {explicit object parameter declared here} {} { target *-*-* } line_g2 }
+// { dg-note {explicit object parameter declared here} {} { target *-*-* } line_g3 }
+
+// { dg-error {'mutable' lambda specifier with explicit object parameter} {} { target *-*-* } line_fc0 }
+// { dg-error {'mutable' lambda specifier with explicit object parameter} {} { target *-*-* } line_fc1 }
+// { dg-error {'mutable' lambda specifier with explicit object parameter} {} { target *-*-* } line_fc2 }
+// { dg-error {'mutable' lambda specifier with explicit object parameter} {} { target *-*-* } line_fc3 }
+
+// { dg-note {the passed in closure object will not be mutated because it is taken by copy/move} {} { target *-*-* } line_fc0 }
+// { dg-note {explicit object parameter is already a mutable reference} {} { target *-*-* } line_fc1 }
+// { dg-note {declare the explicit object parameter as non-const reference instead} {} { target *-*-* } line_fc2 }
+// { dg-note {explicit object parameter is already a mutable reference} {} { target *-*-* } line_fc3 }
+
+// { dg-error {'static' lambda specifier with explicit object parameter} {} { target *-*-* } line_gc0 }
+// { dg-error {'static' lambda specifier with explicit object parameter} {} { target *-*-* } line_gc1 }
+// { dg-error {'static' lambda specifier with explicit object parameter} {} { target *-*-* } line_gc2 }
+// { dg-error {'static' lambda specifier with explicit object parameter} {} { target *-*-* } line_gc3 }
+
+// { dg-note {explicit object parameter declared here} {} { target *-*-* } line_gc0 }
+// { dg-note {explicit object parameter declared here} {} { target *-*-* } line_gc1 }
+// { dg-note {explicit object parameter declared here} {} { target *-*-* } line_gc2 }
+// { dg-note {explicit object parameter declared here} {} { target *-*-* } line_gc3 }
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX20.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX20.C
new file mode 100644
index 00000000000..a808019e28e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX20.C
@@ -0,0 +1,46 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// calling captureless lambda call operator with unrelated explicit object parameter
+// through function pointer
+
+int main()
+{
+  auto f0 = [](this auto self) { return self; };
+  auto fp0_value     = static_cast<int(*)(int)        >(&decltype(f0)::operator());
+  auto fp0_lref      = static_cast<int(*)(int&)       >(&decltype(f0)::operator());
+  auto fp0_rref      = static_cast<int(*)(int&&)      >(&decltype(f0)::operator());
+  auto fp0_constlref = static_cast<int(*)(int const&) >(&decltype(f0)::operator());
+  auto fp0_constrref = static_cast<int(*)(int const&&)>(&decltype(f0)::operator());
+
+  auto f1 = [](this auto&& self) { return self; };
+  auto fp1_lref      = static_cast<int(*)(int&)       >(&decltype(f1)::operator());
+  auto fp1_rref      = static_cast<int(*)(int&&)      >(&decltype(f1)::operator());
+  auto fp1_constlref = static_cast<int(*)(int const&) >(&decltype(f1)::operator());
+  auto fp1_constrref = static_cast<int(*)(int const&&)>(&decltype(f1)::operator());
+
+  // both are needed for lvalue/rvalue overloads
+  #define MAGIC 42
+  int magic = MAGIC;
+
+  if (fp0_value (magic) != magic)
+    __builtin_abort ();
+  if (fp0_lref (magic) != magic)
+    __builtin_abort ();
+  if (fp0_rref (MAGIC) != magic)
+    __builtin_abort ();
+  if (fp0_constlref (magic) != magic)
+    __builtin_abort ();
+  if (fp0_constrref (MAGIC) != magic)
+    __builtin_abort ();
+
+  if (fp1_lref (magic) != magic)
+    __builtin_abort ();
+  if (fp1_rref (MAGIC) != magic)
+    __builtin_abort ();
+  if (fp1_constlref (magic) != magic)
+    __builtin_abort ();
+  if (fp1_constrref (MAGIC) != magic)
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX21.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX21.C
new file mode 100644
index 00000000000..715a2457945
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX21.C
@@ -0,0 +1,39 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// instantiating captureless lambda call operator with unrelated explicit object parameter
+
+void test0()
+{
+  auto f0 = [](this auto self) { return self; };
+  auto fp0_value     = static_cast<int(*)(int)        >(&decltype(f0)::operator());
+  auto fp0_lref      = static_cast<int(*)(int&)       >(&decltype(f0)::operator());
+  auto fp0_rref      = static_cast<int(*)(int&&)      >(&decltype(f0)::operator());
+  auto fp0_constlref = static_cast<int(*)(int const&) >(&decltype(f0)::operator());
+  auto fp0_constrref = static_cast<int(*)(int const&&)>(&decltype(f0)::operator());
+
+  auto f1 = [](this auto&& self) { return self; };
+  auto fp1_value      = static_cast<int(*)(int)        >(&decltype(f1)::operator()); // { dg-error {invalid 'static_cast' from type} }
+  auto fp1_lref       = static_cast<int(*)(int&)       >(&decltype(f1)::operator());
+  auto fp1_rref       = static_cast<int(*)(int&&)      >(&decltype(f1)::operator());
+  auto fp1_constlref  = static_cast<int(*)(int const&) >(&decltype(f1)::operator());
+  auto fp1_constrref  = static_cast<int(*)(int const&&)>(&decltype(f1)::operator());
+}
+
+void test1()
+{
+  auto f0 = [](this auto self) { return self; };
+  int (*fp0_value)(int)             = &decltype(f0)::operator();
+  int (*fp0_lref)(int&)             = &decltype(f0)::operator();
+  int (*fp0_rref)(int&&)            = &decltype(f0)::operator();
+  int (*fp0_constlref)(int const&)  = &decltype(f0)::operator();
+  int (*fp0_constrref)(int const&&) = &decltype(f0)::operator();
+
+  auto f1 = [](this auto&& self) { return self; };
+  int (*fp1_value)(int)              = &decltype(f1)::operator(); // { dg-error {no matches converting function} }
+  int (*fp1_lref)(int&)              = &decltype(f1)::operator();
+  int (*fp1_rref)(int&&)             = &decltype(f1)::operator();
+  int (*fp1_constlref)(int const&)   = &decltype(f1)::operator();
+  int (*fp1_constrref)(int const&&)  = &decltype(f1)::operator();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX24.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX24.C
new file mode 100644
index 00000000000..e8d348a7e7d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX24.C
@@ -0,0 +1,98 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// SFINAE when the call operator for a lambda with captures is instantiated
+// with an unrelated type.
+// diagnose ambiguous overloads when the call operator for a captureless lambda is instantiated
+// with an unrelated type.
+
+/* [expr.prim.lambda.general-5]
+
+   Given a lambda with a lambda-capture, the type of the explicit object
+   parameter, if any, of the lambda's function call operator (possibly
+   instantiated from a function call operator template) shall be either:
+
+   --(5.1) the closure type,
+   --(5.2) a class type derived from the closure type, or
+   --(5.3) a reference to a possibly cv-qualified such type.  */
+
+// The above wording is similar to [dcl.fct-15] which is handled by SFINAE,
+// thus we also handle the following cases the same way.
+
+// We need the 2 overloads to be ambiguous to observe substitution failure
+// for the lambda's call operator when instantiated with an unrelated type.
+// We accomplish this by introducing both overloads through using declarations.
+
+struct B0 {
+  void operator()(this auto) {}
+};
+
+template<typename T>
+struct S0 : T, B0 {
+  using T::operator();
+  using B0::operator();
+  operator int() const {return {};}
+};
+
+void test0()
+{
+  auto s0 = S0{[](this auto){}};
+  s0.operator()<int>(); // { dg-error {call of overloaded 'operator\(\)\(\)' is ambiguous} }
+
+  auto s1 = S0{[x = 42](this auto){}};
+  s1.operator()<int>(); // { dg-bogus {call of overloaded 'operator\(\)\(\)' is ambiguous} }
+}
+
+
+struct B1 {
+  void operator()(this auto&&) {}
+};
+template<typename T>
+struct S1 : T, B1 {
+  using T::operator();
+  using B1::operator();
+  operator int() const {return {};}
+};
+
+void test1()
+{
+  auto s0 = S1{[](this auto&&){}};
+  s0.operator()<int>(); // { dg-error {call of overloaded 'operator\(\)\(\)' is ambiguous} }
+
+  auto s1 = S1{[x = 42](this auto&&){}};
+  s1.operator()<int>(); // { dg-bogus {call of overloaded 'operator\(\)\(\)' is ambiguous} }
+}
+
+
+struct B2 {
+  // needs to be a template, we are explicitly passing a template argument,
+  // without the parameter here this would not be a candidate
+  template<typename U = void>
+  void operator()(this int) {}
+};
+
+template<typename T>
+struct S2 : T, B2 {
+  using T::operator();
+  using B2::operator();
+  operator int() const {return {};}
+};
+
+void test2()
+{
+  auto s0 = S2{[](this auto){}};
+  s0.operator()<int>(); // { dg-error {call of overloaded 'operator\(\)\(\)' is ambiguous} }
+
+  auto s1 = S2{[x = 42](this auto){}};
+  s1.operator()<int>(); // { dg-bogus {call of overloaded 'operator\(\)\(\)' is ambiguous} }
+}
+
+void test3()
+{
+  auto s0 = S2{[](this auto&&){}};
+  s0.operator()<int>(); // { dg-error {call of overloaded 'operator\(\)\(\)' is ambiguous} }
+
+  auto s1 = S2{[x = 42](this auto&&){}};
+  s1.operator()<int>(); // { dg-bogus {call of overloaded 'operator\(\)\(\)' is ambiguous} }
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX25.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX25.C
new file mode 100644
index 00000000000..b25fa006f1f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX25.C
@@ -0,0 +1,101 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// SFINAE when the call operator for a lambda with captures is instantiated
+// with an unrelated type.
+// diagnose ambiguous overloads when the call operator for a captureless lambda is instantiated
+// with an unrelated type.
+
+/* [expr.prim.lambda.general-5]
+
+   Given a lambda with a lambda-capture, the type of the explicit object
+   parameter, if any, of the lambda's function call operator (possibly
+   instantiated from a function call operator template) shall be either:
+
+   --(5.1) the closure type,
+   --(5.2) a class type derived from the closure type, or
+   --(5.3) a reference to a possibly cv-qualified such type.  */
+
+// The above wording is similar to [dcl.fct-15] which is handled by SFINAE,
+// thus we also handle the following cases the same way.
+
+// We need the 2 overloads to be ambiguous to observe substitution failure
+// for the lambda's call operator when instantiated with an unrelated type.
+// We accomplish this by introducing both overloads through using declarations.
+
+struct B0 {
+  void operator()(this auto) {}
+};
+template<typename T>
+struct S0 : T, B0 {
+  using B0::operator();
+  using T::operator();
+};
+
+void test0()
+{
+  auto s0 = S0{[](this auto){}};
+  void (*p0)(int) = &decltype(s0)::operator(); // { dg-error {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+
+  auto s1 = S0{[x = 42](this auto){}};
+  void (*p1)(int) = &decltype(s1)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} {Substitution failure for a captureful lambda with an unrelated xobj parameter type failed!} }
+}
+
+struct B1 {
+  void operator()(this auto&&) {}
+};
+template<typename T>
+struct S1 : T, B1 {
+  using B1::operator();
+  using T::operator();
+};
+
+void test1()
+{
+  auto s0 = S1{[](this auto&&){}};
+  void (*p0)(int&) = &decltype(s0)::operator(); // { dg-error {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+
+  auto s1 = S1{[x = 42](this auto&&){}};
+  void (*p1)(int&) = &decltype(s1)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} {Substitution failure for a captureful lambda with an unrelated xobj parameter type failed!} }
+}
+
+
+struct B2 {
+  // not a template, should be taken over the lambda's call operator
+  void operator()(this int&) {}
+};
+template<typename T>
+struct S2 : T, B2 {
+  using T::operator();
+  using B2::operator();
+};
+
+void test2()
+{
+  auto s0 = S2{[](this auto&&){}};
+  void (*p0)(int&) = &decltype(s0)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+
+  auto s1 = S2{[x = 42](this auto&&){}};
+  void (*p1)(int&) = &decltype(s1)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+}
+
+struct B3 {
+  // must be a template so it is not taken over the lambda's call operator
+  template<typename U = void>
+  void operator()(this int&) {}
+};
+template<typename T>
+struct S3 : T, B3 {
+  using B3::operator();
+  using T::operator();
+};
+
+void test3()
+{
+  auto s0 = S3{[](this auto&&){}};
+  void (*p0)(int&) = &decltype(s0)::operator(); // { dg-error {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} }
+
+  auto s1 = S3{[x = 42](this auto&&){}};
+  void (*p1)(int&) = &decltype(s1)::operator(); // { dg-bogus {converting overloaded function '[^\n\r]+' to type '[^\n\r]+' is ambiguous} {Substitution failure for a captureful lambda with an unrelated xobj parameter type failed!} }
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX4.C
new file mode 100644
index 00000000000..6ce42ebefa8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX4.C
@@ -0,0 +1,23 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// calls to call operator of a lambda with captures with an implicit object argument
+// that derives from the lambda closure object
+
+template<typename T>
+struct S : T {
+    using T::operator();
+};
+
+template<typename T>
+S(T) -> S<T>; 
+
+int main()
+{
+  static constexpr int magic = 42;
+  int n = magic;
+  S s{[n](this auto&&){return n;}};
+  if (s () != magic)
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX40.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX40.C
new file mode 100644
index 00000000000..1e10fb26675
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX40.C
@@ -0,0 +1,20 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// diagnose mutation of lambda capture when called with a deduced as const explicit object parameter
+
+void test()
+{
+  auto f0 = [n = 5](this auto){ n = 10; }; // { dg-bogus {assignment of read-only variable} }
+  auto f1 = [n = 5](this auto const){ n = 10; }; // { dg-error {assignment of read-only variable} }
+  auto f2 = [n = 5](this auto&){ n = 10; };  // { dg-error {assignment of read-only variable} }
+  auto f3 = [n = 5](this auto const&){ n = 10; }; // { dg-error {assignment of read-only variable} }
+  auto f4 = [n = 5](this auto&&){ n = 10; };  // { dg-error {assignment of read-only variable} }
+
+  static_cast<decltype(f0) const&>(f0)();
+  f1();
+  static_cast<decltype(f2) const&>(f2)();
+  f3();
+  static_cast<decltype(f4) const&>(f4)();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX5.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX5.C
new file mode 100644
index 00000000000..88d45d90db1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX5.C
@@ -0,0 +1,21 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// calls to (captureless) lambda with explicit object parameter of unrelated type
+// with an appropriate converting constructor
+
+inline constexpr int magic = 42;
+
+struct S {
+  int _v;  
+  template<typename T>
+  S(T) : _v(magic) {}
+};
+
+int main()
+{
+  auto f = [](this S self){ return self._v; };
+  if (f () != magic)
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX6.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX6.C
new file mode 100644
index 00000000000..92f025431c1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX6.C
@@ -0,0 +1,48 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// unrelated xobj parameter type in captureless lambdas and lambdas with captures
+
+// declared with unrelated type
+
+struct S0{};
+
+void test0()
+{
+  auto f0 = [](this S0){ return 5; }; // { dg-bogus "a lambda with captures may not have an explicit object parameter of an unrelated type" }
+  auto f1 = [x = 42](this S0){ return 5; }; // { dg-error "a lambda with captures may not have an explicit object parameter of an unrelated type" }
+}
+
+// instantiation by calling with explicit template arguments
+
+template<typename T>
+struct S1 : T {
+  using T::operator();
+  operator int() const {return {};}
+};
+
+void test1()
+{
+  auto s0 = S1{[](this auto&& self) { return self; }}; // { dg-bogus {a lambda with captures may not have an explicit object parameter of an unrelated type} }
+  s0.operator()<int>(); // { dg-bogus {no matching function for call to} }
+
+  auto s1 = S1{[x = 0](this auto&& self) { return self; }}; // { dg-line t1_s1 }
+  s1.operator()<int>(); // { dg-error {no matching function for call to} }
+}
+// { dg-note {candidate:} {} { target *-*-* } t1_s1 }
+// { dg-note {template argument deduction/substitution failed} {} { target *-*-* } t1_s1 }
+// { dg-error {a lambda with captures may not have an explicit object parameter of an unrelated type} {} { target *-*-* } t1_s1 }
+
+// instantiation from overload resolution when taking address of call operator
+
+void test2()
+{
+  auto f = [x = 42](this auto&&){ return x; }; // { dg-line t2_f }
+
+  int (*fp0)(decltype(f)&) = &decltype(f)::operator();
+  int (*fp1)(int&) = &decltype(f)::operator(); // { dg-error {no matches converting function} }
+}
+
+// { dg-error "a lambda with captures may not have an explicit object parameter of an unrelated type" {depends on PR112874} { xfail *-*-* } t2_f }
+// { dg-note "candidate is" "" { target *-*-* } t2_f }
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX60.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX60.C
new file mode 100644
index 00000000000..7537b25865a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX60.C
@@ -0,0 +1,279 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// decltype((x)) and decltype(x) in explicit object lambda
+
+template<typename T> inline constexpr bool is_const_v = false;
+template<typename T> inline constexpr bool is_const_v<T const> = true;
+
+template<typename T> inline constexpr bool is_lvalue_ref = false;
+template<typename T> inline constexpr bool is_lvalue_ref<T&> = true;
+
+void non_dep()
+{
+  int n = 0;
+  auto f0 = [=]<typename Self>(this Self&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<Self> == is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f0();
+  static_cast<decltype(f0) const&>(f0)();
+
+  auto f1 = [&]<typename Self>(this Self&){
+    static_assert(__is_same (decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f1();
+  static_cast<decltype(f1) const&>(f1)();
+
+  auto f2 = [n]<typename Self>(this Self&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<Self> == is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f2();
+  static_cast<decltype(f2) const&>(f2)();
+
+  auto f3 = [&n]<typename Self>(this Self&){
+    static_assert(__is_same(decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f3();
+  static_cast<decltype(f3) const&>(f3)();
+
+  auto f4 = []<typename Self>(this Self&){
+    static_assert(__is_same (decltype(n), int));
+    decltype((n)) a; // { dg-error {is not captured} }
+  };
+
+  auto f0_const = [=]<typename Self>(this Self const&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f0_const();
+
+  auto f1_const = [&]<typename Self>(this Self const&){
+    static_assert(__is_same (decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f1_const();
+
+  auto f2_const = [n]<typename Self>(this Self const&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f2_const();
+
+  auto f3_const = [&n]<typename Self>(this Self const&){
+    static_assert(__is_same(decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f3_const();
+
+  auto f4_const = []<typename Self>(this Self const&){
+    static_assert(__is_same (decltype(n), int));
+    decltype((n)) a; // { dg-error {is not captured} }
+  };
+}
+
+template<typename = void>
+void dep0()
+{
+  int n = 0;
+  auto f0 = [=]<typename Self>(this Self&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<Self> == is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f0();
+  static_cast<decltype(f0) const&>(f0)();
+
+  auto f1 = [&]<typename Self>(this Self&){
+    static_assert(__is_same (decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f1();
+  static_cast<decltype(f1) const&>(f1)();
+
+  auto f2 = [n]<typename Self>(this Self&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<Self> == is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f2();
+  static_cast<decltype(f2) const&>(f2)();
+
+  auto f3 = [&n]<typename Self>(this Self&){
+    static_assert(__is_same(decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f3();
+  static_cast<decltype(f3) const&>(f3)();
+
+  auto f4 = []<typename Self>(this Self&){
+    static_assert(__is_same (decltype(n), int));
+    decltype((n)) a; // { dg-error {is not captured} }
+  };
+
+  auto f0_const = [=]<typename Self>(this Self const&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f0_const();
+
+  auto f1_const = [&]<typename Self>(this Self const&){
+    static_assert(__is_same (decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f1_const();
+
+  auto f2_const = [n]<typename Self>(this Self const&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f2_const();
+
+  auto f3_const = [&n]<typename Self>(this Self const&){
+    static_assert(__is_same(decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f3_const();
+
+  auto f4_const = []<typename Self>(this Self const&){
+    static_assert(__is_same (decltype(n), int));
+    decltype((n)) a; // { dg-error {is not captured} }
+  };
+}
+
+// dep1 uses the template parameter
+
+template<typename T = int>
+void dep1()
+{
+  T n = 0;
+  auto f0 = [=]<typename Self>(this Self&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<Self> == is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f0();
+  static_cast<decltype(f0) const&>(f0)();
+
+  auto f1 = [&]<typename Self>(this Self&){
+    static_assert(__is_same (decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f1();
+  static_cast<decltype(f1) const&>(f1)();
+
+  auto f2 = [n]<typename Self>(this Self&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<Self> == is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f2();
+  static_cast<decltype(f2) const&>(f2)();
+
+  auto f3 = [&n]<typename Self>(this Self&){
+    static_assert(__is_same(decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f3();
+  static_cast<decltype(f3) const&>(f3)();
+
+  auto f4 = []<typename Self>(this Self&){
+    static_assert(__is_same (decltype(n), int));
+    decltype((n)) a; // { dg-error {is not captured} }
+  };
+
+  auto f0_const = [=]<typename Self>(this Self const&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f0_const();
+
+  auto f1_const = [&]<typename Self>(this Self const&){
+    static_assert(__is_same (decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f1_const();
+
+  auto f2_const = [n]<typename Self>(this Self const&){
+    static_assert(is_lvalue_ref<decltype((n))>,
+		  "decltype((n)) is not an lvalue ref");
+    static_assert(is_const_v<__remove_reference (decltype((n)))>, // { dg-bogus {static assertion failed: qualification of decltype\(\(n\)\) does not match qualification of Self} }
+		  "qualification of decltype((n)) does not match qualification of Self");
+    static_assert(__is_same (__remove_cvref (decltype((n))), int),
+		  "decltype((n)) is not an int");
+    static_assert(__is_same (decltype(n), int));
+  };
+  f2_const();
+
+  auto f3_const = [&n]<typename Self>(this Self const&){
+    static_assert(__is_same(decltype((n)), int&));
+    static_assert(__is_same (decltype(n), int));
+  };
+  f3_const();
+
+  auto f4_const = []<typename Self>(this Self const&){
+    static_assert(__is_same (decltype(n), int));
+    decltype((n)) a; // { dg-error {is not captured} }
+  };
+}
+
+void instantiate_dep()
+{
+  dep0();
+  dep1();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX7.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX7.C
new file mode 100644
index 00000000000..a068941413a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambdaX7.C
@@ -0,0 +1,87 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// lambda capture mutability with explicit object parameter
+
+void capture_by_value()
+{
+  static constexpr int magic = 42;
+  auto f0 = [n = 0](this auto self){
+    n += magic;
+    return n;
+  };
+  auto f1 = [n = 0](this auto& self){
+    n += magic;
+    return n;
+  };
+  auto f2 = [n = 0](this auto&& self){
+    n += magic;
+    return n;
+  };
+
+  // passed by value, should still return a value equal to magic regardless
+  // of how many times it is called
+  if (f0 () != magic)
+    __builtin_abort ();
+  if (f0 () != magic)
+    __builtin_abort ();
+  // passed by reference, the returned value should increase by magic
+  // each time it is called
+  if (f1 () != magic)
+    __builtin_abort ();
+  if (f1 () != magic + magic)
+    __builtin_abort ();
+  if (f2 () != magic)
+    __builtin_abort ();
+  if (f2 () != magic + magic)
+    __builtin_abort ();
+}
+
+void capture_by_ref()
+{
+  static constexpr int magic = 42;
+  int n0 = 0;
+  auto f0 = [&n0](this auto self){
+    n0 += magic;
+  };
+  int n1 = 0;
+  auto f1 = [&n1](this auto& self){
+    n1 += magic;
+  };
+  int n2 = 0;
+  auto f2 = [&n2](this auto&& self){
+    n2 += magic;
+  };
+  int n3 = 0;
+  auto f3 = [&n3](this auto const& self){
+    n3 += magic;
+  };
+
+  // all calls should mutate their capture, the capture is by reference
+  if (f0 (); n0 != magic)
+    __builtin_abort ();
+  if (f0 (); n0 != magic + magic)
+    __builtin_abort ();
+
+  if (f1 (); n1 != magic)
+    __builtin_abort ();
+  if (f1 (); n1 != magic + magic)
+    __builtin_abort ();
+
+  if (f2 (); n2 != magic)
+    __builtin_abort ();
+  if (f2 (); n2 != magic + magic)
+    __builtin_abort ();
+
+  if (f3 (); n3 != magic)
+    __builtin_abort ();
+  if (f3 (); n3 != magic + magic)
+    __builtin_abort ();
+}
+
+int main()
+{
+  capture_by_value ();
+  capture_by_ref ();
+}
+
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..eb8607781bf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-arrow.C
@@ -0,0 +1,28 @@
+// 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..bb43a0af913
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-assignment.C
@@ -0,0 +1,27 @@
+// 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..ecd6bdfd44c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-call.C
@@ -0,0 +1,40 @@
+// 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..3eb003062c0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-mem-subscript.C
@@ -0,0 +1,40 @@
+// 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..f38615e2b98
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-dep.C
@@ -0,0 +1,58 @@
+// 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_OPS(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..634e878c93e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem-non-dep.C
@@ -0,0 +1,57 @@
+// 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_OPS(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..b897a4bac71
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-non-mem.h
@@ -0,0 +1,210 @@
+// tests for ops that must be member functions are seperate
+
+// the name of the class refers to the type of it's member functions xobj parameter
+
+#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, int) { return self; }	\
+  TYPE operator*(this TYPE self) { return self; }		\
+  TYPE operator->*(this TYPE self, int) { 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, 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) { 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) <=> 0;	\
+			\
+  *(OPERAND);		\
+  (OPERAND) ->* 0;	\
+  &(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) <=> 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))));			\
+  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..d08e938c98d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-mem.C
@@ -0,0 +1,171 @@
+// 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" }
+
+// 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_VALID(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&>; }, "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 const&>; }, "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..865b1f57d4a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-ops-requires-non-mem.C
@@ -0,0 +1,237 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// well-formed and ill-formed uses of non-member capable operators in a requires expression
+
+#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.
+
+template<typename T, typename U>
+concept same_as = __is_same(T, U);
+
+#define TEST_INVALID(OPERAND, CORRECT_TYPE) \
+  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) <=> 0; }, "Unexpected success calling operator <=> with " #OPERAND);	\
+														\
+  static_assert(!requires{ *(OPERAND); }, "Unexpected success calling operator unary* with " #OPERAND);		\
+  static_assert(!requires{ (OPERAND) ->* 0; }, "Unexpected success calling operator ->* with " #OPERAND);	\
+  /* We need to check the return type to confirm the built-in operator was not selected.  */			\
+  static_assert(!requires{ {&(OPERAND)} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected success calling operator unary& with " #OPERAND);					\
+  static_assert(!requires{ {(OPERAND), 0} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected success calling operator , with " #OPERAND);
+
+#define TEST_VALID(OPERAND, CORRECT_TYPE) \
+  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) <=> 0; }, "Unexpected failure calling operator <=> with " #OPERAND);	\
+														\
+  static_assert(requires{ *(OPERAND); }, "Unexpected failure calling operator unary* with " #OPERAND);		\
+  static_assert(requires{ (OPERAND) ->* 0; }, "Unexpected failure calling operator ->* with " #OPERAND);	\
+  /* We need to check the return type to confirm we selected our overload, not the built-in operator.  */	\
+  static_assert(requires{ {&(OPERAND)} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected failure calling operator unary& with " #OPERAND);					\
+  static_assert(requires{ {(OPERAND), 0} -> same_as<CORRECT_TYPE>; },						\
+		"Unexpected failure calling operator , with " #OPERAND);
+
+// Return types need to be tested for the deduced case
+
+#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) <=> 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)} -> 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, DepValue)
+  TEST_VALID(static_cast<DepValue&&>(value), DepValue)
+  TEST_VALID(static_cast<DepValue const&>(value), DepValue)
+  TEST_VALID(static_cast<DepValue const&&>(value), DepValue)
+}
+
+template<typename DepLRef = LRef>
+void test_l_ref()
+{
+  DepLRef l_ref{};
+  TEST_VALID(l_ref, DepLRef&)
+  TEST_INVALID(static_cast<DepLRef&&>(l_ref), DepLRef&)
+  TEST_INVALID(static_cast<DepLRef const&>(l_ref), DepLRef&)
+  TEST_INVALID(static_cast<DepLRef const&&>(l_ref), DepLRef&)
+}
+
+template<typename DepRRef = RRef>
+void test_r_ref()
+{
+  DepRRef r_ref{};
+  TEST_INVALID(r_ref, DepRRef&&)
+  TEST_VALID(static_cast<DepRRef&&>(r_ref), DepRRef&&)
+  TEST_INVALID(static_cast<DepRRef const&>(r_ref), DepRRef&&)
+  TEST_INVALID(static_cast<DepRRef const&&>(r_ref), DepRRef&&)
+}
+
+template<typename DepConstLRef = ConstLRef>
+void test_const_l_ref()
+{
+  DepConstLRef const_l_ref{};
+  TEST_VALID(const_l_ref, DepConstLRef const&)
+  TEST_VALID(static_cast<DepConstLRef&&>(const_l_ref), DepConstLRef const&)
+  TEST_VALID(static_cast<DepConstLRef const&>(const_l_ref), DepConstLRef const&)
+  TEST_VALID(static_cast<DepConstLRef const&&>(const_l_ref), DepConstLRef const&)
+}
+
+template<typename DepConstRRef = ConstRRef>
+void test_const_r_ref()
+{
+  DepConstRRef const_r_ref{};
+  TEST_INVALID(const_r_ref, DepConstRRef const&&)
+  TEST_VALID(static_cast<DepConstRRef&&>(const_r_ref), DepConstRRef const&&)
+  TEST_INVALID(static_cast<DepConstRRef const&>(const_r_ref), DepConstRRef const&&)
+  TEST_VALID(static_cast<DepConstRRef const&&>(const_r_ref), DepConstRRef const&&)
+}
+
+template<typename DepDeduced = Deduced>
+void test_deduced()
+{
+  DepDeduced 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();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl-constraints.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl-constraints.C
new file mode 100644
index 00000000000..a2ad0e35073
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl-constraints.C
@@ -0,0 +1,115 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// redeclarations with equivalent constraints
+
+template<typename T>
+concept Constrain = true;
+
+
+struct S {
+// xobj/static
+  void f_xs_v(this S, Constrain auto) {}; // { dg-note {previous declaration} }
+  static void f_xs_v(Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_ref(this S&, Constrain auto) {}; // { dg-note {previous declaration} }
+  static void f_xs_ref(Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_cref(this S const&, Constrain auto) {}; // { dg-note {previous declaration} }
+  static void f_xs_cref(Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_rref(this S&&, Constrain auto) {}; // { dg-note {previous declaration} }
+  static void f_xs_rref(Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_crref(this S const&&, Constrain auto) {}; // { dg-note {previous declaration} }
+  static void f_xs_crref(Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_dv(this auto, Constrain auto) {}; // { dg-note {previous declaration} {Probably well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dv(Constrain auto) {}; // { dg-error {cannot be overloaded with} {Probably well formed? TBD} { xfail *-*-* } }
+
+  void f_xs_dcref(this auto const&, Constrain auto) {}; // { dg-note {previous declaration} {Probably well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dcref(Constrain auto) {}; // { dg-error {cannot be overloaded with} {Probably well formed? TBD} { xfail *-*-* } }
+
+  void f_xs_dfwdref(this auto&&, Constrain auto) {}; // { dg-note {previous declaration} {Probably well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dfwdref(Constrain auto) {}; // { dg-error {cannot be overloaded with} {Probably well formed? TBD} { xfail *-*-* } }
+
+// static/xobj
+  static void f_sx_v(Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_sx_v(this S, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_ref(Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_sx_ref(this S&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_cref(Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_sx_cref(this S const&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_rref(Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_sx_rref(this S&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_crref(Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_sx_crref(this S const&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_dv(Constrain auto) {}; // { dg-note {previous declaration} {Probably well formed? TBD} { xfail *-*-* } }
+  void f_sx_dv(this auto, Constrain auto) {}; // { dg-error {cannot be overloaded with} {Probably well formed? TBD} { xfail *-*-* } }
+
+  static void f_sx_dcref(Constrain auto) {}; // { dg-note {previous declaration} {Probably well formed? TBD} { xfail *-*-* } }
+  void f_sx_dcref(this auto const&, Constrain auto) {}; // { dg-error {cannot be overloaded with} {Probably well formed? TBD} { xfail *-*-* } }
+
+  static void f_sx_dfwdref(Constrain auto) {}; // { dg-note {previous declaration} {Probably well formed? TBD} { xfail *-*-* } }
+  void f_sx_dfwdref(this auto&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} {Probably well formed? TBD} { xfail *-*-* } }
+
+  // iobj/xobj
+  void f_ix_lref(Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_ix_lref(this S&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_ix_rref(Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_ix_rref(this S&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_ix_const_lref(Constrain auto) const {}; // { dg-note {previous declaration} }
+  void f_ix_const_lref(this S const&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_ix_const_rref(Constrain auto) const {}; // { dg-note {previous declaration} }
+  void f_ix_const_rref(this S const&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+  
+  // xobj/iobj
+  void f_xi_lref(this S&, Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_xi_lref(Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xi_rref(this S&&, Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_xi_rref(Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xi_const_lref(this S const&, Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_xi_const_lref(Constrain auto) const {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xi_const_rref(this S const&&, Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_xi_const_rref(Constrain auto) const {}; // { dg-error {cannot be overloaded with} }
+
+  // with ref qualifier
+
+  // iobj/xobj
+  void f_ix_lref_refqual(Constrain auto) & {}; // { dg-note {previous declaration} }
+  void f_ix_lref_refqual(this S&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_ix_rref_refqual(Constrain auto) && {}; // { dg-note {previous declaration} }
+  void f_ix_rref_refqual(this S&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_ix_const_lref_refqual(Constrain auto) const& {}; // { dg-note {previous declaration} }
+  void f_ix_const_lref_refqual(this S const&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_ix_const_rref_refqual(Constrain auto) const&& {}; // { dg-note {previous declaration} }
+  void f_ix_const_rref_refqual(this S const&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} }
+  
+  // xobj/iobj
+  void f_xi_lref_refqual(this S&, Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_xi_lref_refqual(Constrain auto) & {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xi_rref_refqual(this S&&, Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_xi_rref_refqual(Constrain auto) && {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xi_const_lref_refqual(this S const&, Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_xi_const_lref_refqual(Constrain auto) const& {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xi_const_rref_refqual(this S const&&, Constrain auto) {}; // { dg-note {previous declaration} }
+  void f_xi_const_rref_refqual(Constrain auto) const&& {}; // { dg-error {cannot be overloaded with} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C
new file mode 100644
index 00000000000..7fcfcc31c30
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C
@@ -0,0 +1,246 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// redeclarations of xobj/iobj member functions where the iobj member function
+// is not ref qualified
+
+// it does not make sense to check for the inverse in this test (7 iobj, 1 xobj)
+// because you are not allowed to overload iobj member functions without ref qualifiers
+// with iobj member functions that do (and vice versa)
+
+// iobj first
+
+struct S0 {
+  void f0(); // { dg-note "previous declaration" }
+  void f0(this S0 &&); // { dg-error "cannot be overloaded with" }
+  void f0(this S0 const&); // { dg-bogus "" }
+  void f0(this S0 const&&); // { dg-bogus "" }
+  void f0(this S0 volatile&); // { dg-bogus "" }
+  void f0(this S0 volatile&&); // { dg-bogus "" }
+  void f0(this S0 const volatile&); // { dg-bogus "" }
+  void f0(this S0 const volatile&&); // { dg-bogus "" }
+
+  void f1(); // { dg-note "previous declaration" }
+  void f1(this S0 &); // { dg-error "cannot be overloaded with" }
+  void f1(this S0 const&); // { dg-bogus "" }
+  void f1(this S0 const&&); // { dg-bogus "" }
+  void f1(this S0 volatile&); // { dg-bogus "" }
+  void f1(this S0 volatile&&); // { dg-bogus "" }
+  void f1(this S0 const volatile&); // { dg-bogus "" }
+  void f1(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fc0() const; // { dg-note "previous declaration" }
+  void fc0(this S0 &); // { dg-bogus "" }
+  void fc0(this S0 &&); // { dg-bogus "" }
+  void fc0(this S0 const&&); // { dg-error "cannot be overloaded with" }
+  void fc0(this S0 volatile&); // { dg-bogus "" }
+  void fc0(this S0 volatile&&); // { dg-bogus "" }
+  void fc0(this S0 const volatile&); // { dg-bogus "" }
+  void fc0(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fc1() const; // { dg-note "previous declaration" }
+  void fc1(this S0 &); // { dg-bogus "" }
+  void fc1(this S0 &&); // { dg-bogus "" }
+  void fc1(this S0 const&); // { dg-error "cannot be overloaded with" }
+  void fc1(this S0 volatile&); // { dg-bogus "" }
+  void fc1(this S0 volatile&&); // { dg-bogus "" }
+  void fc1(this S0 const volatile&); // { dg-bogus "" }
+  void fc1(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fv0() volatile; // { dg-note "previous declaration" }
+  void fv0(this S0 &); // { dg-bogus "" }
+  void fv0(this S0 &&); // { dg-bogus "" }
+  void fv0(this S0 const&); // { dg-bogus "" }
+  void fv0(this S0 const&&); // { dg-bogus "" }
+  void fv0(this S0 volatile&&); // { dg-error "cannot be overloaded with" }
+  void fv0(this S0 const volatile&); // { dg-bogus "" }
+  void fv0(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fv1() volatile; // { dg-note "previous declaration" }
+  void fv1(this S0 &); // { dg-bogus "" }
+  void fv1(this S0 &&); // { dg-bogus "" }
+  void fv1(this S0 const&); // { dg-bogus "" }
+  void fv1(this S0 const&&); // { dg-bogus "" }
+  void fv1(this S0 volatile&); // { dg-error "cannot be overloaded with" }
+  void fv1(this S0 const volatile&); // { dg-bogus "" }
+  void fv1(this S0 const volatile&&); // { dg-bogus "" }
+
+  void fcv0() const volatile; // { dg-note "previous declaration" }
+  void fcv0(this S0 &); // { dg-bogus "" }
+  void fcv0(this S0 &&); // { dg-bogus "" }
+  void fcv0(this S0 const&); // { dg-bogus "" }
+  void fcv0(this S0 const&&); // { dg-bogus "" }
+  void fcv0(this S0 volatile&); // { dg-bogus "" }
+  void fcv0(this S0 volatile&&); // { dg-bogus "" }
+  void fcv0(this S0 const volatile&&); // { dg-error "cannot be overloaded with" }
+
+  void fcv1() const volatile; // { dg-note "previous declaration" }
+  void fcv1(this S0 &); // { dg-bogus "" }
+  void fcv1(this S0 &&); // { dg-bogus "" }
+  void fcv1(this S0 const&); // { dg-bogus "" }
+  void fcv1(this S0 const&&); // { dg-bogus "" }
+  void fcv1(this S0 volatile&); // { dg-bogus "" }
+  void fcv1(this S0 volatile&&); // { dg-bogus "" }
+  void fcv1(this S0 const volatile&); // { dg-error "cannot be overloaded with" }
+};
+
+// iobj last
+
+struct S1 {
+  void f0(this S1 &&); // { dg-note "previous declaration" }
+  void f0(this S1 const&); // { dg-bogus "" }
+  void f0(this S1 const&&); // { dg-bogus "" }
+  void f0(this S1 volatile&); // { dg-bogus "" }
+  void f0(this S1 volatile&&); // { dg-bogus "" }
+  void f0(this S1 const volatile&); // { dg-bogus "" }
+  void f0(this S1 const volatile&&); // { dg-bogus "" }
+  void f0(); // { dg-error "cannot be overloaded with" }
+
+  void f1(this S1 &); // { dg-note "previous declaration" }
+  void f1(this S1 const&); // { dg-bogus "" }
+  void f1(this S1 const&&); // { dg-bogus "" }
+  void f1(this S1 volatile&); // { dg-bogus "" }
+  void f1(this S1 volatile&&); // { dg-bogus "" }
+  void f1(this S1 const volatile&); // { dg-bogus "" }
+  void f1(this S1 const volatile&&); // { dg-bogus "" }
+  void f1(); // { dg-error "cannot be overloaded with" }
+
+  void fc0(this S1 &); // { dg-bogus "" }
+  void fc0(this S1 &&); // { dg-bogus "" }
+  void fc0(this S1 const&&); // { dg-note "previous declaration" }
+  void fc0(this S1 volatile&); // { dg-bogus "" }
+  void fc0(this S1 volatile&&); // { dg-bogus "" }
+  void fc0(this S1 const volatile&); // { dg-bogus "" }
+  void fc0(this S1 const volatile&&); // { dg-bogus "" }
+  void fc0() const; // { dg-error "cannot be overloaded with" }
+
+  void fc1(this S1 &); // { dg-bogus "" }
+  void fc1(this S1 &&); // { dg-bogus "" }
+  void fc1(this S1 const&); // { dg-note "previous declaration" }
+  void fc1(this S1 volatile&); // { dg-bogus "" }
+  void fc1(this S1 volatile&&); // { dg-bogus "" }
+  void fc1(this S1 const volatile&); // { dg-bogus "" }
+  void fc1(this S1 const volatile&&); // { dg-bogus "" }
+  void fc1() const; // { dg-error "cannot be overloaded with" }
+
+  void fv0(this S1 &); // { dg-bogus "" }
+  void fv0(this S1 &&); // { dg-bogus "" }
+  void fv0(this S1 const&); // { dg-bogus "" }
+  void fv0(this S1 const&&); // { dg-bogus "" }
+  void fv0(this S1 volatile&&); // { dg-note "previous declaration" }
+  void fv0(this S1 const volatile&); // { dg-bogus "" }
+  void fv0(this S1 const volatile&&); // { dg-bogus "" }
+  void fv0() volatile; // { dg-error "cannot be overloaded with" }
+
+  void fv1(this S1 &); // { dg-bogus "" }
+  void fv1(this S1 &&); // { dg-bogus "" }
+  void fv1(this S1 const&); // { dg-bogus "" }
+  void fv1(this S1 const&&); // { dg-bogus "" }
+  void fv1(this S1 volatile&); // { dg-note "previous declaration" }
+  void fv1(this S1 const volatile&); // { dg-bogus "" }
+  void fv1(this S1 const volatile&&); // { dg-bogus "" }
+  void fv1() volatile; // { dg-error "cannot be overloaded with" }
+
+  void fcv0(this S1 &); // { dg-bogus "" }
+  void fcv0(this S1 &&); // { dg-bogus "" }
+  void fcv0(this S1 const&); // { dg-bogus "" }
+  void fcv0(this S1 const&&); // { dg-bogus "" }
+  void fcv0(this S1 volatile&); // { dg-bogus "" }
+  void fcv0(this S1 volatile&&); // { dg-bogus "" }
+  void fcv0(this S1 const volatile&&); // { dg-note "previous declaration" }
+  void fcv0() const volatile; // { dg-error "cannot be overloaded with" }
+
+  void fcv1(this S1 &); // { dg-bogus "" }
+  void fcv1(this S1 &&); // { dg-bogus "" }
+  void fcv1(this S1 const&); // { dg-bogus "" }
+  void fcv1(this S1 const&&); // { dg-bogus "" }
+  void fcv1(this S1 volatile&); // { dg-bogus "" }
+  void fcv1(this S1 volatile&&); // { dg-bogus "" }
+  void fcv1(this S1 const volatile&); // { dg-note "previous declaration" }
+  void fcv1() const volatile; // { dg-error "cannot be overloaded with" }
+};
+
+// in order (iobj replacing one of the following in each group)
+// lvalue ref to S
+// rvalue ref to S
+// lvalue c ref to S
+// rvalue c ref to S
+// lvalue v ref to S
+// rvalue v ref to S
+// lvalue cv ref to S
+// rvalue cv ref to S
+
+struct S2 {
+  void f0(); // { dg-note "previous declaration" }
+  void f0(this S2 &&); // { dg-error "cannot be overloaded with" }
+  void f0(this S2 const&); // { dg-bogus "" }
+  void f0(this S2 const&&); // { dg-bogus "" }
+  void f0(this S2 volatile&); // { dg-bogus "" }
+  void f0(this S2 volatile&&); // { dg-bogus "" }
+  void f0(this S2 const volatile&); // { dg-bogus "" }
+  void f0(this S2 const volatile&&); // { dg-bogus "" }
+
+  void f1(this S2 &); // { dg-note "previous declaration" }
+  void f1(); // { dg-error "cannot be overloaded with" }
+  void f1(this S2 const&); // { dg-bogus "" }
+  void f1(this S2 const&&); // { dg-bogus "" }
+  void f1(this S2 volatile&); // { dg-bogus "" }
+  void f1(this S2 volatile&&); // { dg-bogus "" }
+  void f1(this S2 const volatile&); // { dg-bogus "" }
+  void f1(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fc0(this S2 &); // { dg-bogus "" }
+  void fc0(this S2 &&); // { dg-bogus "" }
+  void fc0() const; // { dg-note "previous declaration" }
+  void fc0(this S2 const&&); // { dg-error "cannot be overloaded with" }
+  void fc0(this S2 volatile&); // { dg-bogus "" }
+  void fc0(this S2 volatile&&); // { dg-bogus "" }
+  void fc0(this S2 const volatile&); // { dg-bogus "" }
+  void fc0(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fc1(this S2 &); // { dg-bogus "" }
+  void fc1(this S2 &&); // { dg-bogus "" }
+  void fc1(this S2 const&); // { dg-note "previous declaration" }
+  void fc1() const; // { dg-error "cannot be overloaded with" }
+  void fc1(this S2 volatile&); // { dg-bogus "" }
+  void fc1(this S2 volatile&&); // { dg-bogus "" }
+  void fc1(this S2 const volatile&); // { dg-bogus "" }
+  void fc1(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fv0(this S2 &); // { dg-bogus "" }
+  void fv0(this S2 &&); // { dg-bogus "" }
+  void fv0(this S2 const&); // { dg-bogus "" }
+  void fv0(this S2 const&&); // { dg-bogus "" }
+  void fv0() volatile; // { dg-note "previous declaration" }
+  void fv0(this S2 volatile&&); // { dg-error "cannot be overloaded with" }
+  void fv0(this S2 const volatile&); // { dg-bogus "" }
+  void fv0(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fv1(this S2 &); // { dg-bogus "" }
+  void fv1(this S2 &&); // { dg-bogus "" }
+  void fv1(this S2 const&); // { dg-bogus "" }
+  void fv1(this S2 const&&); // { dg-bogus "" }
+  void fv1(this S2 volatile&); // { dg-note "previous declaration" }
+  void fv1() volatile; // { dg-error "cannot be overloaded with" }
+  void fv1(this S2 const volatile&); // { dg-bogus "" }
+  void fv1(this S2 const volatile&&); // { dg-bogus "" }
+
+  void fcv0(this S2 &); // { dg-bogus "" }
+  void fcv0(this S2 &&); // { dg-bogus "" }
+  void fcv0(this S2 const&); // { dg-bogus "" }
+  void fcv0(this S2 const&&); // { dg-bogus "" }
+  void fcv0(this S2 volatile&); // { dg-bogus "" }
+  void fcv0(this S2 volatile&&); // { dg-bogus "" }
+  void fcv0() const volatile; // { dg-note "previous declaration" }
+  void fcv0(this S2 const volatile&&); // { dg-error "cannot be overloaded with" }
+
+  void fcv1(this S2 &); // { dg-bogus "" }
+  void fcv1(this S2 &&); // { dg-bogus "" }
+  void fcv1(this S2 const&); // { dg-bogus "" }
+  void fcv1(this S2 const&&); // { dg-bogus "" }
+  void fcv1(this S2 volatile&); // { dg-bogus "" }
+  void fcv1(this S2 volatile&&); // { dg-bogus "" }
+  void fcv1(this S2 const volatile&); // { dg-note "previous declaration" }
+  void fcv1() const volatile; // { dg-error "cannot be overloaded with" }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C
new file mode 100644
index 00000000000..adb6ae5a380
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C
@@ -0,0 +1,161 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+// { dg-options "-pedantic-errors -Wno-volatile" }
+
+// rejecting redeclarations of by-value xobj member functions
+// as iobj member functions that are not ref qualified (and vice-versa)
+// also check that valid overloads are accepted without diagnostic
+
+//  iobj |  xobj  |  MSVC | clang | ISOC++
+//  none |  none  |   X   |   X   |   X
+//  none |     c  |   X   |   O   |   X
+//  none |     v  |   X   |   O   |   X
+//  none |    cv  |   X   |   O   |   X
+//     c |  none  |   O   |   O   |   O
+//     c |     c  |   O   |   X   |   O
+//     c |     v  |   O   |   O   |   O
+//     c |    cv  |   O   |   O   |   O
+//     v |  none  |   O   |   O   |   O
+//     v |     c  |   O   |   O   |   O
+//     v |     v  |   O   |   X   |   O
+//     v |    cv  |   O   |   O   |   O
+//    cv |  none  |   O   |   O   |   O
+//    cv |     c  |   O   |   O   |   O
+//    cv |     v  |   O   |   O   |   O
+//    cv |    cv  |   O   |   X   |   O
+
+/* Top-level cv qualifiers are supposed to be discarded from
+   the parameters of a function declaration.
+     
+   [dcl.fct.5]
+   After producing the list of parameter types, any top-level
+   cv-qualifiers modifying a parameter type are deleted when forming
+   the function type.
+
+   According to the standard, the type of an implicit object parameter
+   is always a reference. This isn't reflected in GCC but we still need
+   to take this rule into account here.
+
+   [over.match.funcs.general.4]
+   For implicit object member functions, the type of the implicit
+   object parameter is
+   -- “lvalue reference to cv X” for functions declared
+      without a ref-qualifier or with the & ref-qualifier
+   -- “rvalue reference to cv X” for functions declared with
+      the && ref-qualifier
+    
+   When comparing an iobj and xobj member function to see if they are
+   redeclarations we treat them differently depending on if the iobj
+   member function has a ref qualifier or not.
+   If the iobj member function does not have a ref qualifier, we need to
+   strip the top-level references before comparing them.
+
+   [basic.scope.scope.3]
+   Two non-static member functions have corresponding object
+   parameters if:
+   -- exactly one is an implicit object member function with no
+      ref-qualifier and the types of their object parameters
+      ([dcl.fct]), after removing top-level references, are the
+      same, or
+   -- their object parameters have the same type. */
+
+struct S {
+  void f0(); // { dg-note "previous declaration" }
+  void f0(this S); // { dg-error "cannot be overloaded with" }
+
+  void f1(); // { dg-note "previous declaration" }
+  void f1(this S const); // { dg-error "cannot be overloaded with" }
+
+  void f2(); // { dg-note "previous declaration" }
+  void f2(this S volatile); // { dg-error "cannot be overloaded with" }
+
+  void f3(); // { dg-note "previous declaration" }
+  void f3(this S const volatile); // { dg-error "cannot be overloaded with" }
+
+  void fc0() const; // { dg-bogus "" }
+  void fc0(this S); // { dg-bogus "" }
+
+  void fc1() const; // { dg-bogus "" }
+  void fc1(this S const); // { dg-bogus "" }
+
+  void fc2() const; // { dg-bogus "" }
+  void fc2(this S volatile); // { dg-bogus "" }
+
+  void fc3() const; // { dg-bogus "" }
+  void fc3(this S const volatile); // { dg-bogus "" }
+
+  void fv0() volatile; // { dg-bogus "" }
+  void fv0(this S); // { dg-bogus "" }
+
+  void fv1() volatile; // { dg-bogus "" }
+  void fv1(this S const); // { dg-bogus "" }
+
+  void fv2() volatile; // { dg-bogus "" }
+  void fv2(this S volatile); // { dg-bogus "" }
+
+  void fv3() volatile; // { dg-bogus "" }
+  void fv3(this S const volatile); // { dg-bogus "" }
+
+  void fcv0() const volatile; // { dg-bogus "" }
+  void fcv0(this S); // { dg-bogus "" }
+
+  void fcv1() const volatile; // { dg-bogus "" }
+  void fcv1(this S const); // { dg-bogus "" }
+
+  void fcv2() const volatile; // { dg-bogus "" }
+  void fcv2(this S volatile); // { dg-bogus "" }
+
+  void fcv3() const volatile; // { dg-bogus "" }
+  void fcv3(this S const volatile); // { dg-bogus "" }
+
+  // same as the above f cases except reversed
+
+  void g0(this S); // { dg-note "previous declaration" }
+  void g0(); // { dg-error "cannot be overloaded with" }
+
+  void g1(this S const); // { dg-note "previous declaration" }
+  void g1(); // { dg-error "cannot be overloaded with" }
+
+  void g2(this S volatile); // { dg-note "previous declaration" }
+  void g2(); // { dg-error "cannot be overloaded with" }
+
+  void g3(this S const volatile); // { dg-note "previous declaration" }
+  void g3(); // { dg-error "cannot be overloaded with" }
+
+  void gc0(this S); // { dg-bogus "" }
+  void gc0() const; // { dg-bogus "" }
+
+  void gc1(this S const); // { dg-bogus "" }
+  void gc1() const; // { dg-bogus "" }
+
+  void gc2(this S volatile); // { dg-bogus "" }
+  void gc2() const; // { dg-bogus "" }
+
+  void gc3(this S const volatile); // { dg-bogus "" }
+  void gc3() const; // { dg-bogus "" }
+
+  void gv0(this S); // { dg-bogus "" }
+  void gv0() volatile; // { dg-bogus "" }
+
+  void gv1(this S const); // { dg-bogus "" }
+  void gv1() volatile; // { dg-bogus "" }
+
+  void gv2(this S volatile); // { dg-bogus "" }
+  void gv2() volatile; // { dg-bogus "" }
+
+  void gv3(this S const volatile); // { dg-bogus "" }
+  void gv3() volatile; // { dg-bogus "" }
+
+  void gcv0(this S); // { dg-bogus "" }
+  void gcv0() const volatile; // { dg-bogus "" }
+
+  void gcv1(this S const); // { dg-bogus "" }
+  void gcv1() const volatile; // { dg-bogus "" }
+
+  void gcv2(this S volatile); // { dg-bogus "" }
+  void gcv2() const volatile; // { dg-bogus "" }
+
+  void gcv3(this S const volatile); // { dg-bogus "" }
+  void gcv3() const volatile; // { dg-bogus "" }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl3.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl3.C
new file mode 100644
index 00000000000..9942d6e26f8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl3.C
@@ -0,0 +1,263 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// redeclarations of xobj member functions as static member functions and vice versa
+
+// note, the deduced xobj parameter cases should probably be removed
+// I still need clarification on what the correct behavior should be here.
+
+struct S {
+// no additional params
+  void f_xs_v(this S) {}; // { dg-note {previous declaration} }
+  static void f_xs_v() {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_ref(this S&) {}; // { dg-note {previous declaration} }
+  static void f_xs_ref() {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_cref(this S const&) {}; // { dg-note {previous declaration} }
+  static void f_xs_cref() {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_rref(this S&&) {}; // { dg-note {previous declaration} }
+  static void f_xs_rref() {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_crref(this S const&&) {}; // { dg-note {previous declaration} }
+  static void f_xs_crref() {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_dv(this auto) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dv() {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  void f_xs_dcref(this auto const&) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dcref() {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  void f_xs_dfwdref(this auto&&) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dfwdref() {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+// reversed
+  static void f_sx_v() {}; // { dg-note {previous declaration} }
+  void f_sx_v(this S) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_ref() {}; // { dg-note {previous declaration} }
+  void f_sx_ref(this S&) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_cref() {}; // { dg-note {previous declaration} }
+  void f_sx_cref(this S const&) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_rref() {}; // { dg-note {previous declaration} }
+  void f_sx_rref(this S&&) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_crref() {}; // { dg-note {previous declaration} }
+  void f_sx_crref(this S const&&) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_dv() {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  void f_sx_dv(this auto) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  static void f_sx_dcref() {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  void f_sx_dcref(this auto const&) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  static void f_sx_dfwdref() {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  void f_sx_dfwdref(this auto&&) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+// one additional param
+  void f_xs_v_int(this S, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_v_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_ref_int(this S&, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_ref_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_cref_int(this S const&, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_cref_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_rref_int(this S&&, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_rref_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_crref_int(this S const&&, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_crref_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_dv_int(this auto, int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dv_int(int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  void f_xs_dcref_int(this auto const&, int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dcref_int(int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  void f_xs_dfwdref_int(this auto&&, int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dfwdref_int(int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+// reversed
+  static void f_sx_v_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_v_int(this S, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_ref_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_ref_int(this S&, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_cref_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_cref_int(this S const&, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_rref_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_rref_int(this S&&, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_crref_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_crref_int(this S const&&, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_dv_int(int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  void f_sx_dv_int(this auto, int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  static void f_sx_dcref_int(int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  void f_sx_dcref_int(this auto const&, int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  static void f_sx_dfwdref_int(int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  void f_sx_dfwdref_int(this auto&&, int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+// two additional params
+  void f_xs_v_int2(this S, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_v_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_ref_int2(this S&, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_ref_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_cref_int2(this S const&, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_cref_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_rref_int2(this S&&, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_rref_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_crref_int2(this S const&&, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_crref_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_dv_int2(this auto, int, int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dv_int2(int, int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  void f_xs_dcref_int2(this auto const&, int, int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dcref_int2(int, int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  void f_xs_dfwdref_int2(this auto&&, int, int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  static void f_xs_dfwdref_int2(int, int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+// reversed
+  static void f_sx_v_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_v_int2(this S, int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_ref_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_ref_int2(this S&, int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_cref_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_cref_int2(this S const&, int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_rref_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_rref_int2(this S&&, int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_crref_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_crref_int2(this S const&&, int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_dv_int2(int, int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  void f_sx_dv_int2(this auto, int, int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  static void f_sx_dcref_int2(int, int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  void f_sx_dcref_int2(this auto const&, int, int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+
+  static void f_sx_dfwdref_int2(int, int) {}; // { dg-note {previous declaration} {Maybe well formed? TBD} { xfail *-*-* } }
+  void f_sx_dfwdref_int2(this auto&&, int, int) {}; // { dg-error {cannot be overloaded with} {Maybe well formed? TBD} { xfail *-*-* } }
+};
+
+// unrelated explicit object parameter type
+
+struct A {};
+
+struct S1
+{
+// no additional params
+  void f_xs_v(this A) {}; // { dg-note {previous declaration} }
+  static void f_xs_v() {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_ref(this A&) {}; // { dg-note {previous declaration} }
+  static void f_xs_ref() {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_cref(this A const&) {}; // { dg-note {previous declaration} }
+  static void f_xs_cref() {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_rref(this A&&) {}; // { dg-note {previous declaration} }
+  static void f_xs_rref() {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_crref(this A const&&) {}; // { dg-note {previous declaration} }
+  static void f_xs_crref() {}; // { dg-error {cannot be overloaded with} }
+
+// reversed
+  static void f_sx_v() {}; // { dg-note {previous declaration} }
+  void f_sx_v(this A) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_ref() {}; // { dg-note {previous declaration} }
+  void f_sx_ref(this A&) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_cref() {}; // { dg-note {previous declaration} }
+  void f_sx_cref(this A const&) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_rref() {}; // { dg-note {previous declaration} }
+  void f_sx_rref(this A&&) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_crref() {}; // { dg-note {previous declaration} }
+  void f_sx_crref(this A const&&) {}; // { dg-error {cannot be overloaded with} }
+
+// one additional param
+  void f_xs_v_int(this A, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_v_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_ref_int(this A&, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_ref_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_cref_int(this A const&, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_cref_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_rref_int(this A&&, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_rref_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_crref_int(this A const&&, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_crref_int(int) {}; // { dg-error {cannot be overloaded with} }
+
+// reversed
+  static void f_sx_v_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_v_int(this A, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_ref_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_ref_int(this A&, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_cref_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_cref_int(this A const&, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_rref_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_rref_int(this A&&, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_crref_int(int) {}; // { dg-note {previous declaration} }
+  void f_sx_crref_int(this A const&&, int) {}; // { dg-error {cannot be overloaded with} }
+
+// two additional params
+  void f_xs_v_int2(this A, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_v_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_ref_int2(this A&, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_ref_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_cref_int2(this A const&, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_cref_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_rref_int2(this A&&, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_rref_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  void f_xs_crref_int2(this A const&&, int, int) {}; // { dg-note {previous declaration} }
+  static void f_xs_crref_int2(int, int) {}; // { dg-error {cannot be overloaded with} }
+
+// reversed
+  static void f_sx_v_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_v_int2(this A, int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_ref_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_ref_int2(this A&, int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_cref_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_cref_int2(this A const&, int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_rref_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_rref_int2(this A&&, int, int) {}; // { dg-error {cannot be overloaded with} }
+
+  static void f_sx_crref_int2(int, int) {}; // { dg-note {previous declaration} }
+  void f_sx_crref_int2(this A const&&, int, int) {}; // { dg-error {cannot be overloaded with} }
+};
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-virtual.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-virtual.C
new file mode 100644
index 00000000000..023cdc2e0fe
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-virtual.C
@@ -0,0 +1,95 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// diagnose xobj member functions that override
+// or are declared as virtual, override, or final
+
+struct B {
+  virtual void f0() {} // { dg-note {virtual function declared here} }
+  virtual void f1() {} // { dg-note {virtual function declared here} }
+  virtual void f2() {} // { dg-note {virtual function declared here} }
+  virtual void f3() {} // { dg-note {virtual function declared here} }
+  virtual void f4() {} // { dg-note {virtual function declared here} }
+  virtual void f5() {} // { dg-note {virtual function declared here} }
+  virtual void f6() {} // { dg-note {virtual function declared here} }
+  virtual void f7() {} // { dg-note {virtual function declared here} }
+  virtual ~B() {}
+};
+
+struct S : B {
+  virtual void f0(this S&) {}		     // { dg-line line_f0 }
+  virtual void f1(this S&) override {}	     // { dg-line line_f1 }
+  virtual void f2(this S&) final {}	     // { dg-line line_f2 }
+  virtual void f3(this S&) override final {} // { dg-line line_f3 }
+  void f4(this S&) {}			     // { dg-line line_f4 }
+  void f5(this S&) override {}		     // { dg-line line_f5 }
+  void f6(this S&) final {}		     // { dg-line line_f6 }
+  void f7(this S&) override final {}	     // { dg-line line_f7 }
+};
+
+// { dg-error {an explicit object member function cannot be 'virtual'} "" { target *-*-* } line_f0 }
+// { dg-error {an explicit object member function cannot be 'virtual'} "" { target *-*-* } line_f1 }
+// { dg-error {an explicit object member function cannot be 'virtual'} "" { target *-*-* } line_f2 }
+// { dg-error {an explicit object member function cannot be 'virtual'} "" { target *-*-* } line_f3 }
+
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f0 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f1 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f2 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f3 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f4 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f5 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f6 }
+// { dg-error {explicit object member function overrides virtual function} "" { target *-*-* } line_f7 }
+
+// these should be suppressed, the wording conflicts with the error
+// the issue is not that they don't override, it's that they do override, and that isn't allowed
+// { dg-bogus "marked 'override', but does not override" "" { xfail *-*-* } line_f1 }
+// { dg-bogus "marked 'final', but is not virtual"	 "" { xfail *-*-* } line_f2 }
+// { dg-bogus "marked '(override|final)'"		 "" { xfail *-*-* } line_f3 }
+
+// { dg-bogus "marked 'override', but does not override" "" { xfail *-*-* } line_f5 }
+// { dg-bogus "marked 'final', but is not virtual"	 "" { xfail *-*-* } line_f6 }
+// { dg-bogus "marked '(override|final)'"		 "" { xfail *-*-* } line_f7 }
+
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_f0 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_f1 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_f2 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_f3 }
+// { dg-note "explicit object parameter declared here" "" { xfail *-*-* } line_f4 }
+// { dg-note "explicit object parameter declared here" "" { xfail *-*-* } line_f5 }
+// { dg-note "explicit object parameter declared here" "" { xfail *-*-* } line_f6 }
+// { dg-note "explicit object parameter declared here" "" { xfail *-*-* } line_f7 }
+
+struct S1 {
+  virtual void f0(this S&) {}		     // { dg-line line_S1_f0 }
+  virtual void f1(this S&) override {}	     // { dg-line line_S1_f1 }
+  virtual void f2(this S&) final {}	     // { dg-line line_S1_f2 }
+  virtual void f3(this S&) override final {} // { dg-line line_S1_f3 }
+  void f4(this S&) {}
+  void f5(this S&) override {}		     // { dg-line line_S1_f5 }
+  void f6(this S&) final {}		     // { dg-line line_S1_f6 }
+  void f7(this S&) override final {}	     // { dg-line line_S1_f7 }
+};
+
+// { dg-error "an explicit object member function cannot be 'virtual'" "" { target *-*-* } line_S1_f0 }
+// { dg-error "an explicit object member function cannot be 'virtual'" "" { target *-*-* } line_S1_f1 }
+// { dg-error "an explicit object member function cannot be 'virtual'" "" { target *-*-* } line_S1_f2 }
+// { dg-error "an explicit object member function cannot be 'virtual'" "" { target *-*-* } line_S1_f3 }
+
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_S1_f0 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_S1_f1 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_S1_f2 }
+// { dg-note "explicit object parameter declared here" "" { target *-*-* } line_S1_f3 }
+
+// I think I want these suppressed, but theres a decent argument that they should stay
+// theres arguably no reason the error about virtual should suppress these
+// { dg-bogus "marked 'override', but does not override" "" { xfail *-*-* } line_S1_f1 }
+// { dg-bogus "marked 'final', but is not virtual"	 "" { xfail *-*-* } line_S1_f2 }
+// { dg-bogus "marked '(override|final)'"		 "" { xfail *-*-* } line_S1_f3 }
+
+// I don't want to suppress these, there is nothing that could possibly be overridden
+// even if the xobj param was removed
+// { dg-error "marked 'override', but does not override" "" { target *-*-* } line_S1_f5 }
+// { dg-error "marked 'final', but is not virtual"	 "" { target *-*-* } line_S1_f6 }
+// { dg-error "marked '(override|final)'"		 "" { target *-*-* } line_S1_f7 }
+
-- 
2.42.1


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-06  7:33                                                                                                                                 ` [PATCH v7 " waffl3x
@ 2023-12-06  8:48                                                                                                                                   ` Jakub Jelinek
  2023-12-06  9:31                                                                                                                                     ` waffl3x
  2023-12-06 11:08                                                                                                                                   ` waffl3x
  2023-12-08 19:25                                                                                                                                   ` Jason Merrill
  2 siblings, 1 reply; 100+ messages in thread
From: Jakub Jelinek @ 2023-12-06  8:48 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

On Wed, Dec 06, 2023 at 07:33:21AM +0000, waffl3x wrote:
> Here is the next version, it feels very close to finished. As before, I
> haven't ran a bootstrap or the full testsuite yet but I did run the
> explicit-obj tests which completed as expected.
> 
> There's a few test cases that still need to be written but more tests
> can always be added. The behavior added by CWG2789 works in at least
> one case, but I have not added tests for it yet. The test cases for
> dependent lambda expressions need to be fleshed out more, but a few
> temporary ones are included to demonstrate that they do work and that
> the crash is fixed. Explicit object conversion functions work, but I
> need to add fleshed out tests for them, explicit-obj-basic5.C has that
> test.
> 
> I'll start the tests now and report back if anything fails, I'm
> confident everything will be fine though.
> 
> Alex

> From 937e12c57145bfd878a0bc4cd9735c2d3c4fcf22 Mon Sep 17 00:00:00 2001
> From: Waffl3x <waffl3x@protonmail.com>
> Date: Tue, 5 Dec 2023 23:16:01 -0700
> Subject: [PATCH] P0847R7 (Deducing This) [PR102609] Another quick and dirty
>  patch for review, hopefully the last. gcc/cp/ChangeLog:
> 

Please add
	PR c++/102609
line above this.

> 	* call.cc (build_this_conversion):

Note, for the final submission, all the ):
should be followed by descriptions what has changed in there (but not why).
Plus it would be good to mention somewhere early in the cp/ChangeLog
entry that the patch implements C++23 P0847R7 - Deducing this paper
(unfortunately the ChangeLog verifier doesn't allow such free text above
the ChangeLog entry where it used to be written some years ago,
only allows there the PR line; I usually put such text after the ):
of the first entry now and only after it write exactly what changed
in that function.  Does the patch also implement CWG2586?

Also, I don't see in the patch the expected
gcc/c-family/
	* c-cppbuiltin.cc (c_cpp_builtins): Predefine
	__cpp_explicit_this_parameter=202110L for C++23.
part plus gcc/testsuite/cpp{23,26}/feat-cxx*.C additions checking
for that macro presence and its value.

	Jakub


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-06  8:48                                                                                                                                   ` Jakub Jelinek
@ 2023-12-06  9:31                                                                                                                                     ` waffl3x
  0 siblings, 0 replies; 100+ messages in thread
From: waffl3x @ 2023-12-06  9:31 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: Jason Merrill, gcc-patches






On Wednesday, December 6th, 2023 at 1:48 AM, Jakub Jelinek <jakub@redhat.com> wrote:


> 
> 
> On Wed, Dec 06, 2023 at 07:33:21AM +0000, waffl3x wrote:
> 
> > Here is the next version, it feels very close to finished. As before, I
> > haven't ran a bootstrap or the full testsuite yet but I did run the
> > explicit-obj tests which completed as expected.
> > 
> > There's a few test cases that still need to be written but more tests
> > can always be added. The behavior added by CWG2789 works in at least
> > one case, but I have not added tests for it yet. The test cases for
> > dependent lambda expressions need to be fleshed out more, but a few
> > temporary ones are included to demonstrate that they do work and that
> > the crash is fixed. Explicit object conversion functions work, but I
> > need to add fleshed out tests for them, explicit-obj-basic5.C has that
> > test.
> > 
> > I'll start the tests now and report back if anything fails, I'm
> > confident everything will be fine though.
> > 
> > Alex
> 
> > From 937e12c57145bfd878a0bc4cd9735c2d3c4fcf22 Mon Sep 17 00:00:00 2001
> > From: Waffl3x waffl3x@protonmail.com
> > Date: Tue, 5 Dec 2023 23:16:01 -0700
> > Subject: [PATCH] P0847R7 (Deducing This) [PR102609] Another quick and dirty
> > patch for review, hopefully the last. gcc/cp/ChangeLog:
> 
> 
> Please add
> PR c++/102609
> line above this.
> 
> > * call.cc (build_this_conversion):
> 
> 
> Note, for the final submission, all the ):
> should be followed by descriptions what has changed in there (but not why).

Yeah, I remember the drill, it just takes me a long time so I've been
slacking.

> Plus it would be good to mention somewhere early in the cp/ChangeLog
> entry that the patch implements C++23 P0847R7 - Deducing this paper
> (unfortunately the ChangeLog verifier doesn't allow such free text above
> the ChangeLog entry where it used to be written some years ago,
> only allows there the PR line; I usually put such text after the ):
> of the first entry now and only after it write exactly what changed
> in that function. Does the patch also implement CWG2586?

Oh jeez, I had been doing it the way you're saying is rejected.
Shouldn't the ChangeLog verifier be changed to allow this?

The patch does not implement CWG2586 at this time. I couldn't determine
if it were ready to go or not. I have a skeleton of tests for it that I
never finished, but as far as I know the implementation does conform to
CWG2789, this just happened to be how it worked out.

> 
> Also, I don't see in the patch the expected
> gcc/c-family/
> * c-cppbuiltin.cc (c_cpp_builtins): Predefine
> __cpp_explicit_this_parameter=202110L for C++23.
> part plus gcc/testsuite/cpp{23,26}/feat-cxx*.C additions checking
> for that macro presence and its value.
> 
> Jakub

Yeah I was meaning to look into how to do that, I originally added the
test and then never included it in any of the patches, or that's what
remember anyway. This saves me the work though, I'll be sure to add
that.

Alex


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-06  7:33                                                                                                                                 ` [PATCH v7 " waffl3x
  2023-12-06  8:48                                                                                                                                   ` Jakub Jelinek
@ 2023-12-06 11:08                                                                                                                                   ` waffl3x
  2023-12-08 19:25                                                                                                                                   ` Jason Merrill
  2 siblings, 0 replies; 100+ messages in thread
From: waffl3x @ 2023-12-06 11:08 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

Follow up to this, bootstrapped and tested with no regressions.

On Wednesday, December 6th, 2023 at 12:33 AM, waffl3x <waffl3x@protonmail.com> wrote:


> 
> 
> Here is the next version, it feels very close to finished. As before, I
> haven't ran a bootstrap or the full testsuite yet but I did run the
> explicit-obj tests which completed as expected.
> 
> There's a few test cases that still need to be written but more tests
> can always be added. The behavior added by CWG2789 works in at least
> one case, but I have not added tests for it yet. The test cases for
> dependent lambda expressions need to be fleshed out more, but a few
> temporary ones are included to demonstrate that they do work and that
> the crash is fixed. Explicit object conversion functions work, but I
> need to add fleshed out tests for them, explicit-obj-basic5.C has that
> test.
> 
> I'll start the tests now and report back if anything fails, I'm
> confident everything will be fine though.
> 
> Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-06  7:33                                                                                                                                 ` [PATCH v7 " waffl3x
  2023-12-06  8:48                                                                                                                                   ` Jakub Jelinek
  2023-12-06 11:08                                                                                                                                   ` waffl3x
@ 2023-12-08 19:25                                                                                                                                   ` Jason Merrill
  2023-12-10 15:22                                                                                                                                     ` waffl3x
  2 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-12-08 19:25 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 9341 bytes --]

On 12/6/23 02:33, waffl3x wrote:
> Here is the next version, it feels very close to finished. As before, I
> haven't ran a bootstrap or the full testsuite yet but I did run the
> explicit-obj tests which completed as expected.
> 
> There's a few test cases that still need to be written but more tests
> can always be added. The behavior added by CWG2789 works in at least
> one case, but I have not added tests for it yet. The test cases for
> dependent lambda expressions need to be fleshed out more, but a few
> temporary ones are included to demonstrate that they do work and that
> the crash is fixed. Explicit object conversion functions work, but I
> need to add fleshed out tests for them, explicit-obj-basic5.C has that
> test.

> @@ -6586,6 +6586,17 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> *args,
> +           /* FIXME: I believe this will be bugged for xobj member functions,
> +              leaving this comment here to make sure we look into it
> +              at some point.
> +              Seeing this makes me want correspondence checking to be unified
> +              in one place though, not sure if this one needs to be different
> +              from other ones though.
> +              This function is only used here, but maybe we can use it in add
> +              method and move some of the logic out of there?

fns_correspond absolutely needs updating to handle xob fns, and doing 
that by unifying it with add_method's calculation would be good.

> +              Side note: CWG2586 might be relevant for this area in
> +              particular, perhaps we wait to see if it gets accepted first?  */

2586 was accepted last year.

> @@ -12574,17 +12601,25 @@ cand_parms_match (z_candidate *c1, z_candidate *c2)
>        fn1 = DECL_TEMPLATE_RESULT (t1);
>        fn2 = DECL_TEMPLATE_RESULT (t2);
>      }
> +  /* The changes I made here might be stuff I was told not to worry about?
> +     I'm not really sure so I'm going to leave it in.  */

Good choice, this comment can go.

>    tree parms1 = TYPE_ARG_TYPES (TREE_TYPE (fn1));
>    tree parms2 = TYPE_ARG_TYPES (TREE_TYPE (fn2));
>    if (DECL_FUNCTION_MEMBER_P (fn1)
>        && DECL_FUNCTION_MEMBER_P (fn2)
> -      && (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn1)
> -         != DECL_NONSTATIC_MEMBER_FUNCTION_P (fn2)))
> +      && (DECL_STATIC_FUNCTION_P (fn1)
> +         != DECL_STATIC_FUNCTION_P (fn2)))
>      {
>        /* Ignore 'this' when comparing the parameters of a static member
>          function with those of a non-static one.  */
> -      parms1 = skip_artificial_parms_for (fn1, parms1);
> -      parms2 = skip_artificial_parms_for (fn2, parms2);
> +      auto skip_parms = [](tree fn, tree parms){
> +         if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
> +           return TREE_CHAIN (parms);
> +         else
> +           return skip_artificial_parms_for (fn, parms);
> +       };
> +      parms1 = skip_parms (fn1, parms1);
> +      parms2 = skip_parms (fn2, parms2);
>      }

https://cplusplus.github.io/CWG/issues/2789.html fixes the handling of 
xobj fns here.

Your change does the right thing for comparing static and xobj, but 
doesn't handle comparing iobj and xobj; I think we want to share 
parameter comparison code with fns_correspond/add_method.  Maybe 
parms_correspond?

> @@ -8727,21 +8882,42 @@ resolve_address_of_overloaded_function (tree target_type,
>    /* Good, exactly one match.  Now, convert it to the correct type.  */
>    fn = TREE_PURPOSE (matches);
>  
> -  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
> -      && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
> +  if (DECL_OBJECT_MEMBER_FUNCTION_P (fn)
> +      && !(complain & tf_ptrmem_ok))
>      {
> -      static int explained;
> -
> -      if (!(complain & tf_error))
> +      /* For iobj member functions, if if -fms_extensions was passed in, this
> +        is not an error, so we do nothing.  It is still an error regardless
> +        for xobj member functions though, as it is a new feature we
> +        (hopefully) don't need to support the behavior.  */

Unfortunately, it seems that MSVC extended their weirdness to xobj fns, 
so -fms-extensions should as well.
https://godbolt.org/z/nfvn64Kx5

> +                 /* I'm keeping it more basic for now.  */

OK, this comment can go.

> @@ -15502,9 +15627,10 @@ void
>  grok_special_member_properties (tree decl)
>  {
>    tree class_type;
> -
> +  /* I believe we have to make some changes in here depending on the outcome
> +     of CWG2586.  */

As mentioned above, CWG2586 is resolved.  Be sure to scroll down to the 
approved resolution, or refer to the working draft.
https://cplusplus.github.io/CWG/issues/2586.html

> @@ -11754,8 +11754,16 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tre
>    else if (cxx_dialect < cxx23)
>      omitted_parms_loc = cp_lexer_peek_token (parser->lexer)->location;
>  
> +  /* Review note: I figured I might as well update the comments since I'm here.
> +     There are also some additions to the below.  */

Great, this comment can go.

> +      /* [expr.prim.lambda.general-4]
> +        If the lambda-declarator contains an explicit object parameter
> +        ([dcl.fct]), then no lambda-specifier in the lambda-specifier-seq
> +        shall be mutable or static.  */
> +      if (lambda_specs.storage_class == sc_mutable)
> +       {
> +         auto_diagnostic_group d;
> +         error_at (lambda_specs.locations[ds_storage_class],
> +                   "%<mutable%> lambda specifier "
> +                   "with explicit object parameter");
> +         /* Tell the user how to do what they probably meant, maybe fixits
> +            would be apropriate later?  */

"appropriate"

> +         if (!dependent_type_p (non_reference (param_type)))
> +           /* If we are given a non-dependent type we will have already given
> +              a diagnosis that the following would contradict with.  */;

Only if the lambda has captures, though?

We could also change dependent_type_p to the more specific 
WILDCARD_TYPE_P, I think, both here and just above.

> +         else if (!TYPE_REF_P (param_type))
> +           inform (DECL_SOURCE_LOCATION (xobj_param),
> +                   "the passed in closure object will not be mutated because "
> +                   "it is taken by copy/move");

"by value"

> @@ -3092,7 +3093,31 @@ finish_this_expr (void)
>      return rvalue (result);
>  
>    tree fn = current_nonlambda_function ();
> -  if (fn && DECL_STATIC_FUNCTION_P (fn))
> +  if (fn && DECL_XOBJ_MEMBER_FUNCTION_P (fn))
> +    {
> +      auto_diagnostic_group d;
> +      error ("%<this%> is unavailable for explicit object member "
> +            "functions");
> +      /* Doing a fixit here is possible, but hard, might be worthwhile
> +        in the future.  */
> +      tree xobj_parm = DECL_ARGUMENTS (fn);
> +      gcc_assert (xobj_parm);
> +      tree parm_name = DECL_NAME (xobj_parm);
> +      if (parm_name)
> +       inform (DECL_SOURCE_LOCATION (xobj_parm),
> +               "use explicit object parameter %qD instead",
> +               parm_name);
> +      else
> +       {
> +         gcc_rich_location xobj_loc (DECL_SOURCE_LOCATION (xobj_parm));
> +         /* This doesn't work and I don't know why.  I'll probably remove it
> +            before the final version.  */
> +         xobj_loc.add_fixit_insert_after (" self");
> +         inform (DECL_SOURCE_LOCATION (xobj_parm),
> +                 "name and use the explicit object parameter instead");

Now seems to be the time to either fix or remove it.

> @@ -12811,9 +12836,11 @@ capture_decltype (tree decl)
>        if (WILDCARD_TYPE_P (non_reference (obtype)))
>         /* We don't know what the eventual obtype quals will be.  */
>         return NULL_TREE;
> -      int quals = cp_type_quals (type);
> -      if (INDIRECT_TYPE_P (obtype))
> -       quals |= cp_type_quals (TREE_TYPE (obtype));
> +      /* This change possibly conflicts with another patch that is currently
> +        in flight. (Patrick Palka's PR83167 patch) I am just changing it for
> +        now to satisfy my tests.  */
> +      int const quals = cp_type_quals (type)
> +                     | cp_type_quals (TREE_TYPE (obtype));

Doesn't TREE_TYPE (obtype) break for by-value xob?  In that case I think 
we'd want cp_type_quals (obtype).

> +   The name "nonstatic" is no longer accurate with the addition of
> +   xobj member functions, should we change the name?  */
>  
>  bool
>  invalid_nonstatic_memfn_p (location_t loc, tree expr, tsubst_flags_t complain)

This rule still applies to all non-static member functions:
https://eel.is/c++draft/expr.ref#6.3.2

> @@ -2352,7 +2355,7 @@ invalid_nonstatic_memfn_p (location_t loc, tree expr, tsubst_flags_t complain)
>    if (is_overloaded_fn (expr) && !really_overloaded_fn (expr))
>      expr = get_first_fn (expr);
>    if (TREE_TYPE (expr)
> -      && DECL_NONSTATIC_MEMBER_FUNCTION_P (expr))
> +      && DECL_IOBJ_MEMBER_FUNCTION_P (expr))

...and so I think this should be _OBJECT_.
https://godbolt.org/z/r6v4e1ePP

Here's a patch to adjust all the remaining 
DECL_NONSTATIC_MEMBER_FUNCTION_P.  With this patch -diagnostic7.C gets 
the old address of non-static diagnostic, I think correctly, so I'm not 
sure we still need the BASELINK change in cp_build_addr_expr_1?

Jason

[-- Attachment #2: 0001-c-avoid-DECL_NONSTATIC_MEMBER_FUNCTION_P.patch --]
[-- Type: text/x-patch, Size: 22914 bytes --]

From 6322ee6863f0385f1adec1e59672000e6133184b Mon Sep 17 00:00:00 2001
From: Jason Merrill <jason@redhat.com>
Date: Wed, 6 Dec 2023 21:30:18 -0500
Subject: [PATCH] c++: avoid DECL_NONSTATIC_MEMBER_FUNCTION_P
To: gcc-patches@gcc.gnu.org

Explicit object member functions are also non-static member functions, so we
need to change uses of DECL_NONSTATIC_MEMBER_FUNCTION_P to clarify whether
they intend to include or exclude them.

gcc/cp/ChangeLog:

	* cp-tree.h (DECL_NONSTATIC_MEMBER_FUNCTION_P): Poison.
	(DECL_CONST_MEMFUNC_P)
	(DECL_VOLATILE_MEMFUNC_P)
	(DECL_NONSTATIC_MEMBER_P)
	* call.cc (maybe_warn_class_memaccess)
	(joust)
	(do_warn_dangling_reference): Don't use it.
	* class.cc (finalize_literal_type_property)
	(finish_struct)
	* constexpr.cc (is_valid_constexpr_fn)
	* contracts.cc (build_contract_condition_function)
	* cp-objcp-common.cc (cp_decl_dwarf_attribute)
	* cxx-pretty-print.cc (cxx_pretty_printer::postfix_expression)
	(cxx_pretty_printer::declaration_specifiers)
	(cxx_pretty_printer::direct_declarator)
	* decl.cc (cp_finish_decl)
	(record_key_method_defined)
	* decl2.cc (cp_handle_deprecated_or_unavailable)
	* init.cc (find_uninit_fields_r)
	(build_offset_ref)
	* lambda.cc (nonlambda_method_basetype)
	* method.cc (early_check_defaulted_comparison)
	(num_artificial_parms_for)
	* pt.cc (is_specialization_of_friend)
	(determine_specialization)
	(copy_default_args_to_explicit_spec)
	(check_explicit_specialization)
	(tsubst_contract_attribute)
	(check_non_deducible_conversions)
	(maybe_instantiate_noexcept)
	(register_parameter_specializations)
	(value_dependent_expression_p)
	* search.cc (shared_member_p)
	(lookup_member)
	(field_access_p)
	* semantics.cc (finish_omp_declare_simd_methods)
	* tree.cc (lvalue_kind): Don't use
	DECL_NONSTATIC_MEMBER_FUNCTION_P.

libcc1/ChangeLog:

	* libcp1plugin.cc (plugin_pragma_push_user_expression): Don't use
	DECL_NONSTATIC_MEMBER_FUNCTION_P.
---
 gcc/cp/cp-tree.h           | 16 +++++++++-------
 gcc/cp/call.cc             | 11 ++++++-----
 gcc/cp/class.cc            |  4 ++--
 gcc/cp/constexpr.cc        |  2 +-
 gcc/cp/contracts.cc        |  6 +++---
 gcc/cp/cp-objcp-common.cc  |  4 ++--
 gcc/cp/cxx-pretty-print.cc |  6 +++---
 gcc/cp/decl.cc             |  4 ++--
 gcc/cp/decl2.cc            |  2 +-
 gcc/cp/init.cc             |  4 ++--
 gcc/cp/lambda.cc           |  2 +-
 gcc/cp/method.cc           |  6 +++---
 gcc/cp/pt.cc               | 24 ++++++++++++------------
 gcc/cp/search.cc           |  5 +++--
 gcc/cp/semantics.cc        |  2 +-
 gcc/cp/tree.cc             |  2 +-
 libcc1/libcp1plugin.cc     |  2 +-
 17 files changed, 53 insertions(+), 49 deletions(-)

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index e222742092f..111cb7f6a0e 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -3348,10 +3348,12 @@ struct GTY(()) lang_decl {
 #define DECL_STATIC_FUNCTION_P(NODE) \
   (LANG_DECL_FN_CHECK (NODE)->static_function)
 
-/* Nonzero for FUNCTION_DECL means that this decl is a non-static
-   member function, use DECL_IOBJ_MEMBER_FUNCTION_P instead.  */
-#define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) \
-  (TREE_CODE (TREE_TYPE (NODE)) == METHOD_TYPE)
+/* Nonzero for FUNCTION_DECL means that this decl is a non-static member
+   function.  C++23 explicit object member functions are also considered
+   non-static, but most former uses of this macro meant implicit object member
+   function.  Instead of this macro, use DECL_IOBJ_MEMBER_FUNCTION_P or
+   DECL_OBJECT_MEMBER_FUNCTION_P.  */
+#define DECL_NONSTATIC_MEMBER_FUNCTION_P(NODE) did_you_mean_object_or_iobj
 
 /* Nonzero for FUNCTION_DECL means that this decl is an implicit object
    member function.  */
@@ -3381,20 +3383,20 @@ struct GTY(()) lang_decl {
 /* Nonzero for FUNCTION_DECL means that this member function
    has `this' as const X *const.  */
 #define DECL_CONST_MEMFUNC_P(NODE)					 \
-  (DECL_NONSTATIC_MEMBER_FUNCTION_P (NODE)				 \
+  (DECL_IOBJ_MEMBER_FUNCTION_P (NODE)				 \
    && CP_TYPE_CONST_P (TREE_TYPE (TREE_VALUE				 \
 				  (TYPE_ARG_TYPES (TREE_TYPE (NODE))))))
 
 /* Nonzero for FUNCTION_DECL means that this member function
    has `this' as volatile X *const.  */
 #define DECL_VOLATILE_MEMFUNC_P(NODE)					 \
-  (DECL_NONSTATIC_MEMBER_FUNCTION_P (NODE)				 \
+  (DECL_IOBJ_MEMBER_FUNCTION_P (NODE)				 \
    && CP_TYPE_VOLATILE_P (TREE_TYPE (TREE_VALUE				 \
 				  (TYPE_ARG_TYPES (TREE_TYPE (NODE))))))
 
 /* Nonzero for a DECL means that this member is a non-static member.  */
 #define DECL_NONSTATIC_MEMBER_P(NODE)		\
-  (DECL_NONSTATIC_MEMBER_FUNCTION_P (NODE)	\
+  (DECL_OBJECT_MEMBER_FUNCTION_P (NODE)	\
    || TREE_CODE (NODE) == FIELD_DECL)
 
 /* Nonzero for a FIELD_DECL means that this member object type
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index c64d420f073..3d9b575a89b 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -10705,7 +10705,8 @@ maybe_warn_class_memaccess (location_t loc, tree fndecl,
      type.  If so, and if the class has no non-trivial bases or members,
      be more permissive.  */
   if (current_function_decl
-      && DECL_NONSTATIC_MEMBER_FUNCTION_P (current_function_decl)
+      && DECL_OBJECT_MEMBER_FUNCTION_P (current_function_decl)
+      /* ??? is_object_parameter?  */
       && is_this_parameter (tree_strip_nop_conversions (dest)))
     {
       tree ctx = DECL_CONTEXT (current_function_decl);
@@ -12799,7 +12800,7 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn,
 			  print_z_candidate (input_location,
 					     N_("candidate 2:"), l);
 			  if (w->fn == l->fn
-			      && DECL_NONSTATIC_MEMBER_FUNCTION_P (w->fn)
+			      && DECL_IOBJ_MEMBER_FUNCTION_P (w->fn)
 			      && (type_memfn_quals (TREE_TYPE (w->fn))
 				  & TYPE_QUAL_CONST) == 0)
 			    {
@@ -13929,7 +13930,7 @@ do_warn_dangling_reference (tree expr, bool arg_p)
 	       because R refers to one of the int elements of V, not to
 	       a temporary object.  Member operator* may return a reference
 	       but probably not to one of its arguments.  */
-	    || (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
+	    || (DECL_OBJECT_MEMBER_FUNCTION_P (fndecl)
 		&& DECL_OVERLOADED_OPERATOR_P (fndecl)
 		&& DECL_OVERLOADED_OPERATOR_IS (fndecl, INDIRECT_REF)))
 	  return NULL_TREE;
@@ -13955,7 +13956,7 @@ do_warn_dangling_reference (tree expr, bool arg_p)
 	    tree arg = CALL_EXPR_ARG (expr, i);
 	    /* Check that this argument initializes a reference, except for
 	       the argument initializing the object of a member function.  */
-	    if (!DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
+	    if (!DECL_IOBJ_MEMBER_FUNCTION_P (fndecl)
 		&& !TYPE_REF_P (TREE_TYPE (arg)))
 	      continue;
 	    STRIP_NOPS (arg);
@@ -13975,7 +13976,7 @@ do_warn_dangling_reference (tree expr, bool arg_p)
 	       const S& s = S().self();
 	     where 's' dangles.  If we've gotten here, the object this function
 	     is invoked on is not a temporary.  */
-	    if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl))
+	    if (DECL_OBJECT_MEMBER_FUNCTION_P (fndecl))
 	      break;
 	  }
 	return NULL_TREE;
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index d7a4ad5ad51..99c33380c2d 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -6123,7 +6123,7 @@ finalize_literal_type_property (tree t)
     for (fn = TYPE_FIELDS (t); fn; fn = DECL_CHAIN (fn))
       if (TREE_CODE (fn) == FUNCTION_DECL
 	  && DECL_DECLARED_CONSTEXPR_P (fn)
-	  && DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+	  && DECL_IOBJ_MEMBER_FUNCTION_P (fn)
 	  && !DECL_CONSTRUCTOR_P (fn))
 	{
 	  DECL_DECLARED_CONSTEXPR_P (fn) = false;
@@ -8087,7 +8087,7 @@ finish_struct (tree t, tree attributes)
   if (flag_openmp)
     for (tree decl = TYPE_FIELDS (t); decl; decl = DECL_CHAIN (decl))
       if (TREE_CODE (decl) == FUNCTION_DECL
-	  && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
+	  && DECL_OBJECT_MEMBER_FUNCTION_P (decl))
 	if (tree attr = lookup_attribute ("omp declare variant base",
 					  DECL_ATTRIBUTES (decl)))
 	  omp_declare_variant_finalize (decl, attr);
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 58187a4fd12..4da6f1a0524 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -291,7 +291,7 @@ is_valid_constexpr_fn (tree fun, bool complain)
 
       /* C++14 DR 1684 removed this restriction.  */
       if (cxx_dialect < cxx14
-	  && DECL_NONSTATIC_MEMBER_FUNCTION_P (fun)
+	  && DECL_IOBJ_MEMBER_FUNCTION_P (fun)
 	  && !CLASSTYPE_LITERAL_P (DECL_CONTEXT (fun)))
 	{
 	  ret = false;
diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc
index 035ca4827e7..c505b67d339 100644
--- a/gcc/cp/contracts.cc
+++ b/gcc/cp/contracts.cc
@@ -1398,7 +1398,7 @@ build_contract_condition_function (tree fndecl, bool pre)
 {
   if (TREE_TYPE (fndecl) == error_mark_node)
     return error_mark_node;
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
+  if (DECL_IOBJ_MEMBER_FUNCTION_P (fndecl)
       && !TYPE_METHOD_BASETYPE (TREE_TYPE (fndecl)))
     return error_mark_node;
 
@@ -1421,7 +1421,7 @@ build_contract_condition_function (tree fndecl, bool pre)
       arg_type && arg_type != void_list_node;
       arg_type = TREE_CHAIN (arg_type))
     {
-      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
+      if (DECL_IOBJ_MEMBER_FUNCTION_P (fndecl)
 	  && TYPE_ARG_TYPES (TREE_TYPE (fn)) == arg_type)
       {
 	class_type = TREE_TYPE (TREE_VALUE (arg_type));
@@ -1451,7 +1451,7 @@ build_contract_condition_function (tree fndecl, bool pre)
     }
 
   TREE_TYPE (fn) = build_function_type (value_type, arg_types);
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl))
+  if (DECL_IOBJ_MEMBER_FUNCTION_P (fndecl))
     TREE_TYPE (fn) = build_method_type (class_type, TREE_TYPE (fn));
 
   DECL_NAME (fn) = copy_node (DECL_NAME (fn));
diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc
index 9439c4dc744..f3ba8179329 100644
--- a/gcc/cp/cp-objcp-common.cc
+++ b/gcc/cp/cp-objcp-common.cc
@@ -349,7 +349,7 @@ cp_decl_dwarf_attribute (const_tree decl, int attr)
 
     case DW_AT_reference:
       if (TREE_CODE (decl) == FUNCTION_DECL
-	  && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl)
+	  && DECL_IOBJ_MEMBER_FUNCTION_P (decl)
 	  && FUNCTION_REF_QUALIFIED (TREE_TYPE (decl))
 	  && !FUNCTION_RVALUE_QUALIFIED (TREE_TYPE (decl)))
 	return 1;
@@ -357,7 +357,7 @@ cp_decl_dwarf_attribute (const_tree decl, int attr)
 
     case DW_AT_rvalue_reference:
       if (TREE_CODE (decl) == FUNCTION_DECL
-	  && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl)
+	  && DECL_IOBJ_MEMBER_FUNCTION_P (decl)
 	  && FUNCTION_REF_QUALIFIED (TREE_TYPE (decl))
 	  && FUNCTION_RVALUE_QUALIFIED (TREE_TYPE (decl)))
 	return 1;
diff --git a/gcc/cp/cxx-pretty-print.cc b/gcc/cp/cxx-pretty-print.cc
index 6a82358f370..53c0722d2f3 100644
--- a/gcc/cp/cxx-pretty-print.cc
+++ b/gcc/cp/cxx-pretty-print.cc
@@ -553,7 +553,7 @@ cxx_pretty_printer::postfix_expression (tree t)
 	   instantiation time.  */
 	if (TREE_CODE (fun) != FUNCTION_DECL)
 	  ;
-	else if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fun))
+	else if (DECL_OBJECT_MEMBER_FUNCTION_P (fun))
 	  {
 	    tree object = (code == AGGR_INIT_EXPR
 			   ? (AGGR_INIT_VIA_CTOR_P (t)
@@ -1342,7 +1342,7 @@ cxx_pretty_printer::declaration_specifiers (tree t)
 	 do not have a type-specifier in their return types.  */
       if (DECL_CONSTRUCTOR_P (t) || DECL_CONV_FN_P (t))
 	function_specifier (t);
-      else if (DECL_NONSTATIC_MEMBER_FUNCTION_P (t))
+      else if (DECL_IOBJ_MEMBER_FUNCTION_P (t))
 	declaration_specifiers (TREE_TYPE (TREE_TYPE (t)));
       else
         c_pretty_printer::declaration_specifiers (t);
@@ -1700,7 +1700,7 @@ cxx_pretty_printer::direct_declarator (tree t)
       expression (t);
       pp_cxx_parameter_declaration_clause (this, t);
 
-      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (t))
+      if (DECL_IOBJ_MEMBER_FUNCTION_P (t))
 	{
 	  padding = pp_before;
 	  pp_cxx_cv_qualifier_seq (this, pp_cxx_implicit_parameter_type (t));
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index e51ede5d043..e5d19ab9162 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -8524,7 +8524,7 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p,
       && TREE_CODE (decl) == FUNCTION_DECL
       /* #pragma omp declare variant on methods handled in finish_struct
 	 instead.  */
-      && (!DECL_NONSTATIC_MEMBER_FUNCTION_P (decl)
+      && (!DECL_OBJECT_MEMBER_FUNCTION_P (decl)
 	  || COMPLETE_TYPE_P (DECL_CONTEXT (decl))))
     if (tree attr = lookup_attribute ("omp declare variant base",
 				      DECL_ATTRIBUTES (decl)))
@@ -18288,7 +18288,7 @@ outer_curly_brace_block (tree fndecl)
 static void
 record_key_method_defined (tree fndecl)
 {
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)
+  if (DECL_OBJECT_MEMBER_FUNCTION_P (fndecl)
       && DECL_VIRTUAL_P (fndecl)
       && !processing_template_decl)
     {
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index bee84879023..59c41d8e583 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -5625,7 +5625,7 @@ cp_handle_deprecated_or_unavailable (tree decl, tsubst_flags_t complain)
   if (cxx_dialect >= cxx11
       && DECL_P (decl)
       && DECL_ARTIFICIAL (decl)
-      && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl)
+      && DECL_IOBJ_MEMBER_FUNCTION_P (decl)
       && copy_fn_p (decl))
     {
       /* Don't warn if the flag was disabled around the class definition
diff --git a/gcc/cp/init.cc b/gcc/cp/init.cc
index 6444f0a8518..fb5f404805b 100644
--- a/gcc/cp/init.cc
+++ b/gcc/cp/init.cc
@@ -866,7 +866,7 @@ find_uninit_fields_r (tree *tp, int *walk_subtrees, void *data)
   else if (code == CALL_EXPR)
     {
       tree fn = get_callee_fndecl (init);
-      if (fn && DECL_NONSTATIC_MEMBER_FUNCTION_P (fn))
+      if (fn && DECL_IOBJ_MEMBER_FUNCTION_P (fn))
 	{
 	  tree op = CALL_EXPR_ARG (init, 0);
 	  if (TREE_CODE (op) == ADDR_EXPR)
@@ -2477,7 +2477,7 @@ build_offset_ref (tree type, tree member, bool address_p,
 
 	   -- in a mem-initializer for a constructor for that class or for
 	   a class derived from that class (_class.base.init_).  */
-      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (member))
+      if (DECL_OBJECT_MEMBER_FUNCTION_P (member))
 	{
 	  /* Build a representation of the qualified name suitable
 	     for use as the operand to "&" -- even though the "&" is
diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc
index 8baf82578c7..ee48b2170d2 100644
--- a/gcc/cp/lambda.cc
+++ b/gcc/cp/lambda.cc
@@ -1015,7 +1015,7 @@ nonlambda_method_basetype (void)
 
       tree fn = TYPE_CONTEXT (type);
       if (!fn || TREE_CODE (fn) != FUNCTION_DECL
-	  || !DECL_NONSTATIC_MEMBER_FUNCTION_P (fn))
+	  || !DECL_IOBJ_MEMBER_FUNCTION_P (fn))
 	/* No enclosing non-lambda method.  */
 	return NULL_TREE;
       if (!LAMBDA_FUNCTION_P (fn))
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index ca3102a57a5..8302ae2370d 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -1187,7 +1187,7 @@ early_check_defaulted_comparison (tree fn)
 	ok = false;
     }
 
-  bool mem = DECL_NONSTATIC_MEMBER_FUNCTION_P (fn);
+  bool mem = DECL_IOBJ_MEMBER_FUNCTION_P (fn);
   if (mem && type_memfn_quals (TREE_TYPE (fn)) != TYPE_QUAL_CONST)
     {
       error_at (loc, "defaulted %qD must be %<const%>", fn);
@@ -1230,7 +1230,7 @@ early_check_defaulted_comparison (tree fn)
 
   if (saw_bad || (saw_byval && saw_byref))
     {
-      if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn))
+      if (DECL_IOBJ_MEMBER_FUNCTION_P (fn))
 	error_at (loc, "defaulted member %qD must have parameter type "
 		  "%<const %T&%>", fn, ctx);
       else if (saw_bad)
@@ -3627,7 +3627,7 @@ num_artificial_parms_for (const_tree fn)
 {
   int count = 0;
 
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn))
+  if (DECL_IOBJ_MEMBER_FUNCTION_P (fn))
     count++;
   else
     return 0;
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 4a677d5910b..76b5640beac 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -1443,9 +1443,9 @@ is_specialization_of_friend (tree decl, tree friend_decl)
 	     `this' parameter.  */
 	  friend_args_type = TYPE_ARG_TYPES (friend_type);
 	  decl_args_type = TYPE_ARG_TYPES (decl_type);
-	  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (friend_decl))
+	  if (DECL_IOBJ_MEMBER_FUNCTION_P (friend_decl))
 	    friend_args_type = TREE_CHAIN (friend_args_type);
-	  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
+	  if (DECL_IOBJ_MEMBER_FUNCTION_P (decl))
 	    decl_args_type = TREE_CHAIN (decl_args_type);
 
 	  return compparms (decl_args_type, friend_args_type);
@@ -2222,7 +2222,7 @@ determine_specialization (tree template_id,
 	     that the const qualification is the same.  Since
 	     get_bindings does not try to merge the "this" parameter,
 	     we must do the comparison explicitly.  */
-	  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn))
+	  if (DECL_IOBJ_MEMBER_FUNCTION_P (fn))
 	    {
 	      if (!same_type_p (TREE_VALUE (fn_arg_types),
 				TREE_VALUE (decl_arg_types)))
@@ -2345,14 +2345,14 @@ determine_specialization (tree template_id,
 	  /* Adjust the type of DECL in case FN is a static member.  */
 	  decl_arg_types = TYPE_ARG_TYPES (TREE_TYPE (decl));
 	  if (DECL_STATIC_FUNCTION_P (fn)
-	      && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
+	      && DECL_IOBJ_MEMBER_FUNCTION_P (decl))
 	    decl_arg_types = TREE_CHAIN (decl_arg_types);
 
 	  if (!compparms (TYPE_ARG_TYPES (TREE_TYPE (fn)),
 			 decl_arg_types))
             continue;
 
-	  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+	  if (DECL_IOBJ_MEMBER_FUNCTION_P (fn)
 	      && (type_memfn_rqual (TREE_TYPE (decl))
 		  != type_memfn_rqual (TREE_TYPE (fn))))
 	    continue;
@@ -2538,7 +2538,7 @@ copy_default_args_to_explicit_spec (tree decl)
   old_type = TREE_TYPE (decl);
   spec_types = TYPE_ARG_TYPES (old_type);
 
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
+  if (DECL_IOBJ_MEMBER_FUNCTION_P (decl))
     {
       /* Remove the this pointer, but remember the object's type for
 	 CV quals.  */
@@ -3123,7 +3123,7 @@ check_explicit_specialization (tree declarator,
 	     make DECL a static member function as well.  */
 	  if (DECL_FUNCTION_TEMPLATE_P (tmpl)
 	      && DECL_STATIC_FUNCTION_P (tmpl)
-	      && DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
+	      && DECL_IOBJ_MEMBER_FUNCTION_P (decl))
 	    revert_static_member_fn (decl);
 
 	  /* If this is a specialization of a member template of a
@@ -11743,7 +11743,7 @@ tsubst_contract_attribute (tree decl, tree t, tree args,
   /* For member functions, make this available for semantic analysis.  */
   tree save_ccp = current_class_ptr;
   tree save_ccr = current_class_ref;
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
+  if (DECL_IOBJ_MEMBER_FUNCTION_P (decl))
     {
       tree arg_types = TYPE_ARG_TYPES (TREE_TYPE (decl));
       tree this_type = TREE_TYPE (TREE_VALUE (arg_types));
@@ -22054,7 +22054,7 @@ check_non_deducible_conversions (tree parms, const tree *args, unsigned nargs,
 {
   /* Non-constructor methods need to leave a conversion for 'this', which
      isn't included in nargs here.  */
-  unsigned offset = (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+  unsigned offset = (DECL_IOBJ_MEMBER_FUNCTION_P (fn)
 		     && !DECL_CONSTRUCTOR_P (fn));
 
   for (unsigned ia = 0;
@@ -26633,7 +26633,7 @@ maybe_instantiate_noexcept (tree fn, tsubst_flags_t complain)
 	  push_deferring_access_checks (dk_no_deferred);
 	  input_location = DECL_SOURCE_LOCATION (fn);
 
-	  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+	  if (DECL_IOBJ_MEMBER_FUNCTION_P (fn)
 	      && !DECL_LOCAL_DECL_P (fn))
 	    {
 	      /* If needed, set current_class_ptr for the benefit of
@@ -26696,7 +26696,7 @@ register_parameter_specializations (tree pattern, tree inst)
 {
   tree tmpl_parm = DECL_ARGUMENTS (pattern);
   tree spec_parm = DECL_ARGUMENTS (inst);
-  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (inst))
+  if (DECL_IOBJ_MEMBER_FUNCTION_P (inst))
     {
       register_local_specialization (spec_parm, tmpl_parm);
       spec_parm = skip_artificial_parms_for (inst, spec_parm);
@@ -28043,7 +28043,7 @@ value_dependent_expression_p (tree expression)
 	       cause the call to be considered value-dependent.  We also
 	       look through it in potential_constant_expression.  */
 	    if (i == 0 && fn && DECL_DECLARED_CONSTEXPR_P (fn)
-		&& DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
+		&& DECL_IOBJ_MEMBER_FUNCTION_P (fn)
 		&& TREE_CODE (op) == ADDR_EXPR)
 	      op = TREE_OPERAND (op, 0);
 	    if (value_dependent_expression_p (op))
diff --git a/gcc/cp/search.cc b/gcc/cp/search.cc
index efea5078992..a22202fe6f3 100644
--- a/gcc/cp/search.cc
+++ b/gcc/cp/search.cc
@@ -1010,7 +1010,7 @@ shared_member_p (tree t)
 	    /* Conservatively assume a dependent using-declaration
 	       might resolve to a non-static member.  */
 	    return false;
-	  if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
+	  if (DECL_OBJECT_MEMBER_FUNCTION_P (decl))
 	    return false;
 	}
       return true;
@@ -1266,7 +1266,7 @@ lookup_member (tree xbasetype, tree name, int protect, bool want_type,
       decl = strip_using_decl (decl);
       /* A dependent USING_DECL will be checked after tsubsting.  */
       if (TREE_CODE (decl) != USING_DECL
-	  && !DECL_NONSTATIC_MEMBER_FUNCTION_P (decl)
+	  && !DECL_IOBJ_MEMBER_FUNCTION_P (decl)
 	  && !perform_or_defer_access_check (basetype_path, decl, decl,
 					     complain, afi))
 	return error_mark_node;
@@ -1739,6 +1739,7 @@ field_access_p (tree component_ref, tree field_decl, tree field_type)
     return false;
 
   tree ptr = STRIP_NOPS (TREE_OPERAND (indirect_ref, 0));
+  /* ??? is_object_parameter?  */
   if (!is_this_parameter (ptr))
     return false;
 
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index c47f57e889d..21831792ec5 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -6704,7 +6704,7 @@ finish_omp_declare_simd_methods (tree t)
   for (tree x = TYPE_FIELDS (t); x; x = DECL_CHAIN (x))
     {
       if (TREE_CODE (x) == USING_DECL
-	  || !DECL_NONSTATIC_MEMBER_FUNCTION_P (x))
+	  || !DECL_IOBJ_MEMBER_FUNCTION_P (x))
 	continue;
       tree ods = lookup_attribute ("omp declare simd", DECL_ATTRIBUTES (x));
       if (!ods || !TREE_VALUE (ods))
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index 79b6fa05c06..4635cbbba5b 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -298,7 +298,7 @@ lvalue_kind (const_tree ref)
     case FUNCTION_DECL:
       /* All functions (except non-static-member functions) are
 	 lvalues.  */
-      return (DECL_NONSTATIC_MEMBER_FUNCTION_P (ref)
+      return (DECL_IOBJ_MEMBER_FUNCTION_P (ref)
 	      ? clk_none : clk_ordinary);
 
     case BASELINK:
diff --git a/libcc1/libcp1plugin.cc b/libcc1/libcp1plugin.cc
index 3c8e9e8b436..108d5786167 100644
--- a/libcc1/libcp1plugin.cc
+++ b/libcc1/libcp1plugin.cc
@@ -468,7 +468,7 @@ plugin_pragma_push_user_expression (cpp_reader *)
 	}
     }
 
-  if (unchanged_cfun || DECL_NONSTATIC_MEMBER_FUNCTION_P (changed_func_decl))
+  if (unchanged_cfun || DECL_OBJECT_MEMBER_FUNCTION_P (changed_func_decl))
     {
       /* Check whether the oracle supplies us with a "this", and if
 	 so, arrange for data members and this itself to be
-- 
2.39.3


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-08 19:25                                                                                                                                   ` Jason Merrill
@ 2023-12-10 15:22                                                                                                                                     ` waffl3x
  2023-12-10 18:59                                                                                                                                       ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-12-10 15:22 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

On Friday, December 8th, 2023 at 12:25 PM, Jason Merrill <jason@redhat.com> wrote:
> 
> 
> On 12/6/23 02:33, waffl3x wrote:
> 
> > Here is the next version, it feels very close to finished. As before, I
> > haven't ran a bootstrap or the full testsuite yet but I did run the
> > explicit-obj tests which completed as expected.
> > 
> > There's a few test cases that still need to be written but more tests
> > can always be added. The behavior added by CWG2789 works in at least
> > one case, but I have not added tests for it yet. The test cases for
> > dependent lambda expressions need to be fleshed out more, but a few
> > temporary ones are included to demonstrate that they do work and that
> > the crash is fixed. Explicit object conversion functions work, but I
> > need to add fleshed out tests for them, explicit-obj-basic5.C has that
> > test.
> 
> > @@ -6586,6 +6586,17 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> args,
> > + / FIXME: I believe this will be bugged for xobj member functions,
> > + leaving this comment here to make sure we look into it
> > + at some point.
> > + Seeing this makes me want correspondence checking to be unified
> > + in one place though, not sure if this one needs to be different
> > + from other ones though.
> > + This function is only used here, but maybe we can use it in add
> > + method and move some of the logic out of there?
> 
> 
> fns_correspond absolutely needs updating to handle xob fns, and doing
> that by unifying it with add_method's calculation would be good.
> 
> > + Side note: CWG2586 might be relevant for this area in
> > + particular, perhaps we wait to see if it gets accepted first? */
> 
> 
> 2586 was accepted last year.
> 
> > @@ -12574,17 +12601,25 @@ cand_parms_match (z_candidate *c1, z_candidate c2)
> > fn1 = DECL_TEMPLATE_RESULT (t1);
> > fn2 = DECL_TEMPLATE_RESULT (t2);
> > }
> > + / The changes I made here might be stuff I was told not to worry about?
> > + I'm not really sure so I'm going to leave it in. */
> 
> 
> Good choice, this comment can go.
> 
> > tree parms1 = TYPE_ARG_TYPES (TREE_TYPE (fn1));
> > tree parms2 = TYPE_ARG_TYPES (TREE_TYPE (fn2));
> > if (DECL_FUNCTION_MEMBER_P (fn1)
> > && DECL_FUNCTION_MEMBER_P (fn2)
> > - && (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn1)
> > - != DECL_NONSTATIC_MEMBER_FUNCTION_P (fn2)))
> > + && (DECL_STATIC_FUNCTION_P (fn1)
> > + != DECL_STATIC_FUNCTION_P (fn2)))
> > {
> > /* Ignore 'this' when comparing the parameters of a static member
> > function with those of a non-static one. */
> > - parms1 = skip_artificial_parms_for (fn1, parms1);
> > - parms2 = skip_artificial_parms_for (fn2, parms2);
> > + auto skip_parms = [](tree fn, tree parms){
> > + if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
> > + return TREE_CHAIN (parms);
> > + else
> > + return skip_artificial_parms_for (fn, parms);
> > + };
> > + parms1 = skip_parms (fn1, parms1);
> > + parms2 = skip_parms (fn2, parms2);
> > }
> 
> 
> https://cplusplus.github.io/CWG/issues/2789.html fixes the handling of
> xobj fns here.

I have a minor concern here.
https://eel.is/c++draft/basic.scope.scope#3

Is it okay that CWG2789 has separate rules than the rules for
declarations? As far as I know there's nothing else that describes how
to handle the object parameters so it was my assumption that the rules
here are also used for that. I know at least one person has disagreed
with me about that so I'm not sure what is actually correct.

template <typename T = int>
struct S {
  constexpr void f() {}                       // #f1
  constexpr void f(this S&&) requires true {} // #f2

  constexpr void g() requires true {}         // #g1
  constexpr void g(this S&&) {}               // #g2
};

void test() {
  S<>{}.f();             // calls #?
  S<>{}.g();             // calls #?
}

But with the wording proposed by CWG2789, wouldn't this example would
be a problem? If we follow the standard to the letter, constraints
can't be applied here right?

I wouldn't be surprised if I'm missing something but I figured I ought
to raise it just in case. Obviously it should call #f2 and #g1 but I'm
pretty sure the current wording only allows these calls to be ambiguous.

> Your change does the right thing for comparing static and xobj, but
> doesn't handle comparing iobj and xobj; I think we want to share
> parameter comparison code with fns_correspond/add_method. Maybe
> parms_correspond?

Yeah, I'll see what I can put together. The only issue being what I
noted above, I'm not sure the rules are actually the same. I think they
should be, but my reading of things seems like it's not right now.

For the time being I'm going to assume things should work the way I
want them to, because I don't think the example I presented above
should be ambiguous.

> > @@ -8727,21 +8882,42 @@ resolve_address_of_overloaded_function (tree target_type,
> > /* Good, exactly one match. Now, convert it to the correct type. */
> > fn = TREE_PURPOSE (matches);
> > 
> > - if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
> > - && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
> > + if (DECL_OBJECT_MEMBER_FUNCTION_P (fn)
> > + && !(complain & tf_ptrmem_ok))
> > {
> > - static int explained;
> > -
> > - if (!(complain & tf_error))
> > + /* For iobj member functions, if if -fms_extensions was passed in, this
> > + is not an error, so we do nothing. It is still an error regardless
> > + for xobj member functions though, as it is a new feature we
> > + (hopefully) don't need to support the behavior. */
> 
> 
> Unfortunately, it seems that MSVC extended their weirdness to xobj fns,
> so -fms-extensions should as well.
> https://godbolt.org/z/nfvn64Kx5

Ugh, what a shame. We don't have to support it though do we? The
documentation for -fms-extensions seems to state that it's for
microsoft headers, if they aren't using xobj parameters in their
headers as of now, and I can't see them doing so for the near future,
it should be fine to leave this out still shouldn't it?

The code will be simpler if we can't though so I reckon it's win/win
whichever way we choose.

> > + /* I'm keeping it more basic for now. */
> 
> 
> OK, this comment can go.

Yeah, if I ever get comfortable with fixit's I'll do a loop through my
changes and see where I can enhance the ones already present and add
the ones that I wanted.

> > @@ -15502,9 +15627,10 @@ void
> > grok_special_member_properties (tree decl)
> > {
> > tree class_type;
> > -
> > + /* I believe we have to make some changes in here depending on the outcome
> > + of CWG2586. */
> 
> 
> As mentioned above, CWG2586 is resolved. Be sure to scroll down to the
> approved resolution, or refer to the working draft.
> https://cplusplus.github.io/CWG/issues/2586.html

Yeah I got a bit of a lecture on how to read these in IRC, I shouldn't
be making this mistake again. :'D

I'll look at supporting it, I imagine it shouldn't be too hard but I've
been wrong before.

> > @@ -11754,8 +11754,16 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tre
> > else if (cxx_dialect < cxx23)
> > omitted_parms_loc = cp_lexer_peek_token (parser->lexer)->location;
> > 
> > + /* Review note: I figured I might as well update the comments since I'm here.
> > + There are also some additions to the below. */
> 
> 
> Great, this comment can go.

Just noting, I'm also removing the old comment immediately following
this one as it is obsolete.

> > + /* [expr.prim.lambda.general-4]
> > + If the lambda-declarator contains an explicit object parameter
> > + ([dcl.fct]), then no lambda-specifier in the lambda-specifier-seq
> > + shall be mutable or static. /
> > + if (lambda_specs.storage_class == sc_mutable)
> > + {
> > + auto_diagnostic_group d;
> > + error_at (lambda_specs.locations[ds_storage_class],
> > + "%<mutable%> lambda specifier "
> > + "with explicit object parameter");
> > + / Tell the user how to do what they probably meant, maybe fixits
> > + would be apropriate later? */
> 
> 
> "appropriate"
> 
> > + if (!dependent_type_p (non_reference (param_type)))
> > + /* If we are given a non-dependent type we will have already given
> > + a diagnosis that the following would contradict with. */;
> 
> 
> Only if the lambda has captures, though?

Oops, yeah I agree.

> We could also change dependent_type_p to the more specific
> WILDCARD_TYPE_P, I think, both here and just above.

I don't understand the significance of this but I'll trust there is
one. Better to be consistent with your other changes anyway.

> > + else if (!TYPE_REF_P (param_type))
> > + inform (DECL_SOURCE_LOCATION (xobj_param),
> > + "the passed in closure object will not be mutated because "
> > + "it is taken by copy/move");
> 
> 
> "by value"
> 
> > @@ -3092,7 +3093,31 @@ finish_this_expr (void)
> > return rvalue (result);
> > 
> > tree fn = current_nonlambda_function ();
> > - if (fn && DECL_STATIC_FUNCTION_P (fn))
> > + if (fn && DECL_XOBJ_MEMBER_FUNCTION_P (fn))
> > + {
> > + auto_diagnostic_group d;
> > + error ("%<this%> is unavailable for explicit object member "
> > + "functions");
> > + /* Doing a fixit here is possible, but hard, might be worthwhile
> > + in the future. /
> > + tree xobj_parm = DECL_ARGUMENTS (fn);
> > + gcc_assert (xobj_parm);
> > + tree parm_name = DECL_NAME (xobj_parm);
> > + if (parm_name)
> > + inform (DECL_SOURCE_LOCATION (xobj_parm),
> > + "use explicit object parameter %qD instead",
> > + parm_name);
> > + else
> > + {
> > + gcc_rich_location xobj_loc (DECL_SOURCE_LOCATION (xobj_parm));
> > + / This doesn't work and I don't know why. I'll probably remove it
> > + before the final version. */
> > + xobj_loc.add_fixit_insert_after (" self");
> > + inform (DECL_SOURCE_LOCATION (xobj_parm),
> > + "name and use the explicit object parameter instead");
> 
> 
> Now seems to be the time to either fix or remove it.

I think I'll defer it to future work, I'll pull it from the next
version. Fixits are arcane, I'll have to do a deep dive on them to be
able to use them properly I think. I'll also get in touch with David
Malcolm as you suggested and see what I can learn about them from him.

> > @@ -12811,9 +12836,11 @@ capture_decltype (tree decl)
> > if (WILDCARD_TYPE_P (non_reference (obtype)))
> > /* We don't know what the eventual obtype quals will be. /
> > return NULL_TREE;
> > - int quals = cp_type_quals (type);
> > - if (INDIRECT_TYPE_P (obtype))
> > - quals |= cp_type_quals (TREE_TYPE (obtype));
> > + / This change possibly conflicts with another patch that is currently
> > + in flight. (Patrick Palka's PR83167 patch) I am just changing it for
> > + now to satisfy my tests. */
> > + int const quals = cp_type_quals (type)
> > + | cp_type_quals (TREE_TYPE (obtype));
> 
> 
> Doesn't TREE_TYPE (obtype) break for by-value xob? In that case I think
> we'd want cp_type_quals (obtype).

I feel like you're right, and I'm disappointed that I missed this case
in my tests. Indeed, I've checked and it ICEs for by-value xobj
parameters. I'll fix this and add the test case.

> > + The name "nonstatic" is no longer accurate with the addition of
> > + xobj member functions, should we change the name? */
> > 
> > bool
> > invalid_nonstatic_memfn_p (location_t loc, tree expr, tsubst_flags_t complain)
> 
> 
> This rule still applies to all non-static member functions:
> https://eel.is/c++draft/expr.ref#6.3.2

Alright I see where my mistake is now, the comment regarding .* and ->*
threw me for a loop. Even so, I figured since we renamed
DECL_NONSTATIC_MEMBER_FUNCTION_P we might be better off renaming other
things too. So in this case, invalid_object_memfn_p instead of
invalid_nonstatic_memfn_p.

Obviously changing the errors and warnings is a different beast and
probably should be left alone.

> > @@ -2352,7 +2355,7 @@ invalid_nonstatic_memfn_p (location_t loc, tree expr, tsubst_flags_t complain)
> > if (is_overloaded_fn (expr) && !really_overloaded_fn (expr))
> > expr = get_first_fn (expr);
> > if (TREE_TYPE (expr)
> > - && DECL_NONSTATIC_MEMBER_FUNCTION_P (expr))
> > + && DECL_IOBJ_MEMBER_FUNCTION_P (expr))
> 
> 
> ...and so I think this should be OBJECT.
> https://godbolt.org/z/r6v4e1ePP

Yeah, I agree, definitely a mistake on my part.

Since we are here, following the same logic I presented above, can we
refuse to support -fms-extensions for xobj member functions? Until
microsoft is using xobj member functions in their headers we shouldn't
need to support it right?

Supporting their non-conformance going forward doesn't make sense to
me. Especially since the non-conformance with xobj member functions is
likely just because it was already this way. Until we know for sure
that they rely on this behavior for xobj member functions I think it
should remain off, it's a simple change to add it back in after all.

> Here's a patch to adjust all the remaining
> DECL_NONSTATIC_MEMBER_FUNCTION_P. With this patch -diagnostic7.C gets
> the old address of non-static diagnostic, I think correctly, so I'm not
> sure we still need the BASELINK change in cp_build_addr_expr_1?
> 
> Jason

Thanks, I'll evaluate the BASELINK change to see if you're right.
Hopefully you are, that would simplify things.

Is there anything special I need to do when adding this patch? I was
worried I'm supposed to maintain it's origin in it's own commit or
something. Well, with that said it's probably still something I should
learn to do anyway, I'm just trying really hard to put it off until the
patch is done.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-10 15:22                                                                                                                                     ` waffl3x
@ 2023-12-10 18:59                                                                                                                                       ` Jason Merrill
  2023-12-22  9:01                                                                                                                                         ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-12-10 18:59 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 12/10/23 10:22, waffl3x wrote:
> On Friday, December 8th, 2023 at 12:25 PM, Jason Merrill <jason@redhat.com> wrote:
>>
>>
>> On 12/6/23 02:33, waffl3x wrote:
>>
>>> Here is the next version, it feels very close to finished. As before, I
>>> haven't ran a bootstrap or the full testsuite yet but I did run the
>>> explicit-obj tests which completed as expected.
>>>
>>> There's a few test cases that still need to be written but more tests
>>> can always be added. The behavior added by CWG2789 works in at least
>>> one case, but I have not added tests for it yet. The test cases for
>>> dependent lambda expressions need to be fleshed out more, but a few
>>> temporary ones are included to demonstrate that they do work and that
>>> the crash is fixed. Explicit object conversion functions work, but I
>>> need to add fleshed out tests for them, explicit-obj-basic5.C has that
>>> test.
>>
>>> @@ -6586,6 +6586,17 @@ add_candidates (tree fns, tree first_arg, const vec<tree, va_gc> args,
>>> + / FIXME: I believe this will be bugged for xobj member functions,
>>> + leaving this comment here to make sure we look into it
>>> + at some point.
>>> + Seeing this makes me want correspondence checking to be unified
>>> + in one place though, not sure if this one needs to be different
>>> + from other ones though.
>>> + This function is only used here, but maybe we can use it in add
>>> + method and move some of the logic out of there?
>>
>>
>> fns_correspond absolutely needs updating to handle xob fns, and doing
>> that by unifying it with add_method's calculation would be good.
>>
>>> + Side note: CWG2586 might be relevant for this area in
>>> + particular, perhaps we wait to see if it gets accepted first? */
>>
>>
>> 2586 was accepted last year.
>>
>>> @@ -12574,17 +12601,25 @@ cand_parms_match (z_candidate *c1, z_candidate c2)
>>> fn1 = DECL_TEMPLATE_RESULT (t1);
>>> fn2 = DECL_TEMPLATE_RESULT (t2);
>>> }
>>> + / The changes I made here might be stuff I was told not to worry about?
>>> + I'm not really sure so I'm going to leave it in. */
>>
>>
>> Good choice, this comment can go.
>>
>>> tree parms1 = TYPE_ARG_TYPES (TREE_TYPE (fn1));
>>> tree parms2 = TYPE_ARG_TYPES (TREE_TYPE (fn2));
>>> if (DECL_FUNCTION_MEMBER_P (fn1)
>>> && DECL_FUNCTION_MEMBER_P (fn2)
>>> - && (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn1)
>>> - != DECL_NONSTATIC_MEMBER_FUNCTION_P (fn2)))
>>> + && (DECL_STATIC_FUNCTION_P (fn1)
>>> + != DECL_STATIC_FUNCTION_P (fn2)))
>>> {
>>> /* Ignore 'this' when comparing the parameters of a static member
>>> function with those of a non-static one. */
>>> - parms1 = skip_artificial_parms_for (fn1, parms1);
>>> - parms2 = skip_artificial_parms_for (fn2, parms2);
>>> + auto skip_parms = [](tree fn, tree parms){
>>> + if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
>>> + return TREE_CHAIN (parms);
>>> + else
>>> + return skip_artificial_parms_for (fn, parms);
>>> + };
>>> + parms1 = skip_parms (fn1, parms1);
>>> + parms2 = skip_parms (fn2, parms2);
>>> }
>>
>>
>> https://cplusplus.github.io/CWG/issues/2789.html fixes the handling of
>> xobj fns here.
> 
> I have a minor concern here.
> https://eel.is/c++draft/basic.scope.scope#3
> 
> Is it okay that CWG2789 has separate rules than the rules for
> declarations? As far as I know there's nothing else that describes how
> to handle the object parameters so it was my assumption that the rules
> here are also used for that. I know at least one person has disagreed
> with me about that so I'm not sure what is actually correct.
> 
> template <typename T = int>
> struct S {
>    constexpr void f() {}                       // #f1
>    constexpr void f(this S&&) requires true {} // #f2
> 
>    constexpr void g() requires true {}         // #g1
>    constexpr void g(this S&&) {}               // #g2
> };
> 
> void test() {
>    S<>{}.f();             // calls #?
>    S<>{}.g();             // calls #?
> }
> 
> But with the wording proposed by CWG2789, wouldn't this example would
> be a problem? If we follow the standard to the letter, constraints
> can't be applied here right?
> 
> I wouldn't be surprised if I'm missing something but I figured I ought
> to raise it just in case. Obviously it should call #f2 and #g1 but I'm
> pretty sure the current wording only allows these calls to be ambiguous.

I agree, I've suggested amending 2789 to use "corresponding object 
parameter".

>>> @@ -8727,21 +8882,42 @@ resolve_address_of_overloaded_function (tree target_type,
>>> /* Good, exactly one match. Now, convert it to the correct type. */
>>> fn = TREE_PURPOSE (matches);
>>>
>>> - if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fn)
>>> - && !(complain & tf_ptrmem_ok) && !flag_ms_extensions)
>>> + if (DECL_OBJECT_MEMBER_FUNCTION_P (fn)
>>> + && !(complain & tf_ptrmem_ok))
>>> {
>>> - static int explained;
>>> -
>>> - if (!(complain & tf_error))
>>> + /* For iobj member functions, if if -fms_extensions was passed in, this
>>> + is not an error, so we do nothing. It is still an error regardless
>>> + for xobj member functions though, as it is a new feature we
>>> + (hopefully) don't need to support the behavior. */
>>
>>
>> Unfortunately, it seems that MSVC extended their weirdness to xobj fns,
>> so -fms-extensions should as well.
>> https://godbolt.org/z/nfvn64Kx5
> 
> Ugh, what a shame. We don't have to support it though do we? The
> documentation for -fms-extensions seems to state that it's for
> microsoft headers, if they aren't using xobj parameters in their
> headers as of now, and I can't see them doing so for the near future,
> it should be fine to leave this out still shouldn't it?
> 
> The code will be simpler if we can't though so I reckon it's win/win
> whichever way we choose.

I think let's go with the simpler code, it doesn't seem particularly 
useful to split hairs on this obscure option.

>> We could also change dependent_type_p to the more specific
>> WILDCARD_TYPE_P, I think, both here and just above.
> 
> I don't understand the significance of this but I'll trust there is
> one. Better to be consistent with your other changes anyway.

FYI a "wildcard" type is e.g. a template type parm, typename, or 
decltype: types that could be replaced by a type of any form (and 
qualification), unlike e.g. A<T> which is also dependent, but won't turn 
const at instantiation time (if A is a class template).

>>> + The name "nonstatic" is no longer accurate with the addition of
>>> + xobj member functions, should we change the name? */
>>>
>>> bool
>>> invalid_nonstatic_memfn_p (location_t loc, tree expr, tsubst_flags_t complain)
>>
>>
>> This rule still applies to all non-static member functions:
>> https://eel.is/c++draft/expr.ref#6.3.2
> 
> Alright I see where my mistake is now, the comment regarding .* and ->*
> threw me for a loop. Even so, I figured since we renamed
> DECL_NONSTATIC_MEMBER_FUNCTION_P we might be better off renaming other
> things too. So in this case, invalid_object_memfn_p instead of
> invalid_nonstatic_memfn_p.
> 
> Obviously changing the errors and warnings is a different beast and
> probably should be left alone.

I wouldn't bother renaming other things unnecessarily, it just seemed 
important to rename the predicate because it was being used for what are 
now two different meanings.

>> Here's a patch to adjust all the remaining
>> DECL_NONSTATIC_MEMBER_FUNCTION_P. With this patch -diagnostic7.C gets
>> the old address of non-static diagnostic, I think correctly, so I'm not
>> sure we still need the BASELINK change in cp_build_addr_expr_1?
> 
> Thanks, I'll evaluate the BASELINK change to see if you're right.
> Hopefully you are, that would simplify things.
> 
> Is there anything special I need to do when adding this patch? I was
> worried I'm supposed to maintain it's origin in it's own commit or
> something. Well, with that said it's probably still something I should
> learn to do anyway, I'm just trying really hard to put it off until the
> patch is done.

It's OK to integrate it into your patch if that's easier for you.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-10 18:59                                                                                                                                       ` Jason Merrill
@ 2023-12-22  9:01                                                                                                                                         ` waffl3x
  2023-12-22 17:26                                                                                                                                           ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-12-22  9:01 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches


  int n = 0;
  auto f = []<typename Self>(this Self){
    static_assert(__is_same (decltype(n), int));
    decltype((n)) a; // { dg-error {is not captured} }
  };
  f();

Could you clarify if this error being removed was intentional. I do
recall that Patrick Palka wanted to remove this error in his patch, but
it seemed to me like you stated it would be incorrect to allow it.
Since the error is no longer present I assume I am misunderstanding the
exchange.

In any case, let me know if I need to modify my test case or if this
error needs to be added back in.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-22  9:01                                                                                                                                         ` waffl3x
@ 2023-12-22 17:26                                                                                                                                           ` Jason Merrill
  2023-12-23  7:10                                                                                                                                             ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-12-22 17:26 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 12/22/23 04:01, waffl3x wrote:
> 
>    int n = 0;
>    auto f = []<typename Self>(this Self){
>      static_assert(__is_same (decltype(n), int));
>      decltype((n)) a; // { dg-error {is not captured} }
>    };
>    f();
> 
> Could you clarify if this error being removed was intentional. I do
> recall that Patrick Palka wanted to remove this error in his patch, but
> it seemed to me like you stated it would be incorrect to allow it.
> Since the error is no longer present I assume I am misunderstanding the
> exchange.
> 
> In any case, let me know if I need to modify my test case or if this
> error needs to be added back in.

Removing the error was correct under
https://eel.is/c++draft/expr.prim#id.unqual-3
Naming n in that lambda would not refer a capture by copy, so the 
decltype is the same as outside the lambda.

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-22 17:26                                                                                                                                           ` Jason Merrill
@ 2023-12-23  7:10                                                                                                                                             ` waffl3x
  2023-12-26 16:37                                                                                                                                               ` Jason Merrill
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2023-12-23  7:10 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

On Friday, December 22nd, 2023 at 10:26 AM, Jason Merrill <jason@redhat.com> wrote:
> 
> 
> On 12/22/23 04:01, waffl3x wrote:
> 
> > int n = 0;
> > auto f = []<typename Self>(this Self){
> > static_assert(__is_same (decltype(n), int));
> > decltype((n)) a; // { dg-error {is not captured} }
> > };
> > f();
> > 
> > Could you clarify if this error being removed was intentional. I do
> > recall that Patrick Palka wanted to remove this error in his patch, but
> > it seemed to me like you stated it would be incorrect to allow it.
> > Since the error is no longer present I assume I am misunderstanding the
> > exchange.
> > 
> > In any case, let me know if I need to modify my test case or if this
> > error needs to be added back in.
> 
> 
> Removing the error was correct under
> https://eel.is/c++draft/expr.prim#id.unqual-3
> Naming n in that lambda would not refer a capture by copy, so the
> decltype is the same as outside the lambda.
> 
> Jason

Alright, I've fixed my tests to reflect that.

I've got defaulting assignment operators working. Defaulting equality
and comparison operators seemed to work out of the box somehow, so I
just have to make some fleshed out tests for those cases.

There can always be more tests, I have a few ideas for what still needs
to be covered, mostly with dependent lambdas. Tests for xobj conversion
operators definitely need to be more fleshed out. I also need to
formulate some tests to make sure constraints are not being taking into
account when the object parameters should not correspond, but that's a
little more tough to test for than the valid cases.

Other than tests though, is there anything you can think of that the
patch is missing? Other than the aforementioned tests, I'm pretty
confident everything is done.

To recap, I have CWG2789 implemented on my end with the change we
discussed to require corresponding object parameters instead of the
same type, and I have CWG2586 implemented. I can't recall what other
outstanding issues we had, and my notes don't mention anything other
than tests. So I'm assuming everything is good.

Alex

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-23  7:10                                                                                                                                             ` waffl3x
@ 2023-12-26 16:37                                                                                                                                               ` Jason Merrill
  2024-01-01 15:17                                                                                                                                                 ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: Jason Merrill @ 2023-12-26 16:37 UTC (permalink / raw)
  To: waffl3x; +Cc: gcc-patches

On 12/23/23 02:10, waffl3x wrote:
> On Friday, December 22nd, 2023 at 10:26 AM, Jason Merrill <jason@redhat.com> wrote:
>>
>>
>> On 12/22/23 04:01, waffl3x wrote:
>>
>>> int n = 0;
>>> auto f = []<typename Self>(this Self){
>>> static_assert(__is_same (decltype(n), int));
>>> decltype((n)) a; // { dg-error {is not captured} }
>>> };
>>> f();
>>>
>>> Could you clarify if this error being removed was intentional. I do
>>> recall that Patrick Palka wanted to remove this error in his patch, but
>>> it seemed to me like you stated it would be incorrect to allow it.
>>> Since the error is no longer present I assume I am misunderstanding the
>>> exchange.
>>>
>>> In any case, let me know if I need to modify my test case or if this
>>> error needs to be added back in.
>>
>>
>> Removing the error was correct under
>> https://eel.is/c++draft/expr.prim#id.unqual-3
>> Naming n in that lambda would not refer a capture by copy, so the
>> decltype is the same as outside the lambda.
> 
> Alright, I've fixed my tests to reflect that.
> 
> I've got defaulting assignment operators working. Defaulting equality
> and comparison operators seemed to work out of the box somehow, so I
> just have to make some fleshed out tests for those cases.
> 
> There can always be more tests, I have a few ideas for what still needs
> to be covered, mostly with dependent lambdas. Tests for xobj conversion
> operators definitely need to be more fleshed out. I also need to
> formulate some tests to make sure constraints are not being taking into
> account when the object parameters should not correspond, but that's a
> little more tough to test for than the valid cases.
> 
> Other than tests though, is there anything you can think of that the
> patch is missing? Other than the aforementioned tests, I'm pretty
> confident everything is done.
> 
> To recap, I have CWG2789 implemented on my end with the change we
> discussed to require corresponding object parameters instead of the
> same type, and I have CWG2586 implemented. I can't recall what other
> outstanding issues we had, and my notes don't mention anything other
> than tests. So I'm assuming everything is good.

Sounds good!  Did you mean to include the updated patch?

Jason


^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2023-12-26 16:37                                                                                                                                               ` Jason Merrill
@ 2024-01-01 15:17                                                                                                                                                 ` waffl3x
  2024-01-01 15:34                                                                                                                                                   ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2024-01-01 15:17 UTC (permalink / raw)
  To: Jason Merrill; +Cc: gcc-patches

I've been at this for a while, and I'm not sure what the proper way to
fix this is.

```
struct S;

struct B {
  void f(this S&) {}
  void g() {}
};

struct S : B {
  using B::f;
  using B::g;
  void f() {}
  void g(this S&) {}
};

int main()
{
  S s{};
  s.f();
  s.g();
}
```

In short, the call to f is ambiguous, but the call to g is not. I
already know where the problem is, but since I share this code in
places that don't know about whether a function was introduced by a
using declaration (cand_parms_match), I don't want to rely on that to
solve the problem.

```
  /* An iobj member function's object parameter can't be an unrelated type, if
     the xobj member function's object parameter is an unrelated type we know
     they must not correspond to each other.  If the iobj member function was
     introduced with a using declaration, then the type of its object parameter
     is still that of the class we are currently adding a member function to,
     so this assumption holds true in that case as well.

     [over.match.funcs.general.4]
     For non-conversion functions that are implicit object member
     functions nominated by a using-declaration in a derived class, the
     function is considered to be a member of the derived class for the purpose
     of defining the type of the implicit object parameter.

     We don't get to bail yet out even if the xobj parameter is by-value as
     elaborated on below.

     This also implicitly handles xobj parameters of type pointer.  */
  if (DECL_CONTEXT (xobj_fn) != TYPE_MAIN_VARIANT (non_reference (xobj_param)))
    return false;
```

I feel like what we are actually supposed to be doing to be to the
letter of the standard is to be creating a new function entirely, with
a decl_context of the original class, which sounds omega silly, and
might bring a new set of problems.

I think I might have came up with an unfortunately fairly convoluted
way to solve this just now, but I don't know if it brings another new
set of problems. The assumptions I had when I originally implemented
this in add_method bled through when I broke it out into it's own
function. At the very least I need to better document how the function
is intended to be used, at worst I'll need to consider whether it makes
sense to be reusing this logic if the use cases are subtly different.

I don't think the latter is the case now though, I'm noticing GCC just
has a bug in general with constraints and using declarations.

https://godbolt.org/z/EbGvjfG7E

So it might actually just be better to be rewriting functions that are
introduced by using declarations, I have a feeling that will be what
introduces the least pain.

I'm not sure where exactly GCC is deciding that a function introduced
by a using declaration is different from an otherwise corresponding one
declared directly in that class, but I have a feeling on where it is.
Obviously it's in joust, but I'm not sure the object parameters are
actually being compared.

I'll investigate this bug and get back to you, I imagine fixing it is
going to be key to actually implementing the xobj case without hacks.

Finding both these issues has slowed down my next revision as I noticed
the problem while cleaning up my implementation of CWG2789. I want to
note, I am implementing it as if it specifies corresponding object
arguments, not object arguments of the same type, as we previously
discussed, I believe that to be the right resolution as there are
really bad edge cases with the current wording.

Alex

On Tuesday, December 26th, 2023 at 9:37 AM, Jason Merrill <jason@redhat.com> wrote:


> 
> 
> On 12/23/23 02:10, waffl3x wrote:
> 
> > On Friday, December 22nd, 2023 at 10:26 AM, Jason Merrill jason@redhat.com wrote:
> > 
> > > On 12/22/23 04:01, waffl3x wrote:
> > > 
> > > > int n = 0;
> > > > auto f = []<typename Self>(this Self){
> > > > static_assert(__is_same (decltype(n), int));
> > > > decltype((n)) a; // { dg-error {is not captured} }
> > > > };
> > > > f();
> > > > 
> > > > Could you clarify if this error being removed was intentional. I do
> > > > recall that Patrick Palka wanted to remove this error in his patch, but
> > > > it seemed to me like you stated it would be incorrect to allow it.
> > > > Since the error is no longer present I assume I am misunderstanding the
> > > > exchange.
> > > > 
> > > > In any case, let me know if I need to modify my test case or if this
> > > > error needs to be added back in.
> > > 
> > > Removing the error was correct under
> > > https://eel.is/c++draft/expr.prim#id.unqual-3
> > > Naming n in that lambda would not refer a capture by copy, so the
> > > decltype is the same as outside the lambda.
> > 
> > Alright, I've fixed my tests to reflect that.
> > 
> > I've got defaulting assignment operators working. Defaulting equality
> > and comparison operators seemed to work out of the box somehow, so I
> > just have to make some fleshed out tests for those cases.
> > 
> > There can always be more tests, I have a few ideas for what still needs
> > to be covered, mostly with dependent lambdas. Tests for xobj conversion
> > operators definitely need to be more fleshed out. I also need to
> > formulate some tests to make sure constraints are not being taking into
> > account when the object parameters should not correspond, but that's a
> > little more tough to test for than the valid cases.
> > 
> > Other than tests though, is there anything you can think of that the
> > patch is missing? Other than the aforementioned tests, I'm pretty
> > confident everything is done.
> > 
> > To recap, I have CWG2789 implemented on my end with the change we
> > discussed to require corresponding object parameters instead of the
> > same type, and I have CWG2586 implemented. I can't recall what other
> > outstanding issues we had, and my notes don't mention anything other
> > than tests. So I'm assuming everything is good.
> 
> 
> Sounds good! Did you mean to include the updated patch?
> 
> Jason

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2024-01-01 15:17                                                                                                                                                 ` waffl3x
@ 2024-01-01 15:34                                                                                                                                                   ` waffl3x
  2024-01-01 17:12                                                                                                                                                     ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2024-01-01 15:34 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

That was faster than I expected, the problem is exactly just that we
aren't implementing [over.match.funcs.general.4] at all. The result of
compparms for the 2 functions is false which I believe to be wrong. I
believe we have a few choices here, but no matter what we go with it
will be a bit of an overhaul. I will post a PR on bugzilla in a little
bit as this problem feels somewhat out of the scope of my patch now.

I think what I will do is instead of comparing the xobj parameter to
the DECL_CONTEXT of the xobj function, I will compare it to the type of
the iobj member function's object parameter. If I do it like this, it
will work as expected if we rewrite functions that are introduced with
a using declaration.

This might still cause problems, I will look into how the this pointer
for iobj member functions is determined again. Depending on how it is
determined, it might be possible to change the function signature of
iobj member functions without altering their behavior. It would be
incorrect, and would change the meaning of code, if changing the
function signature changed the type of the this pointer.

Anyhow, this is a fairly big change to consider so I won't pretend I
know what the right call is. But the way I've decided to implement
correspondence checking will be consistent with how GCC currently
(incorrectly) treats constraints on iobj member functions introduced
with a using declaration, so I think doing it this way is the right
choice for now.

Some days feel really unproductive when the majority is investigation
and thinking. This was one of them, but at least I'm confident that my
conclusions are correct. Aren't edge cases fun?

Alex

On Monday, January 1st, 2024 at 8:17 AM, waffl3x <waffl3x@protonmail.com> wrote:


> 
> 
> I've been at this for a while, and I'm not sure what the proper way to
> fix this is.
> 
> `struct S; struct B { void f(this S&) {} void g() {} }; struct S : B { using B::f; using B::g; void f() {} void g(this S&) {} }; int main() { S s{}; s.f(); s.g(); }`
> 
> In short, the call to f is ambiguous, but the call to g is not. I
> already know where the problem is, but since I share this code in
> places that don't know about whether a function was introduced by a
> using declaration (cand_parms_match), I don't want to rely on that to
> solve the problem.
> 
> `/* An iobj member function's object parameter can't be an unrelated type, if the xobj member function's object parameter is an unrelated type we know they must not correspond to each other. If the iobj member function was introduced with a using declaration, then the type of its object parameter is still that of the class we are currently adding a member function to, so this assumption holds true in that case as well. [over.match.funcs.general.4] For non-conversion functions that are implicit object member functions nominated by a using-declaration in a derived class, the function is considered to be a member of the derived class for the purpose of defining the type of the implicit object parameter. We don't get to bail yet out even if the xobj parameter is by-value as elaborated on below. This also implicitly handles xobj parameters of type pointer. */ if (DECL_CONTEXT (xobj_fn) != TYPE_MAIN_VARIANT (non_reference (xobj_param))) return false;`
> 
> I feel like what we are actually supposed to be doing to be to the
> letter of the standard is to be creating a new function entirely, with
> a decl_context of the original class, which sounds omega silly, and
> might bring a new set of problems.
> 
> I think I might have came up with an unfortunately fairly convoluted
> way to solve this just now, but I don't know if it brings another new
> set of problems. The assumptions I had when I originally implemented
> this in add_method bled through when I broke it out into it's own
> function. At the very least I need to better document how the function
> is intended to be used, at worst I'll need to consider whether it makes
> sense to be reusing this logic if the use cases are subtly different.
> 
> I don't think the latter is the case now though, I'm noticing GCC just
> has a bug in general with constraints and using declarations.
> 
> https://godbolt.org/z/EbGvjfG7E
> 
> So it might actually just be better to be rewriting functions that are
> introduced by using declarations, I have a feeling that will be what
> introduces the least pain.
> 
> I'm not sure where exactly GCC is deciding that a function introduced
> by a using declaration is different from an otherwise corresponding one
> declared directly in that class, but I have a feeling on where it is.
> Obviously it's in joust, but I'm not sure the object parameters are
> actually being compared.
> 
> I'll investigate this bug and get back to you, I imagine fixing it is
> going to be key to actually implementing the xobj case without hacks.
> 
> Finding both these issues has slowed down my next revision as I noticed
> the problem while cleaning up my implementation of CWG2789. I want to
> note, I am implementing it as if it specifies corresponding object
> arguments, not object arguments of the same type, as we previously
> discussed, I believe that to be the right resolution as there are
> really bad edge cases with the current wording.
> 
> Alex
> 
> On Tuesday, December 26th, 2023 at 9:37 AM, Jason Merrill jason@redhat.com wrote:
> 
> 
> 
> > On 12/23/23 02:10, waffl3x wrote:
> > 
> > > On Friday, December 22nd, 2023 at 10:26 AM, Jason Merrill jason@redhat.com wrote:
> > > 
> > > > On 12/22/23 04:01, waffl3x wrote:
> > > > 
> > > > > int n = 0;
> > > > > auto f = []<typename Self>(this Self){
> > > > > static_assert(__is_same (decltype(n), int));
> > > > > decltype((n)) a; // { dg-error {is not captured} }
> > > > > };
> > > > > f();
> > > > > 
> > > > > Could you clarify if this error being removed was intentional. I do
> > > > > recall that Patrick Palka wanted to remove this error in his patch, but
> > > > > it seemed to me like you stated it would be incorrect to allow it.
> > > > > Since the error is no longer present I assume I am misunderstanding the
> > > > > exchange.
> > > > > 
> > > > > In any case, let me know if I need to modify my test case or if this
> > > > > error needs to be added back in.
> > > > 
> > > > Removing the error was correct under
> > > > https://eel.is/c++draft/expr.prim#id.unqual-3
> > > > Naming n in that lambda would not refer a capture by copy, so the
> > > > decltype is the same as outside the lambda.
> > > 
> > > Alright, I've fixed my tests to reflect that.
> > > 
> > > I've got defaulting assignment operators working. Defaulting equality
> > > and comparison operators seemed to work out of the box somehow, so I
> > > just have to make some fleshed out tests for those cases.
> > > 
> > > There can always be more tests, I have a few ideas for what still needs
> > > to be covered, mostly with dependent lambdas. Tests for xobj conversion
> > > operators definitely need to be more fleshed out. I also need to
> > > formulate some tests to make sure constraints are not being taking into
> > > account when the object parameters should not correspond, but that's a
> > > little more tough to test for than the valid cases.
> > > 
> > > Other than tests though, is there anything you can think of that the
> > > patch is missing? Other than the aforementioned tests, I'm pretty
> > > confident everything is done.
> > > 
> > > To recap, I have CWG2789 implemented on my end with the change we
> > > discussed to require corresponding object parameters instead of the
> > > same type, and I have CWG2586 implemented. I can't recall what other
> > > outstanding issues we had, and my notes don't mention anything other
> > > than tests. So I'm assuming everything is good.
> > 
> > Sounds good! Did you mean to include the updated patch?
> > 
> > Jason

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2024-01-01 15:34                                                                                                                                                   ` waffl3x
@ 2024-01-01 17:12                                                                                                                                                     ` waffl3x
  2024-01-06 12:37                                                                                                                                                       ` waffl3x
  0 siblings, 1 reply; 100+ messages in thread
From: waffl3x @ 2024-01-01 17:12 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113191

I've posted the report here, I ended up doing a bit more investigation,
so the contents might interest you. I'm really starting to think that
we want to do a more thorough re-engineering of how using declarations
are handled, instead of handling it with little hacks like the one
added to more_specialized_fn.

As I note in the report, the addition of xobj member functions really
makes [over.match.funcs.general.4] a lot more relevant, and I don't
think we can get away with not following it more closely anymore. I
know I'm wording myself here as if that passage has existed forever,
but I recognize it might be as recent as C++23 that it was added. I
don't mean to imply anything with how I'm wording it, it's just way
easier to express it this way. Especially since we really could get
away with these kinds of hacks if xobj member functions did not exist.
Unfortunately, the type of the implicit object parameter is suddenly
relevant for cases like this.

Anyway, as I've stated a few times, I'm going to implement my function
that checks correspondence of the object parameter of iobj and xobj
member functions assuming that iobj member functions introduced by
using declarations are handled properly. I think that's the best option
for my patch right now.

Well that investigation took the majority of my day. I'm just glad I'm
certain of what direction to take now.

Alex


On Monday, January 1st, 2024 at 8:34 AM, waffl3x <waffl3x@protonmail.com> wrote:


> 
> 
> That was faster than I expected, the problem is exactly just that we
> aren't implementing [over.match.funcs.general.4] at all. The result of
> compparms for the 2 functions is false which I believe to be wrong. I
> believe we have a few choices here, but no matter what we go with it
> will be a bit of an overhaul. I will post a PR on bugzilla in a little
> bit as this problem feels somewhat out of the scope of my patch now.
> 
> I think what I will do is instead of comparing the xobj parameter to
> the DECL_CONTEXT of the xobj function, I will compare it to the type of
> the iobj member function's object parameter. If I do it like this, it
> will work as expected if we rewrite functions that are introduced with
> a using declaration.
> 
> This might still cause problems, I will look into how the this pointer
> for iobj member functions is determined again. Depending on how it is
> determined, it might be possible to change the function signature of
> iobj member functions without altering their behavior. It would be
> incorrect, and would change the meaning of code, if changing the
> function signature changed the type of the this pointer.
> 
> Anyhow, this is a fairly big change to consider so I won't pretend I
> know what the right call is. But the way I've decided to implement
> correspondence checking will be consistent with how GCC currently
> (incorrectly) treats constraints on iobj member functions introduced
> with a using declaration, so I think doing it this way is the right
> choice for now.
> 
> Some days feel really unproductive when the majority is investigation
> and thinking. This was one of them, but at least I'm confident that my
> conclusions are correct. Aren't edge cases fun?
> 
> Alex
> 
> On Monday, January 1st, 2024 at 8:17 AM, waffl3x waffl3x@protonmail.com wrote:
> 
> 
> 
> > I've been at this for a while, and I'm not sure what the proper way to
> > fix this is.
> > 
> > `struct S; struct B { void f(this S&) {} void g() {} }; struct S : B { using B::f; using B::g; void f() {} void g(this S&) {} }; int main() { S s{}; s.f(); s.g(); }`
> > 
> > In short, the call to f is ambiguous, but the call to g is not. I
> > already know where the problem is, but since I share this code in
> > places that don't know about whether a function was introduced by a
> > using declaration (cand_parms_match), I don't want to rely on that to
> > solve the problem.
> > 
> > `/* An iobj member function's object parameter can't be an unrelated type, if the xobj member function's object parameter is an unrelated type we know they must not correspond to each other. If the iobj member function was introduced with a using declaration, then the type of its object parameter is still that of the class we are currently adding a member function to, so this assumption holds true in that case as well. [over.match.funcs.general.4] For non-conversion functions that are implicit object member functions nominated by a using-declaration in a derived class, the function is considered to be a member of the derived class for the purpose of defining the type of the implicit object parameter. We don't get to bail yet out even if the xobj parameter is by-value as elaborated on below. This also implicitly handles xobj parameters of type pointer. */ if (DECL_CONTEXT (xobj_fn) != TYPE_MAIN_VARIANT (non_reference (xobj_param))) return false;`
> > 
> > I feel like what we are actually supposed to be doing to be to the
> > letter of the standard is to be creating a new function entirely, with
> > a decl_context of the original class, which sounds omega silly, and
> > might bring a new set of problems.
> > 
> > I think I might have came up with an unfortunately fairly convoluted
> > way to solve this just now, but I don't know if it brings another new
> > set of problems. The assumptions I had when I originally implemented
> > this in add_method bled through when I broke it out into it's own
> > function. At the very least I need to better document how the function
> > is intended to be used, at worst I'll need to consider whether it makes
> > sense to be reusing this logic if the use cases are subtly different.
> > 
> > I don't think the latter is the case now though, I'm noticing GCC just
> > has a bug in general with constraints and using declarations.
> > 
> > https://godbolt.org/z/EbGvjfG7E
> > 
> > So it might actually just be better to be rewriting functions that are
> > introduced by using declarations, I have a feeling that will be what
> > introduces the least pain.
> > 
> > I'm not sure where exactly GCC is deciding that a function introduced
> > by a using declaration is different from an otherwise corresponding one
> > declared directly in that class, but I have a feeling on where it is.
> > Obviously it's in joust, but I'm not sure the object parameters are
> > actually being compared.
> > 
> > I'll investigate this bug and get back to you, I imagine fixing it is
> > going to be key to actually implementing the xobj case without hacks.
> > 
> > Finding both these issues has slowed down my next revision as I noticed
> > the problem while cleaning up my implementation of CWG2789. I want to
> > note, I am implementing it as if it specifies corresponding object
> > arguments, not object arguments of the same type, as we previously
> > discussed, I believe that to be the right resolution as there are
> > really bad edge cases with the current wording.
> > 
> > Alex
> > 
> > On Tuesday, December 26th, 2023 at 9:37 AM, Jason Merrill jason@redhat.com wrote:
> > 
> > > On 12/23/23 02:10, waffl3x wrote:
> > > 
> > > > On Friday, December 22nd, 2023 at 10:26 AM, Jason Merrill jason@redhat.com wrote:
> > > > 
> > > > > On 12/22/23 04:01, waffl3x wrote:
> > > > > 
> > > > > > int n = 0;
> > > > > > auto f = []<typename Self>(this Self){
> > > > > > static_assert(__is_same (decltype(n), int));
> > > > > > decltype((n)) a; // { dg-error {is not captured} }
> > > > > > };
> > > > > > f();
> > > > > > 
> > > > > > Could you clarify if this error being removed was intentional. I do
> > > > > > recall that Patrick Palka wanted to remove this error in his patch, but
> > > > > > it seemed to me like you stated it would be incorrect to allow it.
> > > > > > Since the error is no longer present I assume I am misunderstanding the
> > > > > > exchange.
> > > > > > 
> > > > > > In any case, let me know if I need to modify my test case or if this
> > > > > > error needs to be added back in.
> > > > > 
> > > > > Removing the error was correct under
> > > > > https://eel.is/c++draft/expr.prim#id.unqual-3
> > > > > Naming n in that lambda would not refer a capture by copy, so the
> > > > > decltype is the same as outside the lambda.
> > > > 
> > > > Alright, I've fixed my tests to reflect that.
> > > > 
> > > > I've got defaulting assignment operators working. Defaulting equality
> > > > and comparison operators seemed to work out of the box somehow, so I
> > > > just have to make some fleshed out tests for those cases.
> > > > 
> > > > There can always be more tests, I have a few ideas for what still needs
> > > > to be covered, mostly with dependent lambdas. Tests for xobj conversion
> > > > operators definitely need to be more fleshed out. I also need to
> > > > formulate some tests to make sure constraints are not being taking into
> > > > account when the object parameters should not correspond, but that's a
> > > > little more tough to test for than the valid cases.
> > > > 
> > > > Other than tests though, is there anything you can think of that the
> > > > patch is missing? Other than the aforementioned tests, I'm pretty
> > > > confident everything is done.
> > > > 
> > > > To recap, I have CWG2789 implemented on my end with the change we
> > > > discussed to require corresponding object parameters instead of the
> > > > same type, and I have CWG2586 implemented. I can't recall what other
> > > > outstanding issues we had, and my notes don't mention anything other
> > > > than tests. So I'm assuming everything is good.
> > > 
> > > Sounds good! Did you mean to include the updated patch?
> > > 
> > > Jason

^ permalink raw reply	[flat|nested] 100+ messages in thread

* Re: [PATCH v7 1/1] c++: Initial support for P0847R7 (Deducing This) [PR102609]
  2024-01-01 17:12                                                                                                                                                     ` waffl3x
@ 2024-01-06 12:37                                                                                                                                                       ` waffl3x
  0 siblings, 0 replies; 100+ messages in thread
From: waffl3x @ 2024-01-06 12:37 UTC (permalink / raw)
  To: waffl3x; +Cc: Jason Merrill, gcc-patches

I found a bugged test while finishing off the patch.
gcc/testsuite/g++.dg/cpp0x/lambda/lambda-type.C
line 72 has
same_type<decltype((i)),int const&>(); // { dg-error "" "not captured" }
The behavior has changed now, and this test should have failed, but
didn't as it has an empty regex expression. The actual error being
emitted here is this:
error: invalid use of incomplete type 'struct same_type<int&, const int&>'

This still happens to pass because the types don't match, but it should
be fixed to reflect the new intended behavior.

The finished patch will be coming soon(ish), I had to make some changes
so you will have to look over it quickly but hopefully it will be split
up enough to make that convenient.

Alex


On Monday, January 1st, 2024 at 10:12 AM, waffl3x <waffl3x@protonmail.com> wrote:


> 
> 
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113191
> 
> I've posted the report here, I ended up doing a bit more investigation,
> so the contents might interest you. I'm really starting to think that
> we want to do a more thorough re-engineering of how using declarations
> are handled, instead of handling it with little hacks like the one
> added to more_specialized_fn.
> 
> As I note in the report, the addition of xobj member functions really
> makes [over.match.funcs.general.4] a lot more relevant, and I don't
> think we can get away with not following it more closely anymore. I
> know I'm wording myself here as if that passage has existed forever,
> but I recognize it might be as recent as C++23 that it was added. I
> don't mean to imply anything with how I'm wording it, it's just way
> easier to express it this way. Especially since we really could get
> away with these kinds of hacks if xobj member functions did not exist.
> Unfortunately, the type of the implicit object parameter is suddenly
> relevant for cases like this.
> 
> Anyway, as I've stated a few times, I'm going to implement my function
> that checks correspondence of the object parameter of iobj and xobj
> member functions assuming that iobj member functions introduced by
> using declarations are handled properly. I think that's the best option
> for my patch right now.
> 
> Well that investigation took the majority of my day. I'm just glad I'm
> certain of what direction to take now.
> 
> Alex
> 
> 
> On Monday, January 1st, 2024 at 8:34 AM, waffl3x waffl3x@protonmail.com wrote:
> 
> 
> 
> > That was faster than I expected, the problem is exactly just that we
> > aren't implementing [over.match.funcs.general.4] at all. The result of
> > compparms for the 2 functions is false which I believe to be wrong. I
> > believe we have a few choices here, but no matter what we go with it
> > will be a bit of an overhaul. I will post a PR on bugzilla in a little
> > bit as this problem feels somewhat out of the scope of my patch now.
> > 
> > I think what I will do is instead of comparing the xobj parameter to
> > the DECL_CONTEXT of the xobj function, I will compare it to the type of
> > the iobj member function's object parameter. If I do it like this, it
> > will work as expected if we rewrite functions that are introduced with
> > a using declaration.
> > 
> > This might still cause problems, I will look into how the this pointer
> > for iobj member functions is determined again. Depending on how it is
> > determined, it might be possible to change the function signature of
> > iobj member functions without altering their behavior. It would be
> > incorrect, and would change the meaning of code, if changing the
> > function signature changed the type of the this pointer.
> > 
> > Anyhow, this is a fairly big change to consider so I won't pretend I
> > know what the right call is. But the way I've decided to implement
> > correspondence checking will be consistent with how GCC currently
> > (incorrectly) treats constraints on iobj member functions introduced
> > with a using declaration, so I think doing it this way is the right
> > choice for now.
> > 
> > Some days feel really unproductive when the majority is investigation
> > and thinking. This was one of them, but at least I'm confident that my
> > conclusions are correct. Aren't edge cases fun?
> > 
> > Alex
> > 
> > On Monday, January 1st, 2024 at 8:17 AM, waffl3x waffl3x@protonmail.com wrote:
> > 
> > > I've been at this for a while, and I'm not sure what the proper way to
> > > fix this is.
> > > 
> > > `struct S; struct B { void f(this S&) {} void g() {} }; struct S : B { using B::f; using B::g; void f() {} void g(this S&) {} }; int main() { S s{}; s.f(); s.g(); }`
> > > 
> > > In short, the call to f is ambiguous, but the call to g is not. I
> > > already know where the problem is, but since I share this code in
> > > places that don't know about whether a function was introduced by a
> > > using declaration (cand_parms_match), I don't want to rely on that to
> > > solve the problem.
> > > 
> > > `/* An iobj member function's object parameter can't be an unrelated type, if the xobj member function's object parameter is an unrelated type we know they must not correspond to each other. If the iobj member function was introduced with a using declaration, then the type of its object parameter is still that of the class we are currently adding a member function to, so this assumption holds true in that case as well. [over.match.funcs.general.4] For non-conversion functions that are implicit object member functions nominated by a using-declaration in a derived class, the function is considered to be a member of the derived class for the purpose of defining the type of the implicit object parameter. We don't get to bail yet out even if the xobj parameter is by-value as elaborated on below. This also implicitly handles xobj parameters of type pointer. */ if (DECL_CONTEXT (xobj_fn) != TYPE_MAIN_VARIANT (non_reference (xobj_param))) return false;`
> > > 
> > > I feel like what we are actually supposed to be doing to be to the
> > > letter of the standard is to be creating a new function entirely, with
> > > a decl_context of the original class, which sounds omega silly, and
> > > might bring a new set of problems.
> > > 
> > > I think I might have came up with an unfortunately fairly convoluted
> > > way to solve this just now, but I don't know if it brings another new
> > > set of problems. The assumptions I had when I originally implemented
> > > this in add_method bled through when I broke it out into it's own
> > > function. At the very least I need to better document how the function
> > > is intended to be used, at worst I'll need to consider whether it makes
> > > sense to be reusing this logic if the use cases are subtly different.
> > > 
> > > I don't think the latter is the case now though, I'm noticing GCC just
> > > has a bug in general with constraints and using declarations.
> > > 
> > > https://godbolt.org/z/EbGvjfG7E
> > > 
> > > So it might actually just be better to be rewriting functions that are
> > > introduced by using declarations, I have a feeling that will be what
> > > introduces the least pain.
> > > 
> > > I'm not sure where exactly GCC is deciding that a function introduced
> > > by a using declaration is different from an otherwise corresponding one
> > > declared directly in that class, but I have a feeling on where it is.
> > > Obviously it's in joust, but I'm not sure the object parameters are
> > > actually being compared.
> > > 
> > > I'll investigate this bug and get back to you, I imagine fixing it is
> > > going to be key to actually implementing the xobj case without hacks.
> > > 
> > > Finding both these issues has slowed down my next revision as I noticed
> > > the problem while cleaning up my implementation of CWG2789. I want to
> > > note, I am implementing it as if it specifies corresponding object
> > > arguments, not object arguments of the same type, as we previously
> > > discussed, I believe that to be the right resolution as there are
> > > really bad edge cases with the current wording.
> > > 
> > > Alex
> > > 
> > > On Tuesday, December 26th, 2023 at 9:37 AM, Jason Merrill jason@redhat.com wrote:
> > > 
> > > > On 12/23/23 02:10, waffl3x wrote:
> > > > 
> > > > > On Friday, December 22nd, 2023 at 10:26 AM, Jason Merrill jason@redhat.com wrote:
> > > > > 
> > > > > > On 12/22/23 04:01, waffl3x wrote:
> > > > > > 
> > > > > > > int n = 0;
> > > > > > > auto f = []<typename Self>(this Self){
> > > > > > > static_assert(__is_same (decltype(n), int));
> > > > > > > decltype((n)) a; // { dg-error {is not captured} }
> > > > > > > };
> > > > > > > f();
> > > > > > > 
> > > > > > > Could you clarify if this error being removed was intentional. I do
> > > > > > > recall that Patrick Palka wanted to remove this error in his patch, but
> > > > > > > it seemed to me like you stated it would be incorrect to allow it.
> > > > > > > Since the error is no longer present I assume I am misunderstanding the
> > > > > > > exchange.
> > > > > > > 
> > > > > > > In any case, let me know if I need to modify my test case or if this
> > > > > > > error needs to be added back in.
> > > > > > 
> > > > > > Removing the error was correct under
> > > > > > https://eel.is/c++draft/expr.prim#id.unqual-3
> > > > > > Naming n in that lambda would not refer a capture by copy, so the
> > > > > > decltype is the same as outside the lambda.
> > > > > 
> > > > > Alright, I've fixed my tests to reflect that.
> > > > > 
> > > > > I've got defaulting assignment operators working. Defaulting equality
> > > > > and comparison operators seemed to work out of the box somehow, so I
> > > > > just have to make some fleshed out tests for those cases.
> > > > > 
> > > > > There can always be more tests, I have a few ideas for what still needs
> > > > > to be covered, mostly with dependent lambdas. Tests for xobj conversion
> > > > > operators definitely need to be more fleshed out. I also need to
> > > > > formulate some tests to make sure constraints are not being taking into
> > > > > account when the object parameters should not correspond, but that's a
> > > > > little more tough to test for than the valid cases.
> > > > > 
> > > > > Other than tests though, is there anything you can think of that the
> > > > > patch is missing? Other than the aforementioned tests, I'm pretty
> > > > > confident everything is done.
> > > > > 
> > > > > To recap, I have CWG2789 implemented on my end with the change we
> > > > > discussed to require corresponding object parameters instead of the
> > > > > same type, and I have CWG2586 implemented. I can't recall what other
> > > > > outstanding issues we had, and my notes don't mention anything other
> > > > > than tests. So I'm assuming everything is good.
> > > > 
> > > > Sounds good! Did you mean to include the updated patch?
> > > > 
> > > > Jason

^ permalink raw reply	[flat|nested] 100+ messages in thread

end of thread, other threads:[~2024-01-06 12:38 UTC | newest]

Thread overview: 100+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-08-31  6:02 [PATCH 1/2] c++: Initial support for P0847R7 (Deducing This) [PR102609] waffl3x
2023-08-31  8:33 ` Jakub Jelinek
2023-09-02  8:43   ` waffl3x
2023-09-11 13:49     ` [PATCH v2 " waffl3x
2023-09-19 20:14       ` Jason Merrill
2023-09-20  0:30         ` waffl3x
2023-09-20 21:19           ` Jason Merrill
2023-09-21 11:28             ` waffl3x
2023-09-22 11:30               ` Jason Merrill
2023-09-26  1:56                 ` [PATCH v3 " waffl3x
2023-09-27 22:43                   ` Hans-Peter Nilsson
2023-09-27 23:35                     ` Waffl3x
2023-10-17 20:53                   ` Jason Merrill
2023-10-17 21:11                   ` Jason Merrill
2023-10-18 11:46                     ` waffl3x
2023-10-18 14:17                       ` Jason Merrill
2023-10-18 17:28                         ` waffl3x
2023-10-18 17:45                           ` Jakub Jelinek
2023-10-18 18:12                           ` Jason Merrill
2023-10-19 21:05                             ` waffl3x
2023-10-19 21:11                               ` Jakub Jelinek
2023-10-19 21:31                                 ` waffl3x
2023-10-19 21:53                                   ` Jakub Jelinek
2023-10-19 22:18                               ` Jason Merrill
     [not found]                                 ` <Q2xXS2RkwtzDslDUAsi4YWupkb9s3QKvecqsxLNUr=5FL-MdSmzIJcZ96S3B9Avk30GneMm8R67JsQ4D-Uj1JB6N8dhTw6LAlNsrpLKHuLP2o=3D@protonmail.com>
2023-10-19 22:51                                 ` waffl3x
2023-10-19 23:35                                   ` waffl3x
2023-10-20  2:39                                     ` Jason Merrill
2023-10-20  4:34                                       ` waffl3x
2023-10-20 16:01                                         ` Jason Merrill
     [not found]                                           ` <zXWkSXEO=5FH62WXyNUeV1zNAx9wSVGQ5ooxKAfpN2InCP4X25uOC0yTZlnMqbDMoIa4lGwj0hP-KEP5UIcMs1S1zkhz=5FZx4oM3oz09DY2BRg=3D@protonmail.com>
     [not found]                                             ` <9YnRZJPkB8KCk8R86UDwWBoNnmOEAir4IU4enb1qIGgVdkB6dwy76ClgAzwpPqpToQ9sLTBs50EIRwhivBJU6RAFfLt-fjJxdYTZ35YVFWA=3D@protonmail.com>
2023-10-28  4:07                                           ` waffl3x
2023-10-28 23:21                                             ` waffl3x
2023-11-01 23:15                                               ` waffl3x
2023-11-02  7:01                                                 ` waffl3x
2023-11-02  7:01                                                 ` Jakub Jelinek
2023-11-03  3:04                                             ` Jason Merrill
2023-11-03  4:44                                               ` waffl3x
2023-11-03 18:05                                                 ` Jason Merrill
2023-11-04  6:40                                                   ` waffl3x
2023-11-09 18:44                                                     ` Jason Merrill
2023-11-10  4:24                                                       ` waffl3x
2023-11-10 23:12                                                         ` Jason Merrill
2023-11-11 10:43                                                           ` waffl3x
2023-11-11 11:24                                                             ` waffl3x
2023-11-14  3:48                                                             ` Jason Merrill
2023-11-14  4:36                                                               ` waffl3x
2023-11-18  6:43                                                                 ` waffl3x
2023-11-19  6:22                                                                   ` Jason Merrill
2023-11-19 13:39                                                                     ` waffl3x
2023-11-19 16:31                                                                       ` Jason Merrill
2023-11-19 18:36                                                                         ` waffl3x
2023-11-19 20:34                                                                           ` Jason Merrill
2023-11-19 21:44                                                                             ` waffl3x
2023-11-20 14:35                                                                               ` Jason Merrill
     [not found]                                                                                 ` <1MdaTybBd=5Fo4uw-Gb23fYyd5GNz7qFqoSe=5Ff5h90LY=5FBdzM2ge2qPSyCuiCLYoYcZSjmVv13fw1LmjQC=5FM2L8raS1fydY5pEJ=5Fvwvv5Z-0k=3D@protonmail.com>
2023-11-21 10:02                                                                                 ` waffl3x
2023-11-21 13:04                                                                                   ` [PATCH v5 1/1] " waffl3x
2023-11-22  3:22                                                                                     ` Jason Merrill
2023-11-22 20:46                                                                                       ` waffl3x
2023-11-22 21:38                                                                                         ` Jason Merrill
     [not found]                                                                                           ` <kltTuyDDwoyOmhBWostMKm5zF3sQCGz3HjMBrBUK6LOZp1-AbGMl5ijKKMlOncwR2yiWippyp89sFPZykNF3OVyz4yknnCVwn=5FiHJPUl25k=3D@protonmail.com>
     [not found]                                                                                             ` <dHEpSeuiljMbH0YhwLULApd3yO3LNaVkamGW2KJBYBl0EgMrtpJZ41GeTVOc77siD1kh2vkF4zwInWWGxYXfcnW4XV7sfDPX7cY028JiORE=3D@protonmail.com>
2023-11-22 21:56                                                                                           ` waffl3x
2023-11-22 22:44                                                                                           ` waffl3x
2023-11-24  6:49                                                                                             ` waffl3x
2023-11-24 23:26                                                                                               ` waffl3x
2023-11-25  1:14                                                                                                 ` waffl3x
2023-11-26 21:30                                                                                                   ` Jason Merrill
2023-11-26 23:10                                                                                                     ` waffl3x
2023-11-27  1:16                                                                                                       ` Jason Merrill
     [not found]                                                                                                         ` <BEI8PD7nktTuX7dimb22uDnR0b8Bc8ozi4xx9KbiEFj8TjgUCxMfEPpcIPL0bkdThBBab97T1uEJ9rUM3va1eiE1TyRw=5FiLrxwKgg30ZaW0=3D@protonmail.com>
2023-11-27  1:30                                                                                                         ` waffl3x
2023-11-27  1:44                                                                                                         ` waffl3x
2023-11-27  2:40                                                                                                           ` Jason Merrill
2023-11-27  5:35                                                                                                           ` [PATCH v6 " waffl3x
2023-11-28  3:31                                                                                                             ` waffl3x
2023-11-28 10:00                                                                                                               ` waffl3x
2023-11-30  5:00                                                                                                             ` Jason Merrill
2023-11-30  6:36                                                                                                               ` waffl3x
2023-11-30 14:55                                                                                                                 ` Jason Merrill
2023-12-01  6:02                                                                                                                   ` waffl3x
2023-12-01 16:52                                                                                                                     ` Jason Merrill
2023-12-02  1:31                                                                                                                       ` waffl3x
2023-12-02 15:02                                                                                                                         ` Jason Merrill
     [not found]                                                                                                                           ` <KQegse=5FguOyql4Ok1lrAgS7gasZJd1pOoPbCTdGxcHh-G4A9Tlf5zCnGJmqtshMt72edmcXdIapaZNPp4VJp5Ar9PHZbUrbwDsPjTSUrnOI=3D@protonmail.com>
     [not found]                                                                                                                             ` <59LofhYhxl7MLEuElXwZcESRB6MpjdG-iq-89B63siDRo5k0j-y6z-PVa6Y3iE1xE5LkJwpwTFi82bd0RZjB1yZbSJptFdPTBWfvOGj1W78=3D@protonmail.com>
2023-12-05  4:35                                                                                                                           ` waffl3x
2023-12-05  4:39                                                                                                                             ` waffl3x
2023-12-05  5:54                                                                                                                               ` waffl3x
2023-12-06  7:33                                                                                                                                 ` [PATCH v7 " waffl3x
2023-12-06  8:48                                                                                                                                   ` Jakub Jelinek
2023-12-06  9:31                                                                                                                                     ` waffl3x
2023-12-06 11:08                                                                                                                                   ` waffl3x
2023-12-08 19:25                                                                                                                                   ` Jason Merrill
2023-12-10 15:22                                                                                                                                     ` waffl3x
2023-12-10 18:59                                                                                                                                       ` Jason Merrill
2023-12-22  9:01                                                                                                                                         ` waffl3x
2023-12-22 17:26                                                                                                                                           ` Jason Merrill
2023-12-23  7:10                                                                                                                                             ` waffl3x
2023-12-26 16:37                                                                                                                                               ` Jason Merrill
2024-01-01 15:17                                                                                                                                                 ` waffl3x
2024-01-01 15:34                                                                                                                                                   ` waffl3x
2024-01-01 17:12                                                                                                                                                     ` waffl3x
2024-01-06 12:37                                                                                                                                                       ` waffl3x
2023-11-25 17:32                                                                                               ` [PATCH v5 " Jason Merrill
2023-11-25 22:59                                                                                                 ` waffl3x
2023-09-19 20:24   ` [PATCH 1/2] " Jason Merrill
     [not found] <b=5FiIXwWwO63ZE1ZSZHUIAdWyA2sqGsE3FM7eXfsInWogDyBZRsw8CwNsvFSDmEVmBtdq0pqb4zJ55HN2JCR7boDNramlEfne-R5PWdUXjbA=3D@protonmail.com>

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).