c++: refine CWG 2369 satisfaction vs non-dep convs [PR99599]

As described in detail in the PR, the CWG 2369 resolution has the
surprising consequence of introducing constraint recursion in seemingly
valid and innocent code.

This patch attempts to fix this surpising behavior for the majority of
problematic cases.  Rather than checking satisfaction before _all_
non-dependent conversions, as specified by the CWG resolution, this patch
makes us first check "safe" non-dependent conversions, then satisfaction,
then followed by other non-dependent conversions.  A conversion is
considered "safe" if computing it is guaranteed to not induce template
instantiation, and we conservatively determine this by checking for
user-declared constructors (resp. conversion functions) in the parm
(resp. arg) class type, roughly.

	PR c++/99599

gcc/cp/ChangeLog:

	* pt.cc (check_non_deducible_conversions): Add bool parameter
	passed down to check_non_deducible_conversion.
	(fn_type_unification): Call check_non_deducible_conversions
	an extra time before satisfaction with noninst_only_p=true.
	(conversion_may_instantiate_p): Define.
	(check_non_deducible_conversion): Add bool parameter controlling
	whether to compute only conversions that are guaranteed to
	not induce template instantiation.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-recursive-sat4.C: Make 'Int' non-aggregate
	in order to preserve intent of the testcase.
	* g++.dg/cpp2a/concepts-nondep4.C: New test.
This commit is contained in:
Patrick Palka 2023-09-08 12:02:20 -04:00
parent d8bdc978dc
commit 2154bcd6d4
3 changed files with 100 additions and 5 deletions

View file

