c++: constrained hidden friends [PR109751]

r13-4035 avoided a problem with overloading of constrained hidden friends by
checking satisfaction, but checking satisfaction early is inconsistent with
the usual late checking and can lead to hard errors, so let's not do that
after all.

We were wrongly treating the different instantiations of the same friend
template as the same function because maybe_substitute_reqs_for was failing
to actually substitute in the case of a non-template friend.  But we don't
actually need to do the substitution anyway, because [temp.friend] says that
such a friend can't be the same as any other declaration.

After fixing that, instead of a redefinition error we got an ambiguous
overload error, fixed by allowing constrained hidden friends to coexist
until overload resolution, at which point they probably won't be in the same
ADL overload set anyway.

And we avoid mangling collisions by following the proposed mangling for
these friends as a member function with an extra 'F' before the name.  I
demangle this by just adding [friend] to the name of the function because
it's not feasible to reconstruct the actual scope of the function since the
mangling ABI doesn't distinguish between class and namespace scopes.

	PR c++/109751

gcc/cp/ChangeLog:

	* cp-tree.h (member_like_constrained_friend_p): Declare.
	* decl.cc (member_like_constrained_friend_p): New.
	(function_requirements_equivalent_p): Check it.
	(duplicate_decls): Check it.
	(grokfndecl): Check friend template constraints.
	* mangle.cc (decl_mangling_context): Check it.
	(write_unqualified_name): Check it.
	* pt.cc (uses_outer_template_parms_in_constraints): Fix for friends.
	(tsubst_friend_function): Don't check satisfaction.

include/ChangeLog:

	* demangle.h (enum demangle_component_type): Add
	DEMANGLE_COMPONENT_FRIEND.

libiberty/ChangeLog:

	* cp-demangle.c (d_make_comp): Handle DEMANGLE_COMPONENT_FRIEND.
	(d_count_templates_scopes): Likewise.
	(d_print_comp_inner): Likewise.
	(d_unqualified_name): Handle member-like friend mangling.
	* testsuite/demangle-expected: Add test.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-friend11.C: Now works.  Add template.
	* g++.dg/cpp2a/concepts-friend15.C: New test.
This commit is contained in:
Jason Merrill 2023-08-17 11:36:23 -04:00
parent 3571cc9351
commit 810bcc0015
10 changed files with 145 additions and 16 deletions

View File

@ -6859,6 +6859,7 @@ extern void note_break_stmt (void);
extern bool note_iteration_stmt_body_start (void);
extern void note_iteration_stmt_body_end (bool);
extern void determine_local_discriminator (tree);
extern bool member_like_constrained_friend_p (tree);
extern bool fns_correspond (tree, tree);
extern int decls_match (tree, tree, bool = true);
extern bool maybe_version_functions (tree, tree, bool);
@ -7385,7 +7386,7 @@ extern tree lookup_template_function (tree, tree);
extern tree lookup_template_variable (tree, tree, tsubst_flags_t);
extern bool uses_template_parms (tree);
extern bool uses_template_parms_level (tree, int);
extern bool uses_outer_template_parms_in_constraints (tree);
extern bool uses_outer_template_parms_in_constraints (tree, tree = NULL_TREE);
extern bool need_generic_capture (void);
extern tree instantiate_class_template (tree);
extern tree instantiate_template (tree, tree, tsubst_flags_t);

View File

