From e485a79ec5656e72ba46053618843c3d69331eab Mon Sep 17 00:00:00 2001 From: Waffl3x 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 --- 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 *args, tree fn_first_arg = NULL_TREE; const vec *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 + 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 + 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 +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 + 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 + 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 +auto S3::d0(this Self&&) -> void {} +auto S3::d1(this auto&&) -> void {} + +template +void call_with_qualification() +{ + T obj{}; + // by value should take any qualification (f0) + T{}.f0(); + obj.f0(); + static_cast(obj).f0(); + static_cast(obj).f0(); + static_cast(obj).f0(); + // specific qualification (f1 - f4) + T{}.f2(); + T{}.f3(); + T{}.f4(); + obj.f1(); + obj.f3(); + static_cast(obj).f2(); + static_cast(obj).f3(); + static_cast(obj).f4(); + static_cast(obj).f3(); + static_cast(obj).f4(); + // deduced should (obviously) take any qualification (d0, d1) + T{}.d0(); + obj.d0(); + static_cast(obj).d0(); + static_cast(obj).d0(); + static_cast(obj).d0(); + T{}.d1(); + obj.d1(); + static_cast(obj).d1(); + static_cast(obj).d1(); + static_cast(obj).d1(); +} + +void perform_calls() +{ + call_with_qualification(); + call_with_qualification(); + call_with_qualification(); + call_with_qualification(); +} \ 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(&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