c++: corresponding object parms [PR113191]
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.
This commit is contained in:
parent
9bac1d7839
commit
61b493f17e
5 changed files with 228 additions and 91 deletions
|
@ -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);
|
||||
|
|
121
gcc/cp/class.cc
121
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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)),
|
||||
|
|
97
gcc/testsuite/g++.dg/cpp2a/concepts-memfun4.C
Normal file
97
gcc/testsuite/g++.dg/cpp2a/concepts-memfun4.C
Normal file
|
@ -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);
|
||||
}
|
Loading…
Add table
Reference in a new issue