@ -151,7 +151,7 @@ static tree get_partial_spec_bindings (tree, tree, tree);
static void tsubst_enum (tree, tree, tree);
static bool check_instantiated_args (tree, tree, tsubst_flags_t);
static int check_non_deducible_conversion (tree, tree, unification_kind_t, int,
struct conversion **, bool);
struct conversion **, bool, bool);
static int maybe_adjust_types_for_deduction (tree, unification_kind_t,
tree*, tree*, tree);
static int type_unification_real (tree, tree, tree, const tree *,
@ -22321,7 +22321,8 @@ pack_deducible_p (tree parm, tree fn)
static int
check_non_deducible_conversions (tree parms, const tree *args, unsigned nargs,
tree fn, unification_kind_t strict, int flags,
struct conversion **convs, bool explain_p)
struct conversion **convs, bool explain_p,
bool noninst_only_p)
{
/* Non-constructor methods need to leave a conversion for 'this', which
isn't included in nargs here. */
@ -22357,7 +22358,7 @@ check_non_deducible_conversions (tree parms, const tree *args, unsigned nargs,
int lflags = conv_flags (ia, nargs, fn, arg, flags);
if (check_non_deducible_conversion (parm, arg, strict, lflags,
conv_p, explain_p))
conv_p, explain_p, noninst_only_p))
return 1;
}
@ -22657,6 +22658,16 @@ fn_type_unification (tree fn,
deduced:
/* As a refinement of CWG2369, check first and foremost non-dependent
conversions that we know are not going to induce template instantiation
(PR99599). */
if (strict == DEDUCE_CALL
&& incomplete
&& check_non_deducible_conversions (parms, args, nargs, fn, strict, flags,
convs, explain_p,
/*noninst_only_p=*/true))
goto fail;
/* CWG2369: Check satisfaction before non-deducible conversions. */
if (!constraints_satisfied_p (fn, targs))
{
@ -22670,7 +22681,8 @@ fn_type_unification (tree fn,
as the standard says that we substitute explicit args immediately. */
if (incomplete
&& check_non_deducible_conversions (parms, args, nargs, fn, strict, flags,
convs, explain_p))
convs, explain_p,
/*noninst_only_p=*/false))
goto fail;
/* All is well so far. Now, check:
@ -22897,6 +22909,59 @@ maybe_adjust_types_for_deduction (tree tparms,
return result;
}
/* Return true if computing a conversion from FROM to TO might induce template
instantiation. Conversely, if this predicate returns false then computing
the conversion definitely won't induce template instantiation. */
static bool
conversion_may_instantiate_p (tree to, tree from)
{
to = non_reference (to);
from = non_reference (from);
bool ptr_conv_p = false;
if (TYPE_PTR_P (to)
&& TYPE_PTR_P (from))
{
to = TREE_TYPE (to);
from = TREE_TYPE (from);
ptr_conv_p = true;
}
/* If one of the types is a not-yet-instantiated class template
specialization, then computing the conversion might instantiate
it in order to inspect bases, conversion functions and/or
converting constructors. */
if ((CLASS_TYPE_P (to)
&& !COMPLETE_TYPE_P (to)
&& CLASSTYPE_TEMPLATE_INSTANTIATION (to))
|| (CLASS_TYPE_P (from)
&& !COMPLETE_TYPE_P (from)
&& CLASSTYPE_TEMPLATE_INSTANTIATION (from)))
return true;
/* Converting from one pointer type to another, or between
reference-related types, always yields a standard conversion. */
if (ptr_conv_p || reference_related_p (to, from))
return false;
/* Converting to a non-aggregate class type will consider its
user-declared constructors, which might induce instantiation. */
if (CLASS_TYPE_P (to)
&& CLASSTYPE_NON_AGGREGATE (to))
return true;
/* Similarly, converting from a class type will consider its conversion
functions. */
if (CLASS_TYPE_P (from)
&& TYPE_HAS_CONVERSION (from))
return true;
/* Otherwise, computing this conversion definitely won't induce
template instantiation. */
return false;
}
/* Subroutine of fn_type_unification. PARM is a function parameter of a
template which doesn't contain any deducible template parameters; check if
ARG is a suitable match for it. STRICT, FLAGS and EXPLAIN_P are as in
@ -22905,7 +22970,7 @@ maybe_adjust_types_for_deduction (tree tparms,
static int
check_non_deducible_conversion (tree parm, tree arg, unification_kind_t strict,
int flags, struct conversion **conv_p,
bool explain_p)
bool explain_p, bool noninst_only_p)
{
tree type;
@ -22925,6 +22990,18 @@ check_non_deducible_conversion (tree parm, tree arg, unification_kind_t strict,
}
else if (strict == DEDUCE_CALL)
{
if (conv_p && *conv_p)
{
/* This conversion was already computed earlier (when
computing only non-instantiating conversions). */
gcc_checking_assert (!noninst_only_p);
return unify_success (explain_p);
}
if (noninst_only_p
&& conversion_may_instantiate_p (parm, type))
return unify_success (explain_p);
bool ok = false;
tree conv_arg = TYPE_P (arg) ? NULL_TREE : arg;
if (conv_p)

View file

@ -0,0 +1,16 @@
// PR c++/99599
// { dg-do compile { target c++20 } }
struct foo_tag { };
struct bar_tag { };
template<class T>
concept fooable = requires(T it) { invoke_tag(foo_tag{}, it); };
template<class T> void invoke_tag(foo_tag, T);
template<class T> void invoke_tag(bar_tag, T) requires fooable<T>;
int main() {
invoke_tag(foo_tag{}, 0);
invoke_tag(bar_tag{}, 0);
}

View file

@ -1,3 +1,4 @@
// Verify we diagnose constraint recursion.
// PR c++/96840
// { dg-do compile { target c++20 } }
@ -6,6 +7,7 @@ template <class T, class U> concept C = requires(T t, U u) { t * u; };
// { dg-error "depends on itself" "" { target *-*-* } .-2 }
template <class Rep> struct Int {
Int(); // make the class non-aggregate in light of PR99599 fix
template <class T> requires C<T, Rep> friend void operator*(T, Int) { }
template <class T> requires C<T, Rep> friend void operator*(Int, T) { }
};