@ -951,6 +951,30 @@ determine_local_discriminator (tree decl)
}
/* True if DECL is a constrained hidden friend as per [temp.friend]/9:
A non-template friend declaration with a requires-clause shall be a
definition. A friend function template with a constraint that depends on a
template parameter from an enclosing template shall be a definition. Such a
constrained friend function or function template declaration does not
declare the same function or function template as a declaration in any other
scope.
The ABI calls this a "member-like constrained friend" and mangles it like a
member function to avoid collisions. */
bool
member_like_constrained_friend_p (tree decl)
{
return (TREE_CODE (decl) == FUNCTION_DECL
&& DECL_UNIQUE_FRIEND_P (decl)
&& DECL_FRIEND_CONTEXT (decl)
&& get_constraints (decl)
&& (!DECL_TEMPLATE_INFO (decl)
|| !PRIMARY_TEMPLATE_P (DECL_TI_TEMPLATE (decl))
|| (uses_outer_template_parms_in_constraints
(most_general_template (decl)))));
}
/* Returns true if functions FN1 and FN2 have equivalent trailing
requires clauses. */
@ -968,6 +992,13 @@ function_requirements_equivalent_p (tree newfn, tree oldfn)
return cp_tree_equal (req1, req2);
}
/* [temp.friend]/9 "Such a constrained friend function does not declare the
same function as a declaration in any other scope." So no need to
actually compare the requirements. */
if (member_like_constrained_friend_p (newfn)
|| member_like_constrained_friend_p (oldfn))
return false;
/* Compare only trailing requirements. */
tree reqs1 = get_trailing_function_requirements (newfn);
tree reqs2 = get_trailing_function_requirements (oldfn);
@ -1936,6 +1967,10 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
are not ambiguous. */
else if ((!DECL_FUNCTION_VERSIONED (newdecl)
&& !DECL_FUNCTION_VERSIONED (olddecl))
/* Let constrained hidden friends coexist for now, we'll
check satisfaction later. */
&& !member_like_constrained_friend_p (newdecl)
&& !member_like_constrained_friend_p (olddecl)
// The functions have the same parameter types.
&& compparms (TYPE_ARG_TYPES (TREE_TYPE (newdecl)),
TYPE_ARG_TYPES (TREE_TYPE (olddecl)))
@ -10305,16 +10340,28 @@ grokfndecl (tree ctype,
ci = NULL_TREE;
}
/* C++20 CA378: Remove non-templated constrained functions. */
/* [temp.friend]/9 A non-template friend declaration with a
requires-clause shall be a definition. A friend function template with
a constraint that depends on a template parameter from an enclosing
template shall be a definition. */
if (ci
&& (block_local
|| (!flag_concepts_ts
&& (!processing_template_decl
|| (friendp && !memtmpl && !funcdef_flag)))))
{
error_at (location, "constraints on a non-templated function");
if (!friendp || !processing_template_decl)
error_at (location, "constraints on a non-templated function");
else
error_at (location, "constrained non-template friend declaration"
" must be a definition");
ci = NULL_TREE;
}
set_constraints (decl, ci);
if (ci && friendp && memtmpl && !funcdef_flag
&& uses_outer_template_parms_in_constraints (decl, ctx))
error_at (location, "friend function template with constraints that "
"depend on outer template parameters must be a definition");
}
if (TREE_CODE (type) == METHOD_TYPE)

View File

