From bbfbcc72e8c0868559284352c71731394c98441e Mon Sep 17 00:00:00 2001 From: waffl3x 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 --- 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 *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 @@ -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 + 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 (); +} + -- 2.42.0