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
@@ -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
@@ -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,
@@ -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,
@@ -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 ();
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
new file mode 100644
@@ -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
@@ -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