@ -963,6 +963,9 @@ decl_mangling_context (tree decl)
tcontext = CP_DECL_CONTEXT (decl);
if (member_like_constrained_friend_p (decl))
tcontext = DECL_FRIEND_CONTEXT (decl);
/* Ignore the artificial declare reduction functions. */
if (tcontext
&& TREE_CODE (tcontext) == FUNCTION_DECL
@ -1419,6 +1422,7 @@ anon_aggr_naming_decl (tree type)
::= [<module-name>] <source-name>
::= [<module-name>] <unnamed-type-name>
::= <local-source-name>
::= F <source-name> # member-like constrained friend
<local-source-name> ::= L <source-name> <discriminator> */
@ -1476,6 +1480,12 @@ write_unqualified_name (tree decl)
else if (DECL_DECLARES_FUNCTION_P (decl))
{
found = true;
/* A constrained hidden friend is mangled like a member function, with
the name prefixed by 'F'. */
if (member_like_constrained_friend_p (decl))
write_char ('F');
if (DECL_CONSTRUCTOR_P (decl))
write_special_name_constructor (decl);
else if (DECL_DESTRUCTOR_P (decl))

View File

@ -11049,14 +11049,21 @@ uses_outer_template_parms (tree decl)
from its enclosing scope. */
bool
uses_outer_template_parms_in_constraints (tree decl)
uses_outer_template_parms_in_constraints (tree decl, tree ctx/*=NULL_TREE*/)
{
tree ci = get_constraints (decl);
if (ci)
ci = CI_ASSOCIATED_CONSTRAINTS (ci);
if (!ci)
return false;
int depth = template_class_depth (CP_DECL_CONTEXT (decl));
if (!ctx)
{
if (tree fc = DECL_FRIEND_CONTEXT (decl))
ctx = fc;
else
ctx = CP_DECL_CONTEXT (decl);
}
int depth = template_class_depth (ctx);
if (depth == 0)
return false;
return for_each_template_parm (ci, template_parm_outer_level,
@ -11393,9 +11400,6 @@ tsubst_friend_function (tree decl, tree args)
not_tmpl = DECL_TEMPLATE_RESULT (new_friend);
new_friend_result_template_info = DECL_TEMPLATE_INFO (not_tmpl);
}
else if (!constraints_satisfied_p (new_friend))
/* Only define a constrained hidden friend when satisfied. */
return error_mark_node;
/* Inside pushdecl_namespace_level, we will push into the
current namespace. However, the friend function should go

View File

@ -1,21 +1,29 @@
// CWG2596
// { dg-do compile { target c++20 } }
// { dg-additional-options -fno-implicit-constexpr }
struct Base {};
int foo(Base&) { return 0; } // #0
template<int N>
struct S : Base {
friend int foo(Base&) requires (N == 1) { return 1; } // #1
// friend int foo(Base&) requires (N == 2) { return 3; } // #2
friend int foo(Base&) requires (N == 2) { return 3; } // #2
template <class T>
friend int bar(Base&) requires (N == 1) { return 1; }
template <class T>
friend int bar(Base&) requires (N == 2) { return 3; }
};
S<1> s1;
S<2> s2; // OK, no conflict between #1 and #0
int x = foo(s1); // { dg-error "ambiguous" }
int y = foo(s2); // OK, selects #0
S<2> s2; // OK, no conflict between #1 and #2
// ??? currently the foos all mangle the same, so comment out #2
// and only test that #1 isn't multiply defined and overloads with #0.
// The 2596 example does not include #0 and expects both calls to work.
// { dg-final { scan-assembler "_ZN1SILi1EEF3fooER4Base" } }
int x = foo(s1); // OK, selects #1
// { dg-final { scan-assembler "_ZN1SILi2EEF3fooER4Base" } }
int y = foo(s2); // OK, selects #2
// { dg-final { scan-assembler "_ZN1SILi1EEF3barIiEEiR4Base" } }
int x2 = bar<int>(s1); // OK, selects #1
// { dg-final { scan-assembler "_ZN1SILi2EEF3barIiEEiR4Base" } }
int y2 = bar<int>(s2); // OK, selects #2

View File

@ -0,0 +1,15 @@
// CWG2596
// { dg-do compile { target c++20 } }
struct Base {};
template<int N>
struct S : Base {
friend int foo(Base&) requires (N == 1); // { dg-error "must be a definition" }
friend int foo(Base&) requires (N == 2); // { dg-error "must be a definition" }
template <class T>
friend int bar(Base&) requires (N == 1); // { dg-error "must be a definition" }
template <class T>
friend int bar(Base&) requires (N == 2); // { dg-error "must be a definition" }
};

View File

@ -0,0 +1,22 @@
// PR c++/109751
// { dg-do compile { target c++20 } }
template<typename _Tp> concept cmpeq
= requires(_Tp __t, _Tp __u) { { __u != __t } ; };
template<typename D>
struct iterator_interface
{
friend constexpr bool operator>=(D lhs, D rhs)
requires cmpeq<D> { return true; }
};
template<typename T>
struct iterator : iterator_interface<iterator<T>>
{
bool operator==(iterator) const;
iterator &operator++();
iterator &operator++(int);
};
static_assert(cmpeq<iterator<int>>);

View File

@ -448,6 +448,8 @@ enum demangle_component_type
DEMANGLE_COMPONENT_TRANSACTION_SAFE,
/* A cloned function. */
DEMANGLE_COMPONENT_CLONE,
/* A member-like friend function. */
DEMANGLE_COMPONENT_FRIEND,
DEMANGLE_COMPONENT_NOEXCEPT,
DEMANGLE_COMPONENT_THROW_SPEC,

View File

@ -1036,6 +1036,7 @@ d_make_comp (struct d_info *di, enum demangle_component_type type,
case DEMANGLE_COMPONENT_TEMPLATE_NON_TYPE_PARM:
case DEMANGLE_COMPONENT_TEMPLATE_TEMPLATE_PARM:
case DEMANGLE_COMPONENT_TEMPLATE_PACK_PARM:
case DEMANGLE_COMPONENT_FRIEND:
if (left == NULL)
return NULL;
break;
@ -1681,6 +1682,7 @@ d_maybe_module_name (struct d_info *di, struct demangle_component **name)
/* <unqualified-name> ::= [<module-name>] <operator-name> [<abi-tags>]
::= [<module-name>] <ctor-dtor-name> [<abi-tags>]
::= [<module-name>] <source-name> [<abi-tags>]
::= [<module-name>] F <source-name> [<abi-tags>]
::= [<module-name>] <local-source-name> [<abi-tags>]
::= [<module-name>] DC <source-name>+ E [<abi-tags>]
<local-source-name> ::= L <source-name> <discriminator> [<abi-tags>]
@ -1692,11 +1694,18 @@ d_unqualified_name (struct d_info *di, struct demangle_component *scope,
{
struct demangle_component *ret;
char peek;
int member_like_friend = 0;
if (!d_maybe_module_name (di, &module))
return NULL;
peek = d_peek_char (di);
if (peek == 'F')
{
member_like_friend = 1;
d_advance (di, 1);
peek = d_peek_char (di);
}
if (IS_DIGIT (peek))
ret = d_source_name (di);
else if (IS_LOWER (peek))
@ -1773,6 +1782,8 @@ d_unqualified_name (struct d_info *di, struct demangle_component *scope,
ret = d_make_comp (di, DEMANGLE_COMPONENT_MODULE_ENTITY, ret, module);
if (d_peek_char (di) == 'B')
ret = d_abi_tags (di, ret);
if (member_like_friend)
ret = d_make_comp (di, DEMANGLE_COMPONENT_FRIEND, ret, NULL);
if (scope)
ret = d_make_comp (di, DEMANGLE_COMPONENT_QUAL_NAME, scope, ret);
@ -4459,6 +4470,7 @@ d_count_templates_scopes (struct d_print_info *dpi,
case DEMANGLE_COMPONENT_GLOBAL_CONSTRUCTORS:
case DEMANGLE_COMPONENT_GLOBAL_DESTRUCTORS:
case DEMANGLE_COMPONENT_MODULE_ENTITY:
case DEMANGLE_COMPONENT_FRIEND:
d_count_templates_scopes (dpi, d_left (dc));
break;
@ -6197,6 +6209,11 @@ d_print_comp_inner (struct d_print_info *dpi, int options,
d_append_char (dpi, ']');
return;
case DEMANGLE_COMPONENT_FRIEND:
d_print_comp (dpi, options, d_left (dc));
d_append_string (dpi, "[friend]");
return;
case DEMANGLE_COMPONENT_TEMPLATE_HEAD:
{
d_append_char (dpi, '<');

View File

@ -1689,3 +1689,6 @@ X::operator Z<int><int>()::y
_ZZN1XIfEcv1ZIT_EIiEEvE1y
X<float>::operator Z<int><int>()::y
_ZN1SILi1EEF3barIiEEiR4Base
int S<1>::bar[friend]<int>(Base&)