From 2af9cf63ffc8a0b6a65614227cf1ccaf3d226574 Mon Sep 17 00:00:00 2001 From: waffl3x 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 --- 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 *args, tree fn_first_arg = NULL_TREE; const vec *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 *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 " + "%"); + 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 " + "%"); + 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], + "% 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], - "% lambda specifier with lambda capture"); - else + { + error_at (lambda_specs.locations[ds_storage_class], + "% lambda specifier with lambda capture"); + error_emitted = true; + } + if (xobj_param) + { + auto_diagnostic_group d; + error_at (lambda_specs.locations[ds_storage_class], + "% 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, + "% 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 + struct S : T { + using T::operator(); + operator int() const {return {};} + }; + + auto s = S{[x = 0](this auto&&) {}}; + s.operator()(); + + 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 ("% 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 ("% is unavailable for static member functions"); else if (fn && processing_contract_condition && DECL_CONSTRUCTOR_P (fn)) error ("invalid use of % 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 + 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-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_copy_from).f(); + uintptr_t const move_ret = static_cast(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 void d_templ_0(this Self&&) const; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" } + template void d_templ_1(this Self&&) volatile; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" } + template void d_templ_2(this Self&&) const volatile; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have cv-qualifier" } + template void d_templ_3(this Self&&) &; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" } + template void d_templ_4(this Self&&) &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have ref-qualifier" } + template void d_templ_5(this Self&&) const &; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" } + template void d_templ_6(this Self&&) const &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" } + template void d_templ_7(this Self&&) volatile &; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" } + template void d_templ_8(this Self&&) volatile &&; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" } + template void d_templ_9(this Self&&) const volatile &; // { dg-error "explicit object member function '(?!static)\[^\n\r\]+' cannot have (ref|cv)-qualifier" } + template 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(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 +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 +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(&decltype(f0)::operator()); + auto fp0_lref = static_cast(&decltype(f0)::operator()); + auto fp0_rref = static_cast(&decltype(f0)::operator()); + auto fp0_constlref = static_cast(&decltype(f0)::operator()); + auto fp0_constrref = static_cast(&decltype(f0)::operator()); + + auto f1 = [](this auto&& self) { return self; }; + auto fp1_lref = static_cast(&decltype(f1)::operator()); + auto fp1_rref = static_cast(&decltype(f1)::operator()); + auto fp1_constlref = static_cast(&decltype(f1)::operator()); + auto fp1_constrref = static_cast(&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(&decltype(f0)::operator()); + auto fp0_lref = static_cast(&decltype(f0)::operator()); + auto fp0_rref = static_cast(&decltype(f0)::operator()); + auto fp0_constlref = static_cast(&decltype(f0)::operator()); + auto fp0_constrref = static_cast(&decltype(f0)::operator()); + + auto f1 = [](this auto&& self) { return self; }; + auto fp1_value = static_cast(&decltype(f1)::operator()); // { dg-error {invalid 'static_cast' from type} } + auto fp1_lref = static_cast(&decltype(f1)::operator()); + auto fp1_rref = static_cast(&decltype(f1)::operator()); + auto fp1_constlref = static_cast(&decltype(f1)::operator()); + auto fp1_constrref = static_cast(&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 +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 +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 +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 + void operator()(this int&) {} +}; +template +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 +struct S : T { + using T::operator(); +}; + +template +S(T) -> S; + +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(f0)(); + static_cast(f1)(); + // f2 not needed + static_cast(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 + 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 +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()(); // { dg-bogus {no matching function for call to} } + + auto s1 = S1{[x = 0](this auto&& self) { return self; }}; // { dg-line t1_s1 } + s1.operator()(); // { 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 +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 +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 + void operator()(this S&, Args... args) {} +}; + +void non_dep() +{ + S s{}; + s(); + s(0); + s(0, 0); + s(0, 0, 0); +} + +template +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 + void operator[](this S&, Args... args) {} +}; + +void non_dep() +{ + S s{}; + s[]; + s[0]; + s[0, 0]; + s[0, 0, 0]; +} + +template +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 +void do_calls() +{ + Value value{}; + TEST_OPS(value) + TEST_OPS(static_cast(value)) + TEST_OPS(static_cast(value)) + TEST_OPS(static_cast(value)) + + LRef l_ref{}; + TEST_OPS(l_ref) + TEST_INVALID(static_cast(l_ref)) + TEST_INVALID(static_cast(l_ref)) + TEST_INVALID(static_cast(l_ref)) + + RRef r_ref{}; + TEST_INVALID(r_ref) + TEST_OPS(static_cast(r_ref)) + TEST_INVALID(static_cast(r_ref)) + TEST_INVALID(static_cast(r_ref)) + + ConstLRef const_l_ref{}; + TEST_OPS(const_l_ref) + TEST_OPS(static_cast(const_l_ref)) + TEST_OPS(static_cast(const_l_ref)) + TEST_OPS(static_cast(const_l_ref)) + + ConstRRef const_r_ref{}; + TEST_INVALID(const_r_ref) + TEST_OPS(static_cast(const_r_ref)) + TEST_INVALID(static_cast(const_r_ref)) + TEST_OPS(static_cast(const_r_ref)) + + Deduced deduced{}; + TEST_OPS(deduced) + TEST_OPS(static_cast(deduced)) + TEST_OPS(static_cast(deduced)) + TEST_OPS(static_cast(deduced)) + + VALIDATE_RETURN_TYPES(deduced, Deduced&) + VALIDATE_RETURN_TYPES(static_cast(deduced), Deduced&&) + VALIDATE_RETURN_TYPES(static_cast(deduced), Deduced const&) + VALIDATE_RETURN_TYPES(static_cast(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)) + TEST_OPS(static_cast(value)) + TEST_OPS(static_cast(value)) + + LRef l_ref{}; + TEST_OPS(l_ref) + TEST_INVALID(static_cast(l_ref)) + TEST_INVALID(static_cast(l_ref)) + TEST_INVALID(static_cast(l_ref)) + + RRef r_ref{}; + TEST_INVALID(r_ref) + TEST_OPS(static_cast(r_ref)) + TEST_INVALID(static_cast(r_ref)) + TEST_INVALID(static_cast(r_ref)) + + ConstLRef const_l_ref{}; + TEST_OPS(const_l_ref) + TEST_OPS(static_cast(const_l_ref)) + TEST_OPS(static_cast(const_l_ref)) + TEST_OPS(static_cast(const_l_ref)) + + ConstRRef const_r_ref{}; + TEST_INVALID(const_r_ref) + TEST_OPS(static_cast(const_r_ref)) + TEST_INVALID(static_cast(const_r_ref)) + TEST_OPS(static_cast(const_r_ref)) + + Deduced deduced{}; + TEST_OPS(deduced) + TEST_OPS(static_cast(deduced)) + TEST_OPS(static_cast(deduced)) + TEST_OPS(static_cast(deduced)) + + VALIDATE_RETURN_TYPES(deduced, Deduced&) + VALIDATE_RETURN_TYPES(static_cast(deduced), Deduced&&) + VALIDATE_RETURN_TYPES(static_cast(deduced), Deduced const&) + VALIDATE_RETURN_TYPES(static_cast(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 Self&& operator+=(this Self&& self, int) { return static_cast(self); } + template Self&& operator-=(this Self&& self, int) { return static_cast(self); } + template Self&& operator*=(this Self&& self, int) { return static_cast(self); } + template Self&& operator/=(this Self&& self, int) { return static_cast(self); } + template Self&& operator%=(this Self&& self, int) { return static_cast(self); } + template Self&& operator&=(this Self&& self, int) { return static_cast(self); } + template Self&& operator|=(this Self&& self, int) { return static_cast(self); } + template Self&& operator^=(this Self&& self, int) { return static_cast(self); } + template Self&& operator<<=(this Self&& self, int) { return static_cast(self); } + template Self&& operator>>=(this Self&& self, int) { return static_cast(self); } + + template Self&& operator++(this Self&& self) { return static_cast(self); } + template Self&& operator--(this Self&& self) { return static_cast(self); } + template Self&& operator++(this Self&& self, int) { return static_cast(self); } + template Self&& operator--(this Self&& self, int) { return static_cast(self); } + + template Self&& operator+(this Self&& self) { return static_cast(self); } + template Self&& operator-(this Self&& self) { return static_cast(self); } + template Self&& operator+(this Self&& self, int) { return static_cast(self); } + template Self&& operator-(this Self&& self, int) { return static_cast(self); } + template Self&& operator*(this Self&& self, int) { return static_cast(self); } + template Self&& operator/(this Self&& self, int) { return static_cast(self); } + template Self&& operator%(this Self&& self, int) { return static_cast(self); } + template Self&& operator&(this Self&& self, int) { return static_cast(self); } + template Self&& operator|(this Self&& self, int) { return static_cast(self); } + template Self&& operator^(this Self&& self, int) { return static_cast(self); } + template Self&& operator<<(this Self&& self, int) { return static_cast(self); } + template Self&& operator>>(this Self&& self, int) { return static_cast(self); } + + template Self&& operator!(this Self&& self) { return static_cast(self); } + template Self&& operator&&(this Self&& self, int const&) { return static_cast(self); } + template Self&& operator||(this Self&& self, int const&) { return static_cast(self); } + + template Self&& operator==(this Self&& self, int) { return static_cast(self); } + template Self&& operator!=(this Self&& self, int) { return static_cast(self); } + template Self&& operator<(this Self&& self, int) { return static_cast(self); } + template Self&& operator>(this Self&& self, int) { return static_cast(self); } + template Self&& operator<=(this Self&& self, int) { return static_cast(self); } + template Self&& operator>=(this Self&& self, int) { return static_cast(self); } + template Self&& operator<=>(this Self&& self, int) { return static_cast(self); } + + template Self&& operator*(this Self&& self) { return static_cast(self); } + template Self&& operator->*(this Self&& self, int) { return static_cast(self); } + template Self&& operator&(this Self&& self) { return static_cast(self); } + template Self&& operator,(this Self&& self, int) { return static_cast(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(self); } + RRef&& operator()(this RRef&& self) { return static_cast(self); } + RRef&& operator[](this RRef&& self) { return static_cast(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(self); } + ConstRRef const&& operator()(this ConstRRef const&& self) { return static_cast(self); } + ConstRRef const&& operator[](this ConstRRef const&& self) { return static_cast(self); } + ConstRRef const* operator->(this ConstRRef const&& self) { return &self; } +}; + +// needed to implement deduced operator-> +template struct remove_ref { using type = T; }; +template struct remove_ref { using type = T; }; +template struct remove_ref { using type = T; }; +template using remove_ref_t = typename remove_ref::type; + +struct Deduced { + int _v; + template + Self&& operator=(this Self&& self, int) { return static_cast(self); } + template + Self&& operator()(this Self&& self) { return static_cast(self); } + template + Self&& operator[](this Self&& self) { return static_cast(self); } + template + remove_ref_t* 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 +concept same_as = __is_same(T, U); + +#define TEST_VALID_WITH_RETURN_TYPES(OPERAND, CORRECT_TYPE) \ + static_assert(requires{ {(OPERAND) = 0} -> same_as; },"Unexpected failure with return type check calling operator = with " #OPERAND " -> expected return type: " #CORRECT_TYPE); \ + static_assert(requires{ {(OPERAND)()} -> same_as; }, "Unexpected failure with return type check calling operator () with " #OPERAND " -> expected return type: " #CORRECT_TYPE); \ + static_assert(requires{ {(OPERAND)[]} -> same_as; }, "Unexpected failure with return type check calling operator [] with " #OPERAND " -> expected return type: " #CORRECT_TYPE); + + +template +void test_value() +{ + DepValue value{}; + TEST_VALID(value) + TEST_VALID(static_cast(value)) + TEST_VALID(static_cast(value)) + TEST_VALID(static_cast(value)) +} + +template +void test_l_ref() +{ + DepLRef l_ref{}; + TEST_VALID(l_ref) + TEST_INVALID(static_cast(l_ref)) + TEST_INVALID(static_cast(l_ref)) + TEST_INVALID(static_cast(l_ref)) +} + +template +void test_r_ref() +{ + DepRRef r_ref{}; + TEST_INVALID(r_ref) + TEST_VALID(static_cast(r_ref)) + TEST_INVALID(static_cast(r_ref)) + TEST_INVALID(static_cast(r_ref)) +} + +template +void test_const_l_ref() +{ + DepConstLRef const_l_ref{}; + TEST_VALID(const_l_ref) + TEST_VALID(static_cast(const_l_ref)) + TEST_VALID(static_cast(const_l_ref)) + TEST_VALID(static_cast(const_l_ref)) +} + +template +void test_const_r_ref() +{ + DepConstRRef const_r_ref{}; + TEST_INVALID(const_r_ref) + TEST_VALID(static_cast(const_r_ref)) + TEST_INVALID(static_cast(const_r_ref)) + TEST_VALID(static_cast(const_r_ref)) +} + +template +void test_deduced() +{ + DepDeduced deduced{}; + + TEST_VALID(deduced) + TEST_VALID(static_cast(deduced)) + TEST_VALID(static_cast(deduced)) + TEST_VALID(static_cast(deduced)) + + TEST_VALID_WITH_RETURN_TYPES(deduced, DepDeduced&) + TEST_VALID_WITH_RETURN_TYPES(static_cast(deduced), DepDeduced&&) + TEST_VALID_WITH_RETURN_TYPES(static_cast(deduced), DepDeduced const&) + TEST_VALID_WITH_RETURN_TYPES(static_cast(deduced), DepDeduced const&&) + // arrow operator needs to be seperate to check the type of _v + static_assert(requires{ {(deduced->_v)} -> same_as; }, "Unexpected failure with return type check calling operator -> with deduced->_v"); + static_assert(requires{ {(static_cast(deduced)->_v)} -> same_as; }, "Unexpected failure with return type check calling operator -> with static_cast(deduced)->_v"); + static_assert(requires{ {(static_cast(deduced)->_v)} -> same_as; }, "Unexpected failure with return type check calling operator -> with static_cast(deduced)->_v"); + static_assert(requires{ {(static_cast(deduced)->_v)} -> same_as; }, "Unexpected failure with return type check calling operator -> with static_cast(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 +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; }, \ + "Unexpected success calling operator unary& with " #OPERAND); \ + static_assert(!requires{ {(OPERAND), 0} -> same_as; }, \ + "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; }, \ + "Unexpected failure calling operator unary& with " #OPERAND); \ + static_assert(requires{ {(OPERAND), 0} -> same_as; }, \ + "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; }); \ + static_assert(requires{ {(OPERAND) -= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) *= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) /= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) %= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) &= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) |= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) ^= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) <<= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) >>= 0} -> same_as; }); \ + \ + static_assert(requires{ {++(OPERAND)} -> same_as; }); \ + static_assert(requires{ {--(OPERAND)} -> same_as; }); \ + static_assert(requires{ {(OPERAND)++} -> same_as; }); \ + static_assert(requires{ {(OPERAND)--} -> same_as; }); \ + \ + static_assert(requires{ {+(OPERAND)} -> same_as; }); \ + static_assert(requires{ {-(OPERAND)} -> same_as; }); \ + static_assert(requires{ {(OPERAND) + 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) - 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) * 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) / 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) % 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) & 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) | 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) ^ 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) << 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) >> 0} -> same_as; }); \ + \ + static_assert(requires{ {!(OPERAND)} -> same_as; }); \ + static_assert(requires{ {(OPERAND) && 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) || 0} -> same_as; }); \ + \ + static_assert(requires{ {(OPERAND) == 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) != 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) < 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) > 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) <= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) >= 0} -> same_as; }); \ + static_assert(requires{ {(OPERAND) <=> 0} -> same_as; }); \ + \ + static_assert(requires{ {*(OPERAND)} -> same_as; }); \ + static_assert(requires{ {(OPERAND) ->* 0} -> same_as; }); \ + static_assert(requires{ {&(OPERAND)} -> same_as; }); \ + static_assert(requires{ {(OPERAND), 0} -> same_as; }); + +template +void test_value() +{ + DepValue value{}; + TEST_VALID(value, DepValue) + TEST_VALID(static_cast(value), DepValue) + TEST_VALID(static_cast(value), DepValue) + TEST_VALID(static_cast(value), DepValue) +} + +template +void test_l_ref() +{ + DepLRef l_ref{}; + TEST_VALID(l_ref, DepLRef&) + TEST_INVALID(static_cast(l_ref), DepLRef&) + TEST_INVALID(static_cast(l_ref), DepLRef&) + TEST_INVALID(static_cast(l_ref), DepLRef&) +} + +template +void test_r_ref() +{ + DepRRef r_ref{}; + TEST_INVALID(r_ref, DepRRef&&) + TEST_VALID(static_cast(r_ref), DepRRef&&) + TEST_INVALID(static_cast(r_ref), DepRRef&&) + TEST_INVALID(static_cast(r_ref), DepRRef&&) +} + +template +void test_const_l_ref() +{ + DepConstLRef const_l_ref{}; + TEST_VALID(const_l_ref, DepConstLRef const&) + TEST_VALID(static_cast(const_l_ref), DepConstLRef const&) + TEST_VALID(static_cast(const_l_ref), DepConstLRef const&) + TEST_VALID(static_cast(const_l_ref), DepConstLRef const&) +} + +template +void test_const_r_ref() +{ + DepConstRRef const_r_ref{}; + TEST_INVALID(const_r_ref, DepConstRRef const&&) + TEST_VALID(static_cast(const_r_ref), DepConstRRef const&&) + TEST_INVALID(static_cast(const_r_ref), DepConstRRef const&&) + TEST_VALID(static_cast(const_r_ref), DepConstRRef const&&) +} + +template +void test_deduced() +{ + DepDeduced deduced{}; + + TEST_VALID_WITH_RETURN_TYPES(deduced, DepDeduced&) + TEST_VALID_WITH_RETURN_TYPES(static_cast(deduced), DepDeduced&&) + TEST_VALID_WITH_RETURN_TYPES(static_cast(deduced), DepDeduced const&) + TEST_VALID_WITH_RETURN_TYPES(static_cast(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