From 0db52146880faf20e7a7b786dad47c686a5f26d6 Mon Sep 17 00:00:00 2001 From: Waffl3x 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 --- 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 *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 @@ -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 + 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(); +} + 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(&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