Tested x86_64-pc-linux-gnu, applying to trunk. -- 8< --
As discussed, our handling of corresponding object parameters needed to handle the using-declaration case better. And I took the opportunity to share code between the add_method and cand_parms_match uses. This patch specifically doesn't compare reversed parameters, but a follow-up patch will. PR c++/113191 gcc/cp/ChangeLog: * class.cc (xobj_iobj_parameters_correspond): Add context parm. (object_parms_correspond): Factor out of... (add_method): ...here. * method.cc (defaulted_late_check): Use it. * call.cc (class_of_implicit_object): New. (object_parms_correspond): Overload taking two candidates. (cand_parms_match): Use it. (joust): Check reversed before comparing constraints. * cp-tree.h (object_parms_correspond): Declare. gcc/testsuite/ChangeLog: * g++.dg/cpp2a/concepts-memfun4.C: New test. --- gcc/cp/cp-tree.h | 1 + gcc/cp/call.cc | 84 ++++++++---- gcc/cp/class.cc | 121 ++++++++++-------- gcc/cp/method.cc | 16 +-- gcc/testsuite/g++.dg/cpp2a/concepts-memfun4.C | 97 ++++++++++++++ 5 files changed, 228 insertions(+), 91 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-memfun4.C diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index f3f265a3db8..83009fc837c 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -6853,6 +6853,7 @@ extern bool is_empty_base_ref (tree); extern tree build_vtbl_ref (tree, tree); extern tree build_vfn_ref (tree, tree); extern tree get_vtable_decl (tree, int); +extern bool object_parms_correspond (tree, tree, tree); extern bool add_method (tree, tree, bool); extern tree declared_access (tree); extern bool maybe_push_used_methods (tree); diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 191664ee227..6f024b8abc3 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -12685,6 +12685,51 @@ joust_maybe_elide_copy (z_candidate *cand) return false; } +/* Return the class that CAND's implicit object parameter refers to. */ + +static tree +class_of_implicit_object (z_candidate *cand) +{ + if (!DECL_IOBJ_MEMBER_FUNCTION_P (cand->fn)) + return NULL_TREE; + + /* "For conversion functions that are implicit object member functions, + the function is considered to be a member of the class of the implied + object argument for the purpose of defining the type of the implicit + object parameter." */ + if (DECL_CONV_FN_P (cand->fn)) + return TYPE_MAIN_VARIANT (TREE_TYPE (cand->first_arg)); + + /* "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." + + That derived class is reflected in the conversion_path binfo. */ + return BINFO_TYPE (cand->conversion_path); +} + +/* True if candidates C1 and C2 have corresponding object parameters per + [basic.scope.scope]. */ + +static bool +object_parms_correspond (z_candidate *c1, z_candidate *c2) +{ + tree context = class_of_implicit_object (c1); + tree ctx2 = class_of_implicit_object (c2); + if (!ctx2) + /* Leave context as is. */; + else if (!context) + context = ctx2; + else if (context != ctx2) + /* This can't happen for normal function calls, since it means finding + functions in multiple bases which would fail with an ambiguous lookup, + but it can occur with reversed operators. */ + return false; + + return object_parms_correspond (c1->fn, c2->fn, context); +} + /* True if the defining declarations of the two candidates have equivalent parameters. */ @@ -12712,35 +12757,25 @@ 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)); - 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))) + + /* CWG2789 is not adequate, it should specify corresponding object + parameters, not same typed object parameters. */ + else if (!object_parms_correspond (c1, c2)) + return false; + else { - /* Ignore 'this' when comparing the parameters of a static member - function with those of a non-static one. */ - 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. */ + we already returned false. */ + 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); + }; parms1 = skip_parms (fn1, parms1); parms2 = skip_parms (fn2, parms2); } @@ -13104,6 +13139,7 @@ joust (struct z_candidate *cand1, struct z_candidate *cand2, bool warn, if (flag_concepts && DECL_P (cand1->fn) && DECL_P (cand2->fn) && !cand1->template_decl && !cand2->template_decl + && cand1->reversed () == cand2->reversed () && cand_parms_match (cand1, cand2)) { winner = more_constrained (cand1->fn, cand2->fn); diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc index e5e609badf3..3374756fb9a 100644 --- a/gcc/cp/class.cc +++ b/gcc/cp/class.cc @@ -1020,15 +1020,11 @@ 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]. + correspond. CONTEXT is the class that an implicit object parameter + refers to. */ - ??? But it isn't, that's only considered at overload resolution time. - cand_parms_match will probably need to check cand->conversion_path. */ - -bool -xobj_iobj_parameters_correspond (tree fn1, tree fn2) +static bool +xobj_iobj_parameters_correspond (tree fn1, tree fn2, tree context) { gcc_assert (DECL_IOBJ_MEMBER_FUNCTION_P (fn1) || DECL_IOBJ_MEMBER_FUNCTION_P (fn2)); @@ -1042,11 +1038,6 @@ xobj_iobj_parameters_correspond (tree fn1, tree fn2) 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 @@ -1116,7 +1107,7 @@ xobj_iobj_parameters_correspond (tree fn1, tree fn2) check for that case. */ if (!same_type_ignoring_top_level_qualifiers_p - (iobj_param_type, non_reference (xobj_param))) + (context, non_reference (xobj_param))) return false; /* We don't get to bail yet even if we have a by-value xobj parameter, @@ -1214,6 +1205,63 @@ xobj_iobj_parameters_correspond (tree fn1, tree fn2) return true; } +/* True if FN and METHOD have corresponding object parms per + [basic.scope.scope], or if one of them is a static member function (which + are considered to have an object parm that corresponds to any other). + CONTEXT is the class that an implicit object member function is considered + to be a member of for the purpose of this comparison, per + [over.match.funcs]. */ + +bool +object_parms_correspond (tree fn, tree method, tree context) +{ + tree fn_type = TREE_TYPE (fn); + tree method_type = TREE_TYPE (method); + + /* 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_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. */ + if ((FUNCTION_REF_QUALIFIED (fn_type) + == FUNCTION_REF_QUALIFIED (method_type)) + && (type_memfn_quals (fn_type) != type_memfn_quals (method_type) + || type_memfn_rqual (fn_type) != type_memfn_rqual (method_type))) + return false; + return true; + } + /* Treat a static member function as corresponding to any object parm. */ + else if (DECL_STATIC_FUNCTION_P (fn) || DECL_STATIC_FUNCTION_P (method)) + return true; + /* Handle special correspondence rules for xobj vs xobj and xobj vs iobj + member function declarations. + We don't worry about static member functions here. */ + 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)) + return false; + } + else if (DECL_XOBJ_MEMBER_FUNCTION_P (fn) + || DECL_XOBJ_MEMBER_FUNCTION_P (method)) + return xobj_iobj_parameters_correspond (fn, method, context); + else + gcc_unreachable (); + + 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. */ @@ -1268,51 +1316,12 @@ add_method (tree type, tree method, bool via_using) functions in the derived class override and/or hide member functions with the same name and parameter types in a base class (rather than conflicting). */ + if (!object_parms_correspond (fn, method, type)) + continue; + tree fn_type = TREE_TYPE (fn); tree method_type = TREE_TYPE (method); - /* 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_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) - == FUNCTION_REF_QUALIFIED (method_type)) - && (type_memfn_quals (fn_type) != type_memfn_quals (method_type) - || 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/method.cc b/gcc/cp/method.cc index 6a9f03eb8f3..d49e5a565e8 100644 --- a/gcc/cp/method.cc +++ b/gcc/cp/method.cc @@ -3395,24 +3395,18 @@ defaulted_late_check (tree fn) { tree fn_obj_ref_type = TREE_VALUE (fn_parms); /* We can't default xobj operators with an xobj parameter that is not - an lvalue reference. */ + an lvalue reference, even if it would correspond. */ if (!TYPE_REF_P (fn_obj_ref_type) - || TYPE_REF_IS_RVALUE (fn_obj_ref_type)) - return false; - /* If implicit_fn's object parameter is not a pointer, something is not - right. */ - gcc_assert (TYPE_PTR_P (TREE_VALUE (implicit_fn_parms))); - /* Strip the reference/pointer off each object parameter before - comparing them. */ - if (!same_type_p (TREE_TYPE (fn_obj_ref_type), - TREE_TYPE (TREE_VALUE (implicit_fn_parms)))) + || TYPE_REF_IS_RVALUE (fn_obj_ref_type) + || !object_parms_correspond (fn, implicit_fn, + DECL_CONTEXT (implicit_fn))) return false; /* We just compared the object parameters, skip over them before passing to compparms. */ fn_parms = TREE_CHAIN (fn_parms); implicit_fn_parms = TREE_CHAIN (implicit_fn_parms); } - return compparms(fn_parms, implicit_fn_parms); + return compparms (fn_parms, implicit_fn_parms); }; if (!same_type_p (TREE_TYPE (TREE_TYPE (fn)), diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-memfun4.C b/gcc/testsuite/g++.dg/cpp2a/concepts-memfun4.C new file mode 100644 index 00000000000..2fa661dbe96 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-memfun4.C @@ -0,0 +1,97 @@ +// PR c++/113191 +// { dg-do compile { target c++23 } } + +template<typename> struct S; + +template<typename T = void> +struct B { + constexpr int f() const requires true { return 5; } + constexpr operator int () const requires true { return 5; } + constexpr int g(this S<T>&&) requires true { return 5; } + constexpr int h() requires true { return 5; } +}; + +template<typename = void> +struct S : B<> { + using B::f; + using B::g; + using B::h; + constexpr int f() const { return 10; } + constexpr operator int () const { return 10; } + constexpr int g() { return 10; } + constexpr int h(this S&&) { return 10; } +}; + +// implicit object parms match, B::f is more constrained +static_assert(S<>{}.f() == 5); +static_assert(S<>{}.g() == 5); +static_assert(S<>{}.h() == 5); + +template <typename = void> +struct C { + constexpr int f() const { return 15; } + constexpr operator int () const { return 15; } +}; + +template <typename = void> +struct S2: B<>, C<> { }; + +// implicit object parms for conversion functions are all considered to be from +// the class of the object argument +static_assert(S2<>{} == 5); + +// ambiguous lookup, so we never actually compare the candidates +// if we did, implicit object parms don't match due to different classes +// so constraints aren't considered and it would still be ambiguous +static_assert(S2<>{}.f() == 5); // { dg-error "ambiguous" } + +template <typename = void> +struct S3 : B<> { + using B::f; + constexpr int f() volatile { return 10; } +}; + +// implicit object parms don't match due to different cv-quals +static_assert(S3<>{}.f() == 5); // { dg-error "ambiguous" } + +template <typename = void> +struct S4 : B<> { + using B::f; + constexpr int f() const & { return 10; } +}; + +// no ref-qual matches any ref-qual +static_assert(S4<>{}.f() == 5); + +template <typename = void> +struct C2 { + constexpr operator int () volatile { return 15; } +}; + +template <typename = void> +struct S5: B<>, C2<> { }; + +// implicit object parms don't match due to different cv-quals +static_assert(S5<>{} == 5); // { dg-error "ambiguous" } + +namespace N1 { + template <class = void> struct B; + + template <class = void> + struct A { + constexpr bool operator==(B<>&) { return true; } + }; + + template <class> + struct B { + constexpr bool operator==(A<>&) requires true { return false; } + }; + + A<> a; + B<> b; + // when comparing the A op== to the reversed B op==, we don't compare + // constraints and so fall through to the tiebreaker that chooses the + // non-reversed candidate. + // ??? shouldn't we compare constraints? + static_assert (a == b); +} base-commit: 9bac1d7839f129f93f159c27adaf472ee3ab23a2 -- 2.39.3