From fbc980d85149409ce62c22f48d3693113803929e Mon Sep 17 00:00:00 2001 From: waffl3x Date: Sun, 7 Jan 2024 00:01:48 +0000 Subject: [PATCH] c++: P0847R7 (deducing this) - initial functionality. [PR102609] This implements the initial functionality for P0847R7. CWG2789 is implemented, but instead of "same type" for the object parameters we take correspondence into account instead. Without this alteration, the behavior here would be slightly different than the behavior with constrained member function templates, which I believe would be undesirable. There are a few outstanding issues related to xobj member functions overloading iobj member functions that are introduced by using declarations. Unfortunately, fixing this will be a little more involved and will have to be pushed back until later. Most diagnostics have been split out into another patch to improve its clarity and allow all the strictly functional changes to be more distinct. Explicit object lambdas and CWG2586 are addressed in a follow up patch. PR c++/102609 gcc/cp/ChangeLog: PR c++/102609 C++23 P0847R7 (deducing this) - initial functionality. * class.cc (xobj_iobj_parameters_correspond): New function, checks for corresponding object parameters between xobj and iobj member functions. (add_method): Handle object parameters of xobj member functions, use xobj_iobj_parameters_correspond. * call.cc (build_over_call): Refactor, handle xobj member functions. (cand_parms_match): Handle object parameters of xobj and iobj member functions, use xobj_iobj_parameters_correspond. * cp-tree.h (enum cp_decl_spec): Add ds_this, add comments. * decl.cc (grokfndecl): Add xobj_func_p parameter. For xobj member functions, Set xobj_flag, don't set static_function flag. (grokdeclarator): Handle xobj member functions, tell grokfndecl. (grok_op_properties): Don't error for xobj operators. * parser.cc (cp_parser_decl_specifier_seq): Handle this specifier. (cp_parser_parameter_declaration): Set default argument to "this_identifier" for xobj parameters. (set_and_check_decl_spec_loc): Add "this", add comments. * tree.cc (build_min_non_dep_op_overload): Handle xobj operators. * typeck.cc (cp_build_addr_expr_1): Handle address-of xobj member functions. gcc/testsuite/ChangeLog: PR c++/102609 C++23 P0847R7 (deducing this) - initial functionality. * 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-basic5.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-constraints.C: New test. * g++.dg/cpp23/explicit-obj-constraints2.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-redecl3.C: New test. * g++.dg/cpp23/explicit-obj-redecl4.C: New test. Signed-off-by: Waffl3x --- gcc/cp/call.cc | 184 +++++--- gcc/cp/class.cc | 244 +++++++++- gcc/cp/cp-tree.h | 7 +- gcc/cp/decl.cc | 44 +- gcc/cp/parser.cc | 22 +- gcc/cp/tree.cc | 10 +- gcc/cp/typeck.cc | 9 + gcc/testsuite/g++.dg/cpp23/explicit-obj-basic1.C | 114 +++++ gcc/testsuite/g++.dg/cpp23/explicit-obj-basic2.C | 28 ++ gcc/testsuite/g++.dg/cpp23/explicit-obj-basic3.C | 496 +++++++++++++++++++++ gcc/testsuite/g++.dg/cpp23/explicit-obj-basic4.C | 113 +++++ gcc/testsuite/g++.dg/cpp23/explicit-obj-basic5.C | 33 ++ .../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-constraints.C | 418 +++++++++++++++++ .../g++.dg/cpp23/explicit-obj-constraints2.C | 462 +++++++++++++++++++ .../g++.dg/cpp23/explicit-obj-ops-mem-arrow.C | 28 ++ .../g++.dg/cpp23/explicit-obj-ops-mem-assignment.C | 27 ++ .../g++.dg/cpp23/explicit-obj-ops-mem-call.C | 40 ++ .../g++.dg/cpp23/explicit-obj-ops-mem-subscript.C | 40 ++ .../g++.dg/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 +++++++++ .../g++.dg/cpp23/explicit-obj-ops-requires-mem.C | 171 +++++++ .../cpp23/explicit-obj-ops-requires-non-mem.C | 237 ++++++++++ gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl.C | 246 ++++++++++ gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl2.C | 161 +++++++ gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl3.C | 206 +++++++++ gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl4.C | 97 ++++ 31 files changed, 3832 insertions(+), 97 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-basic5.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-constraints.C create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-constraints2.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-redecl3.C create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl4.C diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index be89ff231bd..dca8e5090e2 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -9870,14 +9870,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; @@ -10074,45 +10069,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_FUNCTION_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) { @@ -10187,25 +10183,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_FUNCTION_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 @@ -10227,47 +10255,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; + bool const conversion_warning = !(null_node_p (current_arg) + && DECL_TEMPLATE_INFO (fn) + && cand->template_decl + && !cand->explicit_targs); - 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); - - 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; + 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; + return error_mark_node; + argarray[argarray_size++] = val; } /* Ellipsis */ @@ -10304,11 +10320,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. @@ -10324,7 +10340,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. */ @@ -12697,15 +12713,37 @@ cand_parms_match (z_candidate *c1, z_candidate *c2) } tree parms1 = TYPE_ARG_TYPES (TREE_TYPE (fn1)); tree parms2 = TYPE_ARG_TYPES (TREE_TYPE (fn2)); - if (DECL_FUNCTION_MEMBER_P (fn1) - && DECL_FUNCTION_MEMBER_P (fn2) - && (DECL_STATIC_FUNCTION_P (fn1) - != DECL_STATIC_FUNCTION_P (fn2))) + auto skip_parms = [](tree fn, tree parms){ + if (DECL_XOBJ_MEMBER_FUNCTION_P (fn)) + return TREE_CHAIN (parms); + else + return skip_artificial_parms_for (fn, parms); + }; + if (!(DECL_FUNCTION_MEMBER_P (fn1) + && DECL_FUNCTION_MEMBER_P (fn2))) + /* Early escape. */; + else if ((DECL_STATIC_FUNCTION_P (fn1) + != DECL_STATIC_FUNCTION_P (fn2))) { /* Ignore 'this' when comparing the parameters of a static member function with those of a non-static one. */ - parms1 = skip_artificial_parms_for (fn1, parms1); - parms2 = skip_artificial_parms_for (fn2, parms2); + parms1 = skip_parms (fn1, parms1); + parms2 = skip_parms (fn2, parms2); + } + else if ((DECL_XOBJ_MEMBER_FUNCTION_P (fn1) + || DECL_XOBJ_MEMBER_FUNCTION_P (fn2)) + && (DECL_IOBJ_MEMBER_FUNCTION_P (fn1) + || DECL_IOBJ_MEMBER_FUNCTION_P (fn2))) + { + bool xobj_iobj_parameters_correspond (tree, tree); + /* CWG2789 is not adequate, it should specify corresponding object + parameters, not same typed object parameters. */ + if (!xobj_iobj_parameters_correspond (fn1, fn2)) + return false; + /* We just compared the object parameters, if they don't correspond + we already return false. */ + parms1 = skip_parms (fn1, parms1); + parms2 = skip_parms (fn2, parms2); } return compparms (parms1, parms2); } diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc index 5bfdabecba5..6f924d51d27 100644 --- a/gcc/cp/class.cc +++ b/gcc/cp/class.cc @@ -1019,6 +1019,217 @@ modify_vtable_entry (tree t, } +/* Check if the object parameters of an xobj and iobj member function + correspond. This function assumes that the iobj parameter has been correctly + adjusted when the function is introduced by a using declaration per + [over.match.funcs.general.4]. */ + +bool +xobj_iobj_parameters_correspond (tree fn1, tree fn2) +{ + gcc_assert (DECL_IOBJ_MEMBER_FUNCTION_P (fn1) + || DECL_IOBJ_MEMBER_FUNCTION_P (fn2)); + gcc_assert (DECL_XOBJ_MEMBER_FUNCTION_P (fn1) + || DECL_XOBJ_MEMBER_FUNCTION_P (fn2)); + gcc_assert (fn1 != fn2); + + tree xobj_fn = DECL_XOBJ_MEMBER_FUNCTION_P (fn1) ? fn1 : fn2; + /* A reference, pointer, or something else. */ + tree xobj_param = TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (xobj_fn))); + + tree iobj_fn = DECL_IOBJ_MEMBER_FUNCTION_P (fn1) ? fn1 : fn2; + tree iobj_fn_type = TREE_TYPE (iobj_fn); + /* Will work for a pointer or reference param type. So this will continue + to work even if we change how the object parameter of an iobj member + function is represented. */ + tree iobj_param_type + = TREE_TYPE (TREE_VALUE (TYPE_ARG_TYPES (iobj_fn_type))); + + /* If the iobj member function was introduced with a using declaration, the + type of its object parameter is considered to be that of the class it was + introduced into. + + [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. + + Unfortunately, because of this rule, we can't just compare the xobj member + function's DECL_CONTEXT to its object parameter. + + struct S; + + struct B { + int f(this S&) { return 5; } + }; + + struct S : B { + using B::f; + int f() { return 10; } + }; + + The using declaration does not change the object parameter of B::f as it + is an xobj member function. However, its object parameter still + corresponds to S::f as it was declared with an object parameter of type + S const&. The DECL_CONTEXT of B::f is B, so if we compare the type of the + object parameter to that, it will not match. If we naively assume a + different type from the DECL_CONTEXT for an xobj parameter means that the + object parameters do not correspond, then the object parameters in the + above example will be considered non-corresponding. + + As a result of this, B::f would incorrectly not be discarded, causing an + ambiguity when f is called on an object of type S. + + This also impacts member functions with constraints as in the following + example. + + template + struct S; + + template + struct B { + int f(this S<>&) requires true { return 5; } + }; + + template + struct S : B<> { + using B<>::f; + int f() { return 10; } + }; + + Once again, if we compare the DECL_CONTEXT of B<>::f to it's xobj + parameter, it would not match. If the object parameters do not + correspond, constraints are not taken into account, so in this example we + would (probably) get an ambiguous lookup instead of correctly picking + B<>::f. + + Because of this caveat, we must actually compare the type of the iobj + parameter to the type of the xobj parameter, shortcuts will have these + edge cases. + + Aside from the more complex reasons above, this logic also implicitly + handles xobj parameters of pointer type, we don't have to explicitly + check for that case. */ + + /* FIXME: + + template + struct S; + + template + struct B { + int f(this S&) requires true { return 5; } + }; + + template + struct S : B { + using B::f; + int f() { return 10; } + }; + + This case is broken, the incomplete type seems to screw with things. + I'm not sure how to fix that so I'm just noting the issue here, I have a + feeling it's trivial to do if you know how. */ + + if (TYPE_MAIN_VARIANT (iobj_param_type) + != TYPE_MAIN_VARIANT (non_reference (xobj_param))) + return false; + /* We don't get to bail yet even if we have a by-value xobj parameter, + a by-value xobj parameter can correspond to an iobj parameter provided the + iobj member function is not declared with a reference qualifier. + + 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. */ + + cp_ref_qualifier const iobj_ref_qual = type_memfn_rqual (iobj_fn_type); + /* We only care about cv qualifiers when determining correspondence. */ + 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. + + However, 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 proceed 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 correspondence 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 corresponding object parameters if the iobj member + function has any cv-ref qualifiers. The only case where we might still be + dealing with corresponding object parameters is when the iobj member + function lacks any cv-ref qualification. */ + if (!TYPE_REF_P (xobj_param)) + { + if (iobj_ref_qual || iobj_cv_quals) + return false; + } + 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 ref qual. */ + cp_ref_qualifier const xobj_ref_qual = [&](){ + if (!TYPE_REF_P (xobj_param) || !iobj_ref_qual) + return REF_QUAL_NONE; + return TYPE_REF_IS_RVALUE (xobj_param) ? REF_QUAL_RVALUE + : REF_QUAL_LVALUE; + }(); /* IILE. */ + + /* 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, the object + parameters don't correspond. */ + if (iobj_ref_qual != xobj_ref_qual + || iobj_cv_quals != xobj_cv_quals) + return false; + } + /* If we got past everything else, the object parameters of fn1 and fn2 + definitely correspond. */ + return true; +} + /* Add method METHOD to class TYPE. If VIA_USING indicates whether METHOD is being injected via a using_decl. Returns true if the method could be added to the method vec. */ @@ -1079,8 +1290,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_FUNCTION_P (fn) + && DECL_IOBJ_MEMBER_FUNCTION_P (method) /* Either both or neither need to be ref-qualified for differing quals to allow overloading. */ && (FUNCTION_REF_QUALIFIED (fn_type) @@ -1089,6 +1300,35 @@ add_method (tree type, tree method, bool via_using) || type_memfn_rqual (fn_type) != type_memfn_rqual (method_type))) continue; + /* 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_FUNCTION_P (fn) + && !DECL_XOBJ_MEMBER_FUNCTION_P (method)) + || DECL_STATIC_FUNCTION_P (fn) || DECL_STATIC_FUNCTION_P (method)) + /* Early escape. */; + else if (DECL_XOBJ_MEMBER_FUNCTION_P (fn) + && DECL_XOBJ_MEMBER_FUNCTION_P (method)) + { + auto get_object_param = [](tree fn){ + return TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (fn))); + }; + /* We skip the object parameter below, check it here instead of + making changes to that code. */ + 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_FUNCTION_P (fn) + || DECL_XOBJ_MEMBER_FUNCTION_P (method)) + { + if (!xobj_iobj_parameters_correspond (fn, method)) + continue; + } + else + gcc_unreachable (); + tree real_fn = fn; tree real_method = method; diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index f0bddb7e11d..ce5771692fb 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -6341,11 +6341,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, @@ -6362,6 +6364,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 398ec6baa2b..16ccdb37aa6 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -10516,6 +10516,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, @@ -10525,7 +10526,6 @@ grokfndecl (tree ctype, location_t location) { tree decl; - int staticp = ctype && TREE_CODE (type) == FUNCTION_TYPE; tree t; if (location == UNKNOWN_LOCATION) @@ -10723,12 +10723,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; @@ -13198,6 +13195,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) @@ -13313,6 +13312,22 @@ 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 false; + /* 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 true; + }; + + is_xobj_member_function + = find_xobj_parm (declarator->u.function.parameters); if (reqs) error_at (location_of (reqs), "requires-clause on return type"); reqs = declarator->u.function.requires_clause; @@ -14386,6 +14401,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))) @@ -14607,7 +14624,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); @@ -14942,8 +14960,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); @@ -15744,7 +15762,7 @@ bool grok_op_properties (tree decl, bool complain) { tree argtypes = TYPE_ARG_TYPES (TREE_TYPE (decl)); - bool methodp = TREE_CODE (TREE_TYPE (decl)) == METHOD_TYPE; + bool const methodp = DECL_IOBJ_MEMBER_FUNCTION_P (decl); tree name = DECL_NAME (decl); location_t loc = DECL_SOURCE_LOCATION (decl); @@ -15837,7 +15855,7 @@ grok_op_properties (tree decl, bool complain) /* 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)) + if (!DECL_OBJECT_MEMBER_FUNCTION_P (decl)) { if (operator_code == TYPE_EXPR || operator_code == COMPONENT_REF @@ -15926,7 +15944,7 @@ grok_op_properties (tree decl, bool complain) } ++arity; } - + /* FIXME: We need tests for these errors with xobj member functions. */ /* Verify correct number of arguments. */ switch (op_flags) { diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 706ed21cf13..046c48d5552 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -16260,6 +16260,16 @@ cp_parser_decl_specifier_seq (cp_parser* parser, decl_specs->locations[ds_attribute] = token->location; continue; } + /* Special case for "this" specifier, indicating a parm is an xobj parm. + The "this" specifier must be the first specifier in the declaration, + after any attributes. */ + if (token->keyword == RID_THIS) + { + cp_lexer_consume_token (parser->lexer); + set_and_check_decl_spec_loc (decl_specs, ds_this, token); + continue; + } + /* Assume we will find a decl-specifier keyword. */ found_decl_spec = true; /* If the next token is an appropriate keyword, we can simply @@ -25721,6 +25731,13 @@ 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)) + { + /* 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 @@ -34091,6 +34108,8 @@ set_and_check_decl_spec_loc (cp_decl_specifier_seq *decl_specs, } else { + /* These correspond to cp-tree.h:cp_decl_spec, + changes here should also be reflected there. */ static const char *const decl_spec_names[] = { "signed", "unsigned", @@ -34108,7 +34127,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/tree.cc b/gcc/cp/tree.cc index f2e0f28bf06..77f57e0f9ac 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -3677,7 +3677,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_FUNCTION_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 @@ -3695,7 +3695,7 @@ 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_FUNCTION_P (overload)) { fn = overload; if (op == ARRAY_REF) @@ -3706,7 +3706,7 @@ build_min_non_dep_op_overload (enum tree_code op, vec_safe_push (args, arg); } } - else if (TREE_CODE (TREE_TYPE (overload)) == METHOD_TYPE) + else { tree object = va_arg (p, tree); tree binfo = TYPE_BINFO (TREE_TYPE (object)); @@ -3719,8 +3719,6 @@ build_min_non_dep_op_overload (enum tree_code op, vec_safe_push (args, arg); } } - else - gcc_unreachable (); va_end (p); call = build_min_non_dep_call_vec (non_dep, fn, args); @@ -3747,7 +3745,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_FUNCTION_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 d101cf0bca9..7e66913ae39 100644 --- a/gcc/cp/typeck.cc +++ b/gcc/cp/typeck.cc @@ -7265,6 +7265,15 @@ 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 similar to how + static member functions are handled in the BASELINK case above. */ + if (DECL_XOBJ_MEMBER_FUNCTION_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-basic5.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic5.C new file mode 100644 index 00000000000..9cf23d99047 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic5.C @@ -0,0 +1,33 @@ +// P0847R7 +// { dg-do run { target c++23 } } + +// conversion operators with xobj parameter + +inline constexpr int magic = 42; + +struct S0 { + operator int(this S0 const&) { + return magic; + } +}; + +struct S1 { + int _v; + int f(this int self) { + return self; + } + operator int(this S1 const& self) { + return self._v; + } +}; + +int main() +{ + if (S0{} != magic) + __builtin_abort (); + + S1 s{42}; + if (static_cast(s) != magic) + __builtin_abort (); +} + 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-constraints.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-constraints.C new file mode 100644 index 00000000000..eb8a8557a8d --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-constraints.C @@ -0,0 +1,418 @@ +// P0847R7 +// { dg-do run { target c++23 } } + +// overload resolution of static/xobj and iobj/xobj member functions +// with constraints + +template +concept Constrain = true; + +inline constexpr int iobj_fn = 5; +inline constexpr int xobj_fn = 10; +inline constexpr int static_fn = 20; + +// first 2 letters are the order of the definitions +// the constraint applies to the first definition, +// for *_r cases the constraint applies to the second + +struct S { + // xobj/static + int f_xs_v(this S, Constrain auto) { return xobj_fn; }; + static int f_xs_v(auto) { return static_fn; }; + + int f_xs_ref(this S&, Constrain auto) { return xobj_fn; }; + static int f_xs_ref(auto) { return static_fn; }; + + int f_xs_cref(this S const&, Constrain auto) { return xobj_fn; }; + static int f_xs_cref(auto) { return static_fn; }; + + int f_xs_rref(this S&&, Constrain auto) { return xobj_fn; }; + static int f_xs_rref(auto) { return static_fn; }; + + int f_xs_crref(this S const&&, Constrain auto) { return xobj_fn; }; + static int f_xs_crref(auto) { return static_fn; }; + + // _r + int f_xs_v_r(this S, auto) { return xobj_fn; }; + static int f_xs_v_r(Constrain auto) { return static_fn; }; + + int f_xs_ref_r(this S&, auto) { return xobj_fn; }; + static int f_xs_ref_r(Constrain auto) { return static_fn; }; + + int f_xs_cref_r(this S const&, auto) { return xobj_fn; }; + static int f_xs_cref_r(Constrain auto) { return static_fn; }; + + int f_xs_rref_r(this S&&, auto) { return xobj_fn; }; + static int f_xs_rref_r(Constrain auto) { return static_fn; }; + + int f_xs_crref_r(this S const&&, auto) { return xobj_fn; }; + static int f_xs_crref_r(Constrain auto) { return static_fn; }; + + // static/xobj + static int f_sx_v(Constrain auto) { return static_fn; }; + int f_sx_v(this S, auto) { return xobj_fn; }; + + static int f_sx_ref(Constrain auto) { return static_fn; }; + int f_sx_ref(this S&, auto) { return xobj_fn; }; + + static int f_sx_cref(Constrain auto) { return static_fn; }; + int f_sx_cref(this S const&, auto) { return xobj_fn; }; + + static int f_sx_rref(Constrain auto) { return static_fn; }; + int f_sx_rref(this S&&, auto) { return xobj_fn; }; + + static int f_sx_crref(Constrain auto) { return static_fn; }; + int f_sx_crref(this S const&&, auto) { return xobj_fn; }; + + // _r + static int f_sx_v_r(auto) { return static_fn; }; + int f_sx_v_r(this S, Constrain auto) { return xobj_fn; }; + + static int f_sx_ref_r(auto) { return static_fn; }; + int f_sx_ref_r(this S&, Constrain auto) { return xobj_fn; }; + + static int f_sx_cref_r(auto) { return static_fn; }; + int f_sx_cref_r(this S const&, Constrain auto) { return xobj_fn; }; + + static int f_sx_rref_r(auto) { return static_fn; }; + int f_sx_rref_r(this S&&, Constrain auto) { return xobj_fn; }; + + static int f_sx_crref_r(auto) { return static_fn; }; + int f_sx_crref_r(this S const&&, Constrain auto) { return xobj_fn; }; + + // xobj/iobj with matching object parameters + + // We are only testing constraints here, so we need parameter lists + // to match, which means we need corresponding object parameters. + // Remember, the rules for object parameter correspondence are weird. + // ([basic.scope.scope-3.1]) + + // *_refqual the iobj member function has a reference qualifier + // *_r the constraint applies to the second definition + + // ix + int f_ix_m0(Constrain auto) { return iobj_fn; }; + int f_ix_m0(this S&, auto) { return xobj_fn; }; + // See note + int f_ix_m1(Constrain auto) { return iobj_fn; }; + int f_ix_m1(this S&&, auto) { return xobj_fn; }; + + int f_ix_c0(Constrain auto) const { return iobj_fn; }; + int f_ix_c0(this S const&, auto) { return xobj_fn; }; + // See note + int f_ix_c1(Constrain auto) const { return iobj_fn; }; + int f_ix_c1(this S const&&, auto) { return xobj_fn; }; + + // xi + int f_xi_m0(this S&, Constrain auto) { return xobj_fn; }; + int f_xi_m0(auto) { return iobj_fn; }; + // See note + int f_xi_m1(this S&&, Constrain auto) { return xobj_fn; }; + int f_xi_m1(auto) { return iobj_fn; }; + + int f_xi_c0(this S const&, Constrain auto) { return xobj_fn; }; + int f_xi_c0(auto) const { return iobj_fn; }; + // See note + int f_xi_c1(this S const&&, Constrain auto) { return xobj_fn; }; + int f_xi_c1(auto) const { return iobj_fn; }; + + // with ref qualifier + + // ix + int f_ix_m0_refqual(Constrain auto) & { return iobj_fn; }; + int f_ix_m0_refqual(this S&, auto) { return xobj_fn; }; + + int f_ix_m1_refqual(Constrain auto) && { return iobj_fn; }; + int f_ix_m1_refqual(this S&&, auto) { return xobj_fn; }; + + int f_ix_c0_refqual(Constrain auto) const& { return iobj_fn; }; + int f_ix_c0_refqual(this S const&, auto) { return xobj_fn; }; + + int f_ix_c1_refqual(Constrain auto) const&& { return iobj_fn; }; + int f_ix_c1_refqual(this S const&&, auto) { return xobj_fn; }; + + // xi + int f_xi_m0_refqual(this S&, Constrain auto) { return xobj_fn; }; + int f_xi_m0_refqual(auto) & { return iobj_fn; }; + + int f_xi_m1_refqual(this S&&, Constrain auto) { return xobj_fn; }; + int f_xi_m1_refqual(auto) && { return iobj_fn; }; + + int f_xi_c0_refqual(this S const&, Constrain auto) { return xobj_fn; }; + int f_xi_c0_refqual(auto) const& { return iobj_fn; }; + + int f_xi_c1_refqual(this S const&&, Constrain auto) { return xobj_fn; }; + int f_xi_c1_refqual(auto) const&& { return iobj_fn; }; + + // _r without ref qualifier + + // ix + int f_ix_m0_r(auto) { return iobj_fn; }; + int f_ix_m0_r(this S&, Constrain auto) { return xobj_fn; }; + // See note + int f_ix_m1_r(auto) { return iobj_fn; }; + int f_ix_m1_r(this S&&, Constrain auto) { return xobj_fn; }; + + int f_ix_c0_r(auto) const { return iobj_fn; }; + int f_ix_c0_r(this S const&, Constrain auto) { return xobj_fn; }; + // See note + int f_ix_c1_r(auto) const { return iobj_fn; }; + int f_ix_c1_r(this S const&&, Constrain auto) { return xobj_fn; }; + + // xi + int f_xi_m0_r(this S&, auto) { return xobj_fn; }; + int f_xi_m0_r(Constrain auto) { return iobj_fn; }; + // See note + int f_xi_m1_r(this S&&, auto) { return xobj_fn; }; + int f_xi_m1_r(Constrain auto) { return iobj_fn; }; + + int f_xi_c0_r(this S const&, auto) { return xobj_fn; }; + int f_xi_c0_r(Constrain auto) const { return iobj_fn; }; + // See note + int f_xi_c1_r(this S const&&, auto) { return xobj_fn; }; + int f_xi_c1_r(Constrain auto) const { return iobj_fn; }; + + // _r with ref qualifier + // ix + int f_ix_m0_refqual_r(auto) & { return iobj_fn; }; + int f_ix_m0_refqual_r(this S&, Constrain auto) { return xobj_fn; }; + + int f_ix_m1_refqual_r(auto) && { return iobj_fn; }; + int f_ix_m1_refqual_r(this S&&, Constrain auto) { return xobj_fn; }; + + int f_ix_c0_refqual_r(auto) const& { return iobj_fn; }; + int f_ix_c0_refqual_r(this S const&, Constrain auto) { return xobj_fn; }; + + int f_ix_c1_refqual_r(auto) const&& { return iobj_fn; }; + int f_ix_c1_refqual_r(this S const&&, Constrain auto) { return xobj_fn; }; + + // xi + int f_xi_m0_refqual_r(this S&, auto) { return xobj_fn; }; + int f_xi_m0_refqual_r(Constrain auto) & { return iobj_fn; }; + + int f_xi_m1_refqual_r(this S&&, auto) { return xobj_fn; }; + int f_xi_m1_refqual_r(Constrain auto) && { return iobj_fn; }; + + int f_xi_c0_refqual_r(this S const&, auto) { return xobj_fn; }; + int f_xi_c0_refqual_r(Constrain auto) const& { return iobj_fn; }; + + int f_xi_c1_refqual_r(this S const&&, auto) { return xobj_fn; }; + int f_xi_c1_refqual_r(Constrain auto) const&& { return iobj_fn; }; +}; + + +int main() +{ + // The commented out cases are ambiguous, which is most likely the correct + // behavior. It is something that I want to propose to change, and I want + // to leave them in as they are a little weird. + // + // Furthermore, as the comment at the top of this file indicates, I am not + // clear on the correct behavior of the static/xobj cases in general. + + S s{}; + if (s.f_xs_v (0) != xobj_fn) + __builtin_abort (); + if (s.f_xs_ref (0) != xobj_fn) + __builtin_abort (); + if (s.f_xs_cref (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xs_rref (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xs_crref (0) != xobj_fn) + __builtin_abort (); + // if (s.f_xs_dv (0) != xobj_fn) + // __builtin_abort (); + // if (s.f_xs_dcref (0) != xobj_fn) + // __builtin_abort (); + // if (s.f_xs_dfwdref (0) != xobj_fn) + // __builtin_abort (); + + if (s.f_xs_v_r (0) != static_fn) + __builtin_abort (); + if (s.f_xs_ref_r (0) != static_fn) + __builtin_abort (); + if (s.f_xs_cref_r (0) != static_fn) + __builtin_abort (); + if (static_cast(s).f_xs_rref_r (0) != static_fn) + __builtin_abort (); + if (static_cast(s).f_xs_crref_r (0) != static_fn) + __builtin_abort (); + // if (s.f_xs_dv_r (0) != static_fn) + // __builtin_abort (); + // if (s.f_xs_dcref_r (0) != static_fn) + // __builtin_abort (); + // if (s.f_xs_dfwdref_r (0) != static_fn) + // __builtin_abort (); + + if (s.f_sx_v (0) != static_fn) + __builtin_abort (); + if (s.f_sx_ref (0) != static_fn) + __builtin_abort (); + if (s.f_sx_cref (0) != static_fn) + __builtin_abort (); + if (static_cast(s).f_sx_rref (0) != static_fn) + __builtin_abort (); + if (static_cast(s).f_sx_crref (0) != static_fn) + __builtin_abort (); + // if (s.f_sx_dv (0) != static_fn) + // __builtin_abort (); + // if (s.f_sx_dcref (0) != static_fn) + // __builtin_abort (); + // if (s.f_sx_dfwdref (0) != static_fn) + // __builtin_abort (); + + if (s.f_sx_v_r (0) != xobj_fn) + __builtin_abort (); + if (s.f_sx_ref_r (0) != xobj_fn) + __builtin_abort (); + if (s.f_sx_cref_r (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_sx_rref_r (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_sx_crref_r (0) != xobj_fn) + __builtin_abort (); + // if (s.f_sx_dv_r (0) != xobj_fn) + // __builtin_abort (); + // if (s.f_sx_dcref_r (0) != xobj_fn) + // __builtin_abort (); + // if (s.f_sx_dfwdref_r (0) != xobj_fn) + // __builtin_abort (); + + // iobj/xobj + + // The commented out cases are tested below as their correct behavior is + // unintuitive, see the note below for details. + + if (s.f_ix_m0 (0) != iobj_fn) + __builtin_abort (); + // s.f_ix_m1 + if (s.f_ix_c0 (0) != iobj_fn) + __builtin_abort (); + // s.f_ix_c1 + if (s.f_xi_m0 (0) != xobj_fn) + __builtin_abort (); + // s.f_xi_m1 + if (s.f_xi_c0 (0) != xobj_fn) + __builtin_abort (); + // s.f_xi_c1 + if (s.f_ix_m0_refqual (0) != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_m1_refqual (0) != iobj_fn) + __builtin_abort (); + if (s.f_ix_c0_refqual (0) != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_c1_refqual (0) != iobj_fn) + __builtin_abort (); + if (s.f_xi_m0_refqual (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_m1_refqual (0) != xobj_fn) + __builtin_abort (); + if (s.f_xi_c0_refqual (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_c1_refqual (0) != xobj_fn) + __builtin_abort (); + if (s.f_ix_m0_r (0) != xobj_fn) + __builtin_abort (); + // s.f_ix_m1_r + if (s.f_ix_c0_r (0) != xobj_fn) + __builtin_abort (); + // s.f_ix_c1_r + if (s.f_xi_m0_r (0) != iobj_fn) + __builtin_abort (); + // s.f_xi_m1_r + if (s.f_xi_c0_r (0) != iobj_fn) + __builtin_abort (); + // s.f_xi_c1_r + if (s.f_ix_m0_refqual_r (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_m1_refqual_r (0) != xobj_fn) + __builtin_abort (); + if (s.f_ix_c0_refqual_r (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_c1_refqual_r (0) != xobj_fn) + __builtin_abort (); + if (s.f_xi_m0_refqual_r (0) != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_m1_refqual_r (0) != iobj_fn) + __builtin_abort (); + if (s.f_xi_c0_refqual_r (0) != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_c1_refqual_r (0) != iobj_fn) + __builtin_abort (); + + +/* Note: + These cases are weird, the object argument correspond, but are not the same + type ([basic.scope.scope-3.1]), so we get this funny edge case where the + constraint stops them from being considered redeclarations, but isn't taken + into account for the lvalue case. You can't bind an lvalue to an rvalue + reference so the iobj member function is always taken regardless of which + overload is constrained. + + [over.match.funcs.general-4] + For implicit object member functions, the type of the implicit object + parameter is + (4.1) “lvalue reference to cv X” for functions declared without a + ref-qualifier or with the & ref-qualifier + + You would think that calling these functions with an rvalue would be the + same then, always taking the xobj member function. However, for backwards + compatibility reasons, an unqualified member function can be called on an + object that is an rvalue. + + [over.match.funcs.general-5] + For implicit object member functions declared without a ref-qualifier, even + if the implicit object parameter is not const-qualified, an rvalue can be + bound to the parameter as long as in all other respects the argument can be + converted to the type of the implicit object parameter. + + And finally, since the object parameters correspond ([basic.scope.scope-3.1]) + the constraints are taken into account. + + So in conclusion, calling these functions with an lvalue always resolves to + the iobj member function, and calling them with rvalues take the constraints + into account. + + As wacky as this is, this is the correct behavior. */ + + // Always takes the iobj member function, can't bind an lvalue to an rvalue + // reference. + if (s.f_ix_m1 (0) != iobj_fn) + __builtin_abort (); + if (s.f_ix_c1 (0) != iobj_fn) + __builtin_abort (); + if (s.f_xi_m1 (0) != iobj_fn) + __builtin_abort (); + if (s.f_xi_c1 (0) != iobj_fn) + __builtin_abort (); + + if (s.f_ix_m1_r (0) != iobj_fn) + __builtin_abort (); + if (s.f_ix_c1_r (0) != iobj_fn) + __builtin_abort (); + if (s.f_xi_m1_r (0) != iobj_fn) + __builtin_abort (); + if (s.f_xi_c1_r (0) != iobj_fn) + __builtin_abort (); + + // Constraints are taken into account here, see note for more information. + if (static_cast(s).f_ix_m1 (0) != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_c1 (0) != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_m1 (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_c1 (0) != xobj_fn) + __builtin_abort (); + + if (static_cast(s).f_ix_m1_r (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_c1_r (0) != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_m1_r (0) != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_c1_r (0) != iobj_fn) + __builtin_abort (); +} + diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-constraints2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-constraints2.C new file mode 100644 index 00000000000..d3909ec272d --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-constraints2.C @@ -0,0 +1,462 @@ +// P0847R7 +// { dg-do run { target c++23 } } + +// overload resolution of static/xobj and iobj/xobj non-template member functions +// with constraints in a class template + +template +concept Constrain = true; + +inline constexpr int iobj_fn = 5; +inline constexpr int xobj_fn = 10; +inline constexpr int static_fn = 20; + +// first 2 letters are the order of the definitions +// the constraint applies to the first definition, +// for *_r cases the constraint applies to the second + +template +struct S { + // xobj/static + int f_xs_v(this S) requires Constrain { return xobj_fn; }; + static int f_xs_v() { return static_fn; }; + + int f_xs_ref(this S&) requires Constrain { return xobj_fn; }; + static int f_xs_ref() { return static_fn; }; + + int f_xs_cref(this S const&) requires Constrain { return xobj_fn; }; + static int f_xs_cref() { return static_fn; }; + + int f_xs_rref(this S&&) requires Constrain { return xobj_fn; }; + static int f_xs_rref() { return static_fn; }; + + int f_xs_crref(this S const&&) requires Constrain { return xobj_fn; }; + static int f_xs_crref() { return static_fn; }; + + int f_xs_dv(this auto) requires Constrain { return xobj_fn; }; + static int f_xs_dv() { return static_fn; }; + + int f_xs_dcref(this auto const&) requires Constrain { return xobj_fn; }; + static int f_xs_dcref() { return static_fn; }; + + int f_xs_dfwdref(this auto&&) requires Constrain { return xobj_fn; }; + static int f_xs_dfwdref() { return static_fn; }; + + // _r + int f_xs_v_r(this S) { return xobj_fn; }; + static int f_xs_v_r() requires Constrain { return static_fn; }; + + int f_xs_ref_r(this S&) { return xobj_fn; }; + static int f_xs_ref_r() requires Constrain { return static_fn; }; + + int f_xs_cref_r(this S const&) { return xobj_fn; }; + static int f_xs_cref_r() requires Constrain { return static_fn; }; + + int f_xs_rref_r(this S&&) { return xobj_fn; }; + static int f_xs_rref_r() requires Constrain { return static_fn; }; + + int f_xs_crref_r(this S const&&) { return xobj_fn; }; + static int f_xs_crref_r() requires Constrain { return static_fn; }; + + int f_xs_dv_r(this auto) { return xobj_fn; }; + static int f_xs_dv_r() requires Constrain { return static_fn; }; + + int f_xs_dcref_r(this auto const&) { return xobj_fn; }; + static int f_xs_dcref_r() requires Constrain { return static_fn; }; + + int f_xs_dfwdref_r(this auto&&) { return xobj_fn; }; + static int f_xs_dfwdref_r() requires Constrain { return static_fn; }; + + // static/xobj + static int f_sx_v() requires Constrain { return static_fn; }; + int f_sx_v(this S) { return xobj_fn; }; + + static int f_sx_ref() requires Constrain { return static_fn; }; + int f_sx_ref(this S&) { return xobj_fn; }; + + static int f_sx_cref() requires Constrain { return static_fn; }; + int f_sx_cref(this S const&) { return xobj_fn; }; + + static int f_sx_rref() requires Constrain { return static_fn; }; + int f_sx_rref(this S&&) { return xobj_fn; }; + + static int f_sx_crref() requires Constrain { return static_fn; }; + int f_sx_crref(this S const&&) { return xobj_fn; }; + + static int f_sx_dv() requires Constrain { return static_fn; }; + int f_sx_dv(this auto) { return xobj_fn; }; + + static int f_sx_dcref() requires Constrain { return static_fn; }; + int f_sx_dcref(this auto const&) { return xobj_fn; }; + + static int f_sx_dfwdref() requires Constrain { return static_fn; }; + int f_sx_dfwdref(this auto&&) { return xobj_fn; }; + + // _r + static int f_sx_v_r() { return static_fn; }; + int f_sx_v_r(this S) requires Constrain { return xobj_fn; }; + + static int f_sx_ref_r() { return static_fn; }; + int f_sx_ref_r(this S&) requires Constrain { return xobj_fn; }; + + static int f_sx_cref_r() { return static_fn; }; + int f_sx_cref_r(this S const&) requires Constrain { return xobj_fn; }; + + static int f_sx_rref_r() { return static_fn; }; + int f_sx_rref_r(this S&&) requires Constrain { return xobj_fn; }; + + static int f_sx_crref_r() { return static_fn; }; + int f_sx_crref_r(this S const&&) requires Constrain { return xobj_fn; }; + + static int f_sx_dv_r() { return static_fn; }; + int f_sx_dv_r(this auto) requires Constrain { return xobj_fn; }; + + static int f_sx_dcref_r() { return static_fn; }; + int f_sx_dcref_r(this auto const&) requires Constrain { return xobj_fn; }; + + static int f_sx_dfwdref_r() { return static_fn; }; + int f_sx_dfwdref_r(this auto&&) requires Constrain { return xobj_fn; }; + + // xobj/iobj with matching object parameters + + // We are only testing constraints here, so we need parameter lists + // to match, which means we need corresponding object parameters. + // Remember, the rules for object parameter correspondence are weird. + // ([basic.scope.scope-3.1]) + // + // NOTE: CWG2789 does not specify this properly, I am implementing it + // assuming the above correspondence rules + + // *_refqual the iobj member function has a reference qualifier + // *_r the constraint applies to the second definition + + // ix + int f_ix_m0() requires Constrain { return iobj_fn; }; + int f_ix_m0(this S&) { return xobj_fn; }; + // See note + int f_ix_m1() requires Constrain { return iobj_fn; }; + int f_ix_m1(this S&&) { return xobj_fn; }; + + int f_ix_c0() const requires Constrain { return iobj_fn; }; + int f_ix_c0(this S const&) { return xobj_fn; }; + // See note + int f_ix_c1() const requires Constrain { return iobj_fn; }; + int f_ix_c1(this S const&&) { return xobj_fn; }; + + // xi + int f_xi_m0(this S&) requires Constrain { return xobj_fn; }; + int f_xi_m0() { return iobj_fn; }; + // See note + int f_xi_m1(this S&&) requires Constrain { return xobj_fn; }; + int f_xi_m1() { return iobj_fn; }; + + int f_xi_c0(this S const&) requires Constrain { return xobj_fn; }; + int f_xi_c0() const { return iobj_fn; }; + // See note + int f_xi_c1(this S const&&) requires Constrain { return xobj_fn; }; + int f_xi_c1() const { return iobj_fn; }; + + // with ref qualifier + + // ix + int f_ix_m0_refqual() & requires Constrain { return iobj_fn; }; + int f_ix_m0_refqual(this S&) { return xobj_fn; }; + + int f_ix_m1_refqual() && requires Constrain { return iobj_fn; }; + int f_ix_m1_refqual(this S&&) { return xobj_fn; }; + + int f_ix_c0_refqual() const& requires Constrain { return iobj_fn; }; + int f_ix_c0_refqual(this S const&) { return xobj_fn; }; + + int f_ix_c1_refqual() const&& requires Constrain { return iobj_fn; }; + int f_ix_c1_refqual(this S const&&) { return xobj_fn; }; + + // xi + int f_xi_m0_refqual(this S&) requires Constrain { return xobj_fn; }; + int f_xi_m0_refqual() & { return iobj_fn; }; + + int f_xi_m1_refqual(this S&&) requires Constrain { return xobj_fn; }; + int f_xi_m1_refqual() && { return iobj_fn; }; + + int f_xi_c0_refqual(this S const&) requires Constrain { return xobj_fn; }; + int f_xi_c0_refqual() const& { return iobj_fn; }; + + int f_xi_c1_refqual(this S const&&) requires Constrain { return xobj_fn; }; + int f_xi_c1_refqual() const&& { return iobj_fn; }; + + // _r without ref qualifier + + // ix + int f_ix_m0_r() { return iobj_fn; }; + int f_ix_m0_r(this S&) requires Constrain { return xobj_fn; }; + // See note + int f_ix_m1_r() { return iobj_fn; }; + int f_ix_m1_r(this S&&) requires Constrain { return xobj_fn; }; + + int f_ix_c0_r() const { return iobj_fn; }; + int f_ix_c0_r(this S const&) requires Constrain { return xobj_fn; }; + // See note + int f_ix_c1_r() const { return iobj_fn; }; + int f_ix_c1_r(this S const&&) requires Constrain { return xobj_fn; }; + + // xi + int f_xi_m0_r(this S&) { return xobj_fn; }; + int f_xi_m0_r() requires Constrain { return iobj_fn; }; + // See note + int f_xi_m1_r(this S&&) { return xobj_fn; }; + int f_xi_m1_r() requires Constrain { return iobj_fn; }; + + int f_xi_c0_r(this S const&) { return xobj_fn; }; + int f_xi_c0_r() const requires Constrain { return iobj_fn; }; + // See note + int f_xi_c1_r(this S const&&) { return xobj_fn; }; + int f_xi_c1_r() const requires Constrain { return iobj_fn; }; + + // _r with ref qualifier + // ix + int f_ix_m0_refqual_r() & { return iobj_fn; }; + int f_ix_m0_refqual_r(this S&) requires Constrain { return xobj_fn; }; + + int f_ix_m1_refqual_r() && { return iobj_fn; }; + int f_ix_m1_refqual_r(this S&&) requires Constrain { return xobj_fn; }; + + int f_ix_c0_refqual_r() const& { return iobj_fn; }; + int f_ix_c0_refqual_r(this S const&) requires Constrain { return xobj_fn; }; + + int f_ix_c1_refqual_r() const&& { return iobj_fn; }; + int f_ix_c1_refqual_r(this S const&&) requires Constrain { return xobj_fn; }; + + // xi + int f_xi_m0_refqual_r(this S&) { return xobj_fn; }; + int f_xi_m0_refqual_r() & requires Constrain { return iobj_fn; }; + + int f_xi_m1_refqual_r(this S&&) { return xobj_fn; }; + int f_xi_m1_refqual_r() && requires Constrain { return iobj_fn; }; + + int f_xi_c0_refqual_r(this S const&) { return xobj_fn; }; + int f_xi_c0_refqual_r() const& requires Constrain { return iobj_fn; }; + + int f_xi_c1_refqual_r(this S const&&) { return xobj_fn; }; + int f_xi_c1_refqual_r() const&& requires Constrain { return iobj_fn; }; +}; + +int main() +{ + // The commented out cases are ambiguous, which is most likely the correct + // behavior. It is something that I want to propose to change, and I want + // to leave them in as they are a little weird. + // + // Furthermore, as the comment at the top of this file indicates, I am not + // clear on the correct behavior of the static/xobj cases in general. + using S = S; + S s{}; + if (s.f_xs_v () != xobj_fn) + __builtin_abort (); + if (s.f_xs_ref () != xobj_fn) + __builtin_abort (); + if (s.f_xs_cref () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xs_rref () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xs_crref () != xobj_fn) + __builtin_abort (); + // if (s.f_xs_dv () != xobj_fn) + // __builtin_abort (); + // if (s.f_xs_dcref () != xobj_fn) + // __builtin_abort (); + // if (s.f_xs_dfwdref () != xobj_fn) + // __builtin_abort (); + + if (s.f_xs_v_r () != static_fn) + __builtin_abort (); + if (s.f_xs_ref_r () != static_fn) + __builtin_abort (); + if (s.f_xs_cref_r () != static_fn) + __builtin_abort (); + if (static_cast(s).f_xs_rref_r () != static_fn) + __builtin_abort (); + if (static_cast(s).f_xs_crref_r () != static_fn) + __builtin_abort (); + // if (s.f_xs_dv_r () != static_fn) + // __builtin_abort (); + // if (s.f_xs_dcref_r () != static_fn) + // __builtin_abort (); + // if (s.f_xs_dfwdref_r () != static_fn) + // __builtin_abort (); + + if (s.f_sx_v () != static_fn) + __builtin_abort (); + if (s.f_sx_ref () != static_fn) + __builtin_abort (); + if (s.f_sx_cref () != static_fn) + __builtin_abort (); + if (static_cast(s).f_sx_rref () != static_fn) + __builtin_abort (); + if (static_cast(s).f_sx_crref () != static_fn) + __builtin_abort (); + // if (s.f_sx_dv () != static_fn) + // __builtin_abort (); + // if (s.f_sx_dcref () != static_fn) + // __builtin_abort (); + // if (s.f_sx_dfwdref () != static_fn) + // __builtin_abort (); + + if (s.f_sx_v_r () != xobj_fn) + __builtin_abort (); + if (s.f_sx_ref_r () != xobj_fn) + __builtin_abort (); + if (s.f_sx_cref_r () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_sx_rref_r () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_sx_crref_r () != xobj_fn) + __builtin_abort (); + // if (s.f_sx_dv_r () != xobj_fn) + // __builtin_abort (); + // if (s.f_sx_dcref_r () != xobj_fn) + // __builtin_abort (); + // if (s.f_sx_dfwdref_r () != xobj_fn) + // __builtin_abort (); + + // iobj/xobj + + // The commented out cases are tested below as their correct behavior is + // unintuitive, see the note below for details. + + if (s.f_ix_m0 () != iobj_fn) + __builtin_abort (); + // s.f_ix_m1 + if (s.f_ix_c0 () != iobj_fn) + __builtin_abort (); + // s.f_ix_c1 + if (s.f_xi_m0 () != xobj_fn) + __builtin_abort (); + // s.f_xi_m1 + if (s.f_xi_c0 () != xobj_fn) + __builtin_abort (); + // s.f_xi_c1 + if (s.f_ix_m0_refqual () != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_m1_refqual () != iobj_fn) + __builtin_abort (); + if (s.f_ix_c0_refqual () != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_c1_refqual () != iobj_fn) + __builtin_abort (); + if (s.f_xi_m0_refqual () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_m1_refqual () != xobj_fn) + __builtin_abort (); + if (s.f_xi_c0_refqual () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_c1_refqual () != xobj_fn) + __builtin_abort (); + if (s.f_ix_m0_r () != xobj_fn) + __builtin_abort (); + // s.f_ix_m1_r + if (s.f_ix_c0_r () != xobj_fn) + __builtin_abort (); + // s.f_ix_c1_r + if (s.f_xi_m0_r () != iobj_fn) + __builtin_abort (); + // s.f_xi_m1_r + if (s.f_xi_c0_r () != iobj_fn) + __builtin_abort (); + // s.f_xi_c1_r + if (s.f_ix_m0_refqual_r () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_m1_refqual_r () != xobj_fn) + __builtin_abort (); + if (s.f_ix_c0_refqual_r () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_c1_refqual_r () != xobj_fn) + __builtin_abort (); + if (s.f_xi_m0_refqual_r () != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_m1_refqual_r () != iobj_fn) + __builtin_abort (); + if (s.f_xi_c0_refqual_r () != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_c1_refqual_r () != iobj_fn) + __builtin_abort (); + + +/* Foreword: CWG2789 does not specifiy this correctly, it needs to be changed + to consider correspondence instead of "same type" or else the following + does not make sense. My implementation assumes correspondence should be + considered. + + Note: + These cases are weird, the object argument correspond, but are not the same + type ([basic.scope.scope-3.1]), so we get this funny edge case where the + constraint stops them from being considered redeclarations, but isn't taken + into account for the lvalue case. You can't bind an lvalue to an rvalue + reference so the iobj member function is always taken regardless of which + overload is constrained. + + [over.match.funcs.general-4] + For implicit object member functions, the type of the implicit object + parameter is + (4.1) “lvalue reference to cv X” for functions declared without a + ref-qualifier or with the & ref-qualifier + + You would think that calling these functions with an rvalue would be the + same then, always taking the xobj member function. However, for backwards + compatibility reasons, an unqualified member function can be called on an + object that is an rvalue. + + [over.match.funcs.general-5] + For implicit object member functions declared without a ref-qualifier, even + if the implicit object parameter is not const-qualified, an rvalue can be + bound to the parameter as long as in all other respects the argument can be + converted to the type of the implicit object parameter. + + And finally, since the object parameters correspond ([basic.scope.scope-3.1]) + the constraints are taken into account. + + So in conclusion, calling these functions with an lvalue always resolves to + the iobj member function, and calling them with rvalues take the constraints + into account. + + As wacky as this is, this is the correct behavior. */ + + // Always takes the iobj member function, can't bind an lvalue to an rvalue + // reference. + if (s.f_ix_m1 () != iobj_fn) + __builtin_abort (); + if (s.f_ix_c1 () != iobj_fn) + __builtin_abort (); + if (s.f_xi_m1 () != iobj_fn) + __builtin_abort (); + if (s.f_xi_c1 () != iobj_fn) + __builtin_abort (); + + if (s.f_ix_m1_r () != iobj_fn) + __builtin_abort (); + if (s.f_ix_c1_r () != iobj_fn) + __builtin_abort (); + if (s.f_xi_m1_r () != iobj_fn) + __builtin_abort (); + if (s.f_xi_c1_r () != iobj_fn) + __builtin_abort (); + + // Constraints are taken into account here, see note for more information. + if (static_cast(s).f_ix_m1 () != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_c1 () != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_m1 () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_c1 () != xobj_fn) + __builtin_abort (); + + if (static_cast(s).f_ix_m1_r () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_ix_c1_r () != xobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_m1_r () != iobj_fn) + __builtin_abort (); + if (static_cast(s).f_xi_c1_r () != iobj_fn) + __builtin_abort (); +} + 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-redecl3.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl3.C new file mode 100644 index 00000000000..10cf82d4978 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl3.C @@ -0,0 +1,206 @@ +// P0847R7 +// { dg-do compile { target c++23 } } + +// redeclarations of xobj member functions as static member functions and vice versa + +struct S { +// no additional params + void f_xs_v(this S) {}; // { dg-note {previous declaration} } + static void f_xs_v() {}; // { dg-error {cannot be overloaded with} } + + void f_xs_ref(this S&) {}; // { dg-note {previous declaration} } + static void f_xs_ref() {}; // { dg-error {cannot be overloaded with} } + + void f_xs_cref(this S const&) {}; // { dg-note {previous declaration} } + static void f_xs_cref() {}; // { dg-error {cannot be overloaded with} } + + void f_xs_rref(this S&&) {}; // { dg-note {previous declaration} } + static void f_xs_rref() {}; // { dg-error {cannot be overloaded with} } + + void f_xs_crref(this S const&&) {}; // { dg-note {previous declaration} } + static void f_xs_crref() {}; // { dg-error {cannot be overloaded with} } + +// reversed + static void f_sx_v() {}; // { dg-note {previous declaration} } + void f_sx_v(this S) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_ref() {}; // { dg-note {previous declaration} } + void f_sx_ref(this S&) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_cref() {}; // { dg-note {previous declaration} } + void f_sx_cref(this S const&) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_rref() {}; // { dg-note {previous declaration} } + void f_sx_rref(this S&&) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_crref() {}; // { dg-note {previous declaration} } + void f_sx_crref(this S const&&) {}; // { dg-error {cannot be overloaded with} } + +// one additional param + void f_xs_v_int(this S, int) {}; // { dg-note {previous declaration} } + static void f_xs_v_int(int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_ref_int(this S&, int) {}; // { dg-note {previous declaration} } + static void f_xs_ref_int(int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_cref_int(this S const&, int) {}; // { dg-note {previous declaration} } + static void f_xs_cref_int(int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_rref_int(this S&&, int) {}; // { dg-note {previous declaration} } + static void f_xs_rref_int(int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_crref_int(this S const&&, int) {}; // { dg-note {previous declaration} } + static void f_xs_crref_int(int) {}; // { dg-error {cannot be overloaded with} } + +// reversed + static void f_sx_v_int(int) {}; // { dg-note {previous declaration} } + void f_sx_v_int(this S, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_ref_int(int) {}; // { dg-note {previous declaration} } + void f_sx_ref_int(this S&, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_cref_int(int) {}; // { dg-note {previous declaration} } + void f_sx_cref_int(this S const&, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_rref_int(int) {}; // { dg-note {previous declaration} } + void f_sx_rref_int(this S&&, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_crref_int(int) {}; // { dg-note {previous declaration} } + void f_sx_crref_int(this S const&&, int) {}; // { dg-error {cannot be overloaded with} } + +// two additional params + void f_xs_v_int2(this S, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_v_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_ref_int2(this S&, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_ref_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_cref_int2(this S const&, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_cref_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_rref_int2(this S&&, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_rref_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_crref_int2(this S const&&, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_crref_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + +// reversed + static void f_sx_v_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_v_int2(this S, int, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_ref_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_ref_int2(this S&, int, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_cref_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_cref_int2(this S const&, int, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_rref_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_rref_int2(this S&&, int, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_crref_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_crref_int2(this S const&&, int, int) {}; // { dg-error {cannot be overloaded with} } +}; + +// unrelated explicit object parameter type + +struct A {}; + +struct S1 +{ +// no additional params + void f_xs_v(this A) {}; // { dg-note {previous declaration} } + static void f_xs_v() {}; // { dg-error {cannot be overloaded with} } + + void f_xs_ref(this A&) {}; // { dg-note {previous declaration} } + static void f_xs_ref() {}; // { dg-error {cannot be overloaded with} } + + void f_xs_cref(this A const&) {}; // { dg-note {previous declaration} } + static void f_xs_cref() {}; // { dg-error {cannot be overloaded with} } + + void f_xs_rref(this A&&) {}; // { dg-note {previous declaration} } + static void f_xs_rref() {}; // { dg-error {cannot be overloaded with} } + + void f_xs_crref(this A const&&) {}; // { dg-note {previous declaration} } + static void f_xs_crref() {}; // { dg-error {cannot be overloaded with} } + +// reversed + static void f_sx_v() {}; // { dg-note {previous declaration} } + void f_sx_v(this A) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_ref() {}; // { dg-note {previous declaration} } + void f_sx_ref(this A&) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_cref() {}; // { dg-note {previous declaration} } + void f_sx_cref(this A const&) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_rref() {}; // { dg-note {previous declaration} } + void f_sx_rref(this A&&) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_crref() {}; // { dg-note {previous declaration} } + void f_sx_crref(this A const&&) {}; // { dg-error {cannot be overloaded with} } + +// one additional param + void f_xs_v_int(this A, int) {}; // { dg-note {previous declaration} } + static void f_xs_v_int(int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_ref_int(this A&, int) {}; // { dg-note {previous declaration} } + static void f_xs_ref_int(int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_cref_int(this A const&, int) {}; // { dg-note {previous declaration} } + static void f_xs_cref_int(int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_rref_int(this A&&, int) {}; // { dg-note {previous declaration} } + static void f_xs_rref_int(int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_crref_int(this A const&&, int) {}; // { dg-note {previous declaration} } + static void f_xs_crref_int(int) {}; // { dg-error {cannot be overloaded with} } + +// reversed + static void f_sx_v_int(int) {}; // { dg-note {previous declaration} } + void f_sx_v_int(this A, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_ref_int(int) {}; // { dg-note {previous declaration} } + void f_sx_ref_int(this A&, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_cref_int(int) {}; // { dg-note {previous declaration} } + void f_sx_cref_int(this A const&, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_rref_int(int) {}; // { dg-note {previous declaration} } + void f_sx_rref_int(this A&&, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_crref_int(int) {}; // { dg-note {previous declaration} } + void f_sx_crref_int(this A const&&, int) {}; // { dg-error {cannot be overloaded with} } + +// two additional params + void f_xs_v_int2(this A, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_v_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_ref_int2(this A&, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_ref_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_cref_int2(this A const&, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_cref_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_rref_int2(this A&&, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_rref_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_crref_int2(this A const&&, int, int) {}; // { dg-note {previous declaration} } + static void f_xs_crref_int2(int, int) {}; // { dg-error {cannot be overloaded with} } + +// reversed + static void f_sx_v_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_v_int2(this A, int, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_ref_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_ref_int2(this A&, int, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_cref_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_cref_int2(this A const&, int, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_rref_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_rref_int2(this A&&, int, int) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_crref_int2(int, int) {}; // { dg-note {previous declaration} } + void f_sx_crref_int2(this A const&&, int, int) {}; // { dg-error {cannot be overloaded with} } +}; + diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl4.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl4.C new file mode 100644 index 00000000000..2e1b12b874f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-redecl4.C @@ -0,0 +1,97 @@ +// P0847R7 +// { dg-do compile { target c++23 } } + +// redeclarations with equivalent constraints + +template +concept Constrain = true; + + +struct S { +// xobj/static + void f_xs_v(this S, Constrain auto) {}; // { dg-note {previous declaration} } + static void f_xs_v(Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_ref(this S&, Constrain auto) {}; // { dg-note {previous declaration} } + static void f_xs_ref(Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_cref(this S const&, Constrain auto) {}; // { dg-note {previous declaration} } + static void f_xs_cref(Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_rref(this S&&, Constrain auto) {}; // { dg-note {previous declaration} } + static void f_xs_rref(Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_xs_crref(this S const&&, Constrain auto) {}; // { dg-note {previous declaration} } + static void f_xs_crref(Constrain auto) {}; // { dg-error {cannot be overloaded with} } + +// static/xobj + static void f_sx_v(Constrain auto) {}; // { dg-note {previous declaration} } + void f_sx_v(this S, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_ref(Constrain auto) {}; // { dg-note {previous declaration} } + void f_sx_ref(this S&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_cref(Constrain auto) {}; // { dg-note {previous declaration} } + void f_sx_cref(this S const&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_rref(Constrain auto) {}; // { dg-note {previous declaration} } + void f_sx_rref(this S&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + static void f_sx_crref(Constrain auto) {}; // { dg-note {previous declaration} } + void f_sx_crref(this S const&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + // iobj/xobj + void f_ix_lref(Constrain auto) {}; // { dg-note {previous declaration} } + void f_ix_lref(this S&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_ix_rref(Constrain auto) {}; // { dg-note {previous declaration} } + void f_ix_rref(this S&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_ix_const_lref(Constrain auto) const {}; // { dg-note {previous declaration} } + void f_ix_const_lref(this S const&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_ix_const_rref(Constrain auto) const {}; // { dg-note {previous declaration} } + void f_ix_const_rref(this S const&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + // xobj/iobj + void f_xi_lref(this S&, Constrain auto) {}; // { dg-note {previous declaration} } + void f_xi_lref(Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_xi_rref(this S&&, Constrain auto) {}; // { dg-note {previous declaration} } + void f_xi_rref(Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_xi_const_lref(this S const&, Constrain auto) {}; // { dg-note {previous declaration} } + void f_xi_const_lref(Constrain auto) const {}; // { dg-error {cannot be overloaded with} } + + void f_xi_const_rref(this S const&&, Constrain auto) {}; // { dg-note {previous declaration} } + void f_xi_const_rref(Constrain auto) const {}; // { dg-error {cannot be overloaded with} } + + // with ref qualifier + + // iobj/xobj + void f_ix_lref_refqual(Constrain auto) & {}; // { dg-note {previous declaration} } + void f_ix_lref_refqual(this S&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_ix_rref_refqual(Constrain auto) && {}; // { dg-note {previous declaration} } + void f_ix_rref_refqual(this S&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_ix_const_lref_refqual(Constrain auto) const& {}; // { dg-note {previous declaration} } + void f_ix_const_lref_refqual(this S const&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + void f_ix_const_rref_refqual(Constrain auto) const&& {}; // { dg-note {previous declaration} } + void f_ix_const_rref_refqual(this S const&&, Constrain auto) {}; // { dg-error {cannot be overloaded with} } + + // xobj/iobj + void f_xi_lref_refqual(this S&, Constrain auto) {}; // { dg-note {previous declaration} } + void f_xi_lref_refqual(Constrain auto) & {}; // { dg-error {cannot be overloaded with} } + + void f_xi_rref_refqual(this S&&, Constrain auto) {}; // { dg-note {previous declaration} } + void f_xi_rref_refqual(Constrain auto) && {}; // { dg-error {cannot be overloaded with} } + + void f_xi_const_lref_refqual(this S const&, Constrain auto) {}; // { dg-note {previous declaration} } + void f_xi_const_lref_refqual(Constrain auto) const& {}; // { dg-error {cannot be overloaded with} } + + void f_xi_const_rref_refqual(this S const&&, Constrain auto) {}; // { dg-note {previous declaration} } + void f_xi_const_rref_refqual(Constrain auto) const&& {}; // { dg-error {cannot be overloaded with} } +}; + -- 2.11.4.GIT