diff mbox series

[RFH] c++: Implement C++26 P2963R3 - Ordering of constraints involving fold expressions [PR115746]

Message ID ZqPSwdl4jwvfvmQc@tucnak
State New
Headers show
Series [RFH] c++: Implement C++26 P2963R3 - Ordering of constraints involving fold expressions [PR115746] | expand

Commit Message

Jakub Jelinek July 26, 2024, 4:45 p.m. UTC
Hi!

I've tried to implement the C++26 fold expanded constraints paper but ran
into issues (see below).  Would appreciate some guidance/help, I'm afraid
I'm stuck.

The patch introduces a FOLD_CONSTR tree to represent fold expanded
constraints, normalizes for C++26 some {U,BI}NARY_{LEFT,RIGHT}_FOLD_EXPR
into those (+ CONJ_CONSTR or DISJ_CONSTR for the binary ones), attempts
to handle their satisfaction (that is the unresolved issue) and handle it
in the subsumption checking code.

Most of the newly added tests pass, compared to what clang++ trunk (which
claims to implement this paper) there are some differences:
static_assert (bar <int> ()); in cpp26/fold-constr5.C is accepted
by clang in C++26 mode and rejected by GCC.  I believe GCC is right,
C<typename U::type> && ... && C<typename T::type>
where U is an empty pack should be normalized to
(C<typename U::type> /\ ...) /\ C<typename T::type>
so I think (C<typename U::type> /\ ...) is satisfied but
C<typename T::type> should not as int::type is invalid, so the overall
result should be not satisfied.
Another difference is
static_assert (U<int>::bar<int> ()); in cpp26/fold-constr6.C, which is
rejected by clang in C++26 mode and accepted by GCC.
This is for
  template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
  static constexpr bool bar () { return false; }
  template <class... V> requires ((C2<V> && ...) && ... && C2<T>)
  static constexpr bool bar () { return true; }
where both T and V are packs, I believe the first should be normalized to
(C1<T> /\ ...) && (C1<V> /\ ...)
and the second to
(C2<V> /\ ...) && (C2<T> /\ ...)
where C2<int> subsumes C1<int>.  clang++ accepts when it is written in
the other order, so when normalized to
(C2<T> /\ ...) && (C2<V> /\ ...)
and in that case GCC and clang agree that the latter subsumes the former.
C2<V> /\ ... subsumes C1<V> /\ ...
and
C2<T> /\ ... subsumes C1<T> /\ ...
and
C2<T> /\ ... does not subsume C1<V> /\ ...
and
C2<V> /\ ... does not subsume C1<T> /\ ...
but overall I believe in either order the result is subsumes.
Another difference is in the cpp26/fold-constr10.C testcase, which is
essentially PR116106 and Patrick claims that unused template parameters in
the mapping aren't substituted into.
Also, the new code affects not just explicitly written fold expressions
in the constraints, but also implicitly created ones (say for
template <Concept... T> and similar), but here the GCC patch agrees with
clang trunk.

Anyway, the way I've attempted to implement satisfy_fold is through updating
the args vector or vectors (COW) to change parameter pack into parameter
pack's element which is currently checked for satisfaction.
That works most of the time, when all the occurrences of the template
parameter(s) recorded in PACK_EXPANSION_PARAMETER_PACKS (FOLD_EXPR_PACK (t))
of the original *_FOLD_EXPR are to be replaced by the pack element, which
should be fine for any such occurences unless they appear in another
expansion pack with the same template parameter.
The instantiation of those nested {TYPE,EXPR}_PACK_EXPANSION need to be able
to access the original whole pack rather than just one of its elements.
And some atomic constraints e.g. can refer to both; I've tried to
write this in cpp26/fold-constr7.C testcase (which is the only one
from newly added that FAILs with C++26):
template <class... T> requires ((((sizeof (T) + ...) < 8 * sizeof (int)) && C2<T>) && ...)
constexpr bool foo (T...) { return true; };
here actually the ((sizeof (T) + ...) < 8 * sizeof (int)) atomic constraint
only needs the whole T pack and C2<T> atomic constraint only needs the T's
pack element, but guess it can be also changed so that one atomic constraint
needs both whole pack and pack element.
Do you have some suggestions on how to handle this instead?
Besides
+FAIL: g++.dg/cpp26/fold-constr7.C  -std=c++26 (test for excess errors)
I spoke about there are some regressions on existing tests:
+FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 36)
+FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 38)
+FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 43)
+FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 45)
+FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 47)
+FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26 (internal compiler error: tree check: expected type_argument_pack or nontype_argument_pack, have integer_type in satisfy_fold, at cp/constraint.cc:2940)
+FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26 (test for excess errors)
+FAIL: g++.dg/cpp2a/concepts-pr67860.C  -std=c++26 (internal compiler error: tree check: expected type_argument_pack or nontype_argument_pack, have integer_type in satisfy_fold, at cp/constraint.cc:2940)
+FAIL: g++.dg/cpp2a/concepts-pr67860.C  -std=c++26 (test for excess errors)
+FAIL: g++.dg/warn/Wdangling-reference17.C  -std=gnu++26 (internal compiler error: in keep_template_parm, at cp/pt.cc:10975)
+FAIL: g++.dg/warn/Wdangling-reference17.C  -std=gnu++26 (test for excess errors)
there concepts-fn3.C, concepts-pr67860.C are one ICE clearly caused by the
problem with the need to use whole pack inside of something that needs to
use just pack element inside of an fold expanded constraint,
Wdangling-reference17.C is an ICE that repeats then in
FAIL: 24_iterators/const_iterator/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: 24_iterators/const_iterator/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: 24_iterators/move_iterator/lwg3391.cc  -std=gnu++26 (test for excess errors)
FAIL: 25_algorithms/fold_left/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: 25_algorithms/fold_left/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: 25_algorithms/fold_right/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: 25_algorithms/fold_right/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/100479.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/100479.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 105)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 106)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 108)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 110)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 112)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 113)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 115)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 117)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 119)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 120)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 121)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 122)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 91)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 92)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 93)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 94)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 96)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 97)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 98)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 99)
FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26 (test for excess errors)
FAIL: std/ranges/adaptors/116038.cc  -std=gnu++26 (test for excess errors)
FAIL: std/ranges/adaptors/95322.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/95322.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/99433.cc  -std=gnu++26 (test for excess errors)
FAIL: std/ranges/adaptors/adjacent/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/adjacent/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/adjacent_transform/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/adjacent_transform/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/all.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/all.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/as_const/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/as_const/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/as_rvalue/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/as_rvalue/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/chunk/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/chunk/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/chunk_by/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/chunk_by/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/conditionally_borrowed.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/conditionally_borrowed.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/drop.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/drop.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/drop_while.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/drop_while.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/elements.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/elements.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/filter.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/filter.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/join.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/join.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/join_with/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/join_with/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/lazy_split.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/lazy_split.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26  (test for errors, line 33)
FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26  (test for errors, line 41)
FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26  (test for errors, line 42)
FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26 (test for excess errors)
FAIL: std/ranges/adaptors/lwg3286.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/lwg3286.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/lwg3715.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/lwg3715.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/p1739.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/p1739.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/p2281.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/p2281.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/p2770r0.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/p2770r0.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/reverse.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/reverse.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/slide/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/slide/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/split.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/split.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/stride/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/stride/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/take.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/take.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/take_while.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/take_while.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/adaptors/transform.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/adaptors/transform.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/cartesian_product/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/cartesian_product/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/concat/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/concat/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 434)
FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 435)
FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 436)
FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 437)
FAIL: std/ranges/conv/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/conv/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/istream_view.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/istream_view.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/p2259.cc  -std=gnu++26 (test for excess errors)
FAIL: std/ranges/p2325.cc  -std=gnu++26 (test for excess errors)
FAIL: std/ranges/p2367.cc  -std=gnu++26 (test for excess errors)
FAIL: std/ranges/range_adaptor_closure.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/range_adaptor_closure.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/repeat/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/repeat/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/zip/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/zip/1.cc  -std=gnu++26 compilation failed to produce executable
FAIL: std/ranges/zip_transform/1.cc  -std=gnu++26 (test for excess errors)
UNRESOLVED: std/ranges/zip_transform/1.cc  -std=gnu++26 compilation failed to produce executable
inside of libstdc++ all at c++26 and I think is most likely the same
problem, just different exact ICE.

Bootstrapped/regtested on x86_64-linux and i686-linux with the above
mentioned regressions.

2024-07-26  Jakub Jelinek  <jakub@redhat.com>

	PR c++/115746
gcc/cp/
	* cp-tree.def: Implement C++26 P2963R3 - Ordering of constraints
	involving fold expressions.
	(FOLD_CONSTR): New tree code.
	* cp-tree.h (CONSTR_P): Handle FOLD_CONSTR.
	(CONSTR_CHECK): Include FOLD_CONSTR.
	(FOLD_CONSTR_EXPR): Define.
	(FOLD_CONSTR_PACKS): Define.
	(FOLD_CONSTR_DISJ_P): Define.
	* cp-objcp-common.cc (cp_common_init_ts): Handle FOLD_CONSTR.
	* constraint.cc (normalize_fold_expr): New function.
	(normalize_expression): Use it for {U,BI}NARY_{LEFT,RIGHT}_FOLD_EXPR.
	(fold_constraints_identical_p): New function.
	(constraints_equivalent_p): Use it for FOLD_CONSTR.
	(add_constraint): Handle FOLD_CONSTR.
	(tsubst_parameter_mapping): Undo template_parm_to_arg returning
	parameter packs if inside fold expanded constraint.  Formatting fix
	in wrapper.
	(satisfy_fold): New function.
	(satisfy_constraint_r): Handle FOLD_CONSTR.
	* error.cc (dump_expr): Likewise.
	* logic.cc (struct clause): Add m_fold member.
	(clause::clause): Handle FOLD_CONSTR, for copy ctor copy over m_fold.
	(clause::replace, clause::insert): Handle FOLD_CONSTR.
	(clause::folds): New method.
	(atomic_p): Also return true for FOLD_CONSTR.
	(decompose_atom): Adjust function comment.
	(derive_fold_proof): New function.
	(derive_proof): Handle FOLD_CONSTR.  Change default: case to
	case ATOMIC_CONSTR, for default: add gcc_unreachable ().  Formatting
	fixes.
	(subsumes_constraints_nonnull): Use auto_cond_timevar instead of
	auto_timevar.
	* cxx-pretty-print.cc (cxx_pretty_printer::expression): Handle
	FOLD_CONSTR.
	(pp_cxx_fold_expanded_constraint): New function.
	(pp_cxx_constraint): Handle FOLD_CONSTR.
gcc/testsuite/
	* g++.dg/concepts/diagnostic3.C: Guard diagnostics on c++23_down.
	* g++.dg/concepts/variadic2.C: Likewise.
	* g++.dg/concepts/variadic4.C: Likewise.
	* g++.dg/cpp2a/concepts-requires33.C: Expect another error for c++26.
	* g++.dg/cpp26/fold-constr1.C: New test.
	* g++.dg/cpp26/fold-constr2.C: New test.
	* g++.dg/cpp26/fold-constr3.C: New test.
	* g++.dg/cpp26/fold-constr4.C: New test.
	* g++.dg/cpp26/fold-constr5.C: New test.
	* g++.dg/cpp26/fold-constr6.C: New test.
	* g++.dg/cpp26/fold-constr7.C: New test.
	* g++.dg/cpp26/fold-constr8.C: New test.
	* g++.dg/cpp26/fold-constr9.C: New test.
	* g++.dg/cpp26/fold-constr10.C: New test.


	Jakub

Comments

Patrick Palka July 26, 2024, 6:15 p.m. UTC | #1
On Fri, 26 Jul 2024, Jakub Jelinek wrote:

> Hi!
> 
> I've tried to implement the C++26 fold expanded constraints paper but ran
> into issues (see below).  Would appreciate some guidance/help, I'm afraid
> I'm stuck.
> 
> The patch introduces a FOLD_CONSTR tree to represent fold expanded
> constraints, normalizes for C++26 some {U,BI}NARY_{LEFT,RIGHT}_FOLD_EXPR
> into those (+ CONJ_CONSTR or DISJ_CONSTR for the binary ones), attempts
> to handle their satisfaction (that is the unresolved issue) and handle it
> in the subsumption checking code.
> 
> Most of the newly added tests pass, compared to what clang++ trunk (which
> claims to implement this paper) there are some differences:
> static_assert (bar <int> ()); in cpp26/fold-constr5.C is accepted
> by clang in C++26 mode and rejected by GCC.  I believe GCC is right,
> C<typename U::type> && ... && C<typename T::type>
> where U is an empty pack should be normalized to
> (C<typename U::type> /\ ...) /\ C<typename T::type>
> so I think (C<typename U::type> /\ ...) is satisfied but
> C<typename T::type> should not as int::type is invalid, so the overall
> result should be not satisfied.
> Another difference is
> static_assert (U<int>::bar<int> ()); in cpp26/fold-constr6.C, which is
> rejected by clang in C++26 mode and accepted by GCC.
> This is for
>   template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
>   static constexpr bool bar () { return false; }
>   template <class... V> requires ((C2<V> && ...) && ... && C2<T>)
>   static constexpr bool bar () { return true; }
> where both T and V are packs, I believe the first should be normalized to
> (C1<T> /\ ...) && (C1<V> /\ ...)
> and the second to
> (C2<V> /\ ...) && (C2<T> /\ ...)
> where C2<int> subsumes C1<int>.  clang++ accepts when it is written in
> the other order, so when normalized to
> (C2<T> /\ ...) && (C2<V> /\ ...)
> and in that case GCC and clang agree that the latter subsumes the former.
> C2<V> /\ ... subsumes C1<V> /\ ...
> and
> C2<T> /\ ... subsumes C1<T> /\ ...
> and
> C2<T> /\ ... does not subsume C1<V> /\ ...
> and
> C2<V> /\ ... does not subsume C1<T> /\ ...
> but overall I believe in either order the result is subsumes.
> Another difference is in the cpp26/fold-constr10.C testcase, which is
> essentially PR116106 and Patrick claims that unused template parameters in
> the mapping aren't substituted into.
> Also, the new code affects not just explicitly written fold expressions
> in the constraints, but also implicitly created ones (say for
> template <Concept... T> and similar), but here the GCC patch agrees with
> clang trunk.
> 
> Anyway, the way I've attempted to implement satisfy_fold is through updating
> the args vector or vectors (COW) to change parameter pack into parameter
> pack's element which is currently checked for satisfaction.
> That works most of the time, when all the occurrences of the template
> parameter(s) recorded in PACK_EXPANSION_PARAMETER_PACKS (FOLD_EXPR_PACK (t))
> of the original *_FOLD_EXPR are to be replaced by the pack element, which
> should be fine for any such occurences unless they appear in another
> expansion pack with the same template parameter.
> The instantiation of those nested {TYPE,EXPR}_PACK_EXPANSION need to be able
> to access the original whole pack rather than just one of its elements.
> And some atomic constraints e.g. can refer to both; I've tried to
> write this in cpp26/fold-constr7.C testcase (which is the only one
> from newly added that FAILs with C++26):
> template <class... T> requires ((((sizeof (T) + ...) < 8 * sizeof (int)) && C2<T>) && ...)
> constexpr bool foo (T...) { return true; };
> here actually the ((sizeof (T) + ...) < 8 * sizeof (int)) atomic constraint
> only needs the whole T pack and C2<T> atomic constraint only needs the T's
> pack element, but guess it can be also changed so that one atomic constraint
> needs both whole pack and pack element.
> Do you have some suggestions on how to handle this instead?

IIUC the way gen_elem_of_pack_expansion_instantiation handles this for
ordinary pack expnasions is by replacing each ARGUMENT_PACK with an
ARGUMENT_PACK_SELECT.  This ARGUMENT_PACK_SELECT contains the entire
pack as well as the current index into the pack, and it essentially
acts as an overloaded template argument that's treated either as a
single pack element or as the entire pack itself as needed.

On that note I wonder if there's any opportunity for code reuse from
gen_elem_of_pack_expansion_instantiation / tsubst_pack_expansion?

> Besides
> +FAIL: g++.dg/cpp26/fold-constr7.C  -std=c++26 (test for excess errors)
> I spoke about there are some regressions on existing tests:
> +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 36)
> +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 38)
> +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 43)
> +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 45)
> +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 47)
> +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26 (internal compiler error: tree check: expected type_argument_pack or nontype_argument_pack, have integer_type in satisfy_fold, at cp/constraint.cc:2940)
> +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26 (test for excess errors)
> +FAIL: g++.dg/cpp2a/concepts-pr67860.C  -std=c++26 (internal compiler error: tree check: expected type_argument_pack or nontype_argument_pack, have integer_type in satisfy_fold, at cp/constraint.cc:2940)
> +FAIL: g++.dg/cpp2a/concepts-pr67860.C  -std=c++26 (test for excess errors)
> +FAIL: g++.dg/warn/Wdangling-reference17.C  -std=gnu++26 (internal compiler error: in keep_template_parm, at cp/pt.cc:10975)
> +FAIL: g++.dg/warn/Wdangling-reference17.C  -std=gnu++26 (test for excess errors)
> there concepts-fn3.C, concepts-pr67860.C are one ICE clearly caused by the
> problem with the need to use whole pack inside of something that needs to
> use just pack element inside of an fold expanded constraint,
> Wdangling-reference17.C is an ICE that repeats then in
> FAIL: 24_iterators/const_iterator/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: 24_iterators/const_iterator/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: 24_iterators/move_iterator/lwg3391.cc  -std=gnu++26 (test for excess errors)
> FAIL: 25_algorithms/fold_left/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: 25_algorithms/fold_left/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: 25_algorithms/fold_right/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: 25_algorithms/fold_right/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/100479.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/100479.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 105)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 106)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 108)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 110)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 112)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 113)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 115)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 117)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 119)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 120)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 121)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 122)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 91)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 92)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 93)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 94)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 96)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 97)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 98)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 99)
> FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26 (test for excess errors)
> FAIL: std/ranges/adaptors/116038.cc  -std=gnu++26 (test for excess errors)
> FAIL: std/ranges/adaptors/95322.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/95322.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/99433.cc  -std=gnu++26 (test for excess errors)
> FAIL: std/ranges/adaptors/adjacent/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/adjacent/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/adjacent_transform/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/adjacent_transform/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/all.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/all.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/as_const/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/as_const/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/as_rvalue/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/as_rvalue/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/chunk/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/chunk/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/chunk_by/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/chunk_by/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/conditionally_borrowed.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/conditionally_borrowed.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/drop.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/drop.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/drop_while.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/drop_while.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/elements.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/elements.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/filter.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/filter.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/join.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/join.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/join_with/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/join_with/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/lazy_split.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/lazy_split.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26  (test for errors, line 33)
> FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26  (test for errors, line 41)
> FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26  (test for errors, line 42)
> FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26 (test for excess errors)
> FAIL: std/ranges/adaptors/lwg3286.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/lwg3286.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/lwg3715.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/lwg3715.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/p1739.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/p1739.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/p2281.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/p2281.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/p2770r0.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/p2770r0.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/reverse.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/reverse.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/slide/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/slide/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/split.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/split.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/stride/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/stride/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/take.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/take.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/take_while.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/take_while.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/adaptors/transform.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/adaptors/transform.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/cartesian_product/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/cartesian_product/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/concat/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/concat/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 434)
> FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 435)
> FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 436)
> FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 437)
> FAIL: std/ranges/conv/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/conv/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/istream_view.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/istream_view.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/p2259.cc  -std=gnu++26 (test for excess errors)
> FAIL: std/ranges/p2325.cc  -std=gnu++26 (test for excess errors)
> FAIL: std/ranges/p2367.cc  -std=gnu++26 (test for excess errors)
> FAIL: std/ranges/range_adaptor_closure.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/range_adaptor_closure.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/repeat/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/repeat/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/zip/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/zip/1.cc  -std=gnu++26 compilation failed to produce executable
> FAIL: std/ranges/zip_transform/1.cc  -std=gnu++26 (test for excess errors)
> UNRESOLVED: std/ranges/zip_transform/1.cc  -std=gnu++26 compilation failed to produce executable
> inside of libstdc++ all at c++26 and I think is most likely the same
> problem, just different exact ICE.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux with the above
> mentioned regressions.
> 
> 2024-07-26  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR c++/115746
> gcc/cp/
> 	* cp-tree.def: Implement C++26 P2963R3 - Ordering of constraints
> 	involving fold expressions.
> 	(FOLD_CONSTR): New tree code.
> 	* cp-tree.h (CONSTR_P): Handle FOLD_CONSTR.
> 	(CONSTR_CHECK): Include FOLD_CONSTR.
> 	(FOLD_CONSTR_EXPR): Define.
> 	(FOLD_CONSTR_PACKS): Define.
> 	(FOLD_CONSTR_DISJ_P): Define.
> 	* cp-objcp-common.cc (cp_common_init_ts): Handle FOLD_CONSTR.
> 	* constraint.cc (normalize_fold_expr): New function.
> 	(normalize_expression): Use it for {U,BI}NARY_{LEFT,RIGHT}_FOLD_EXPR.
> 	(fold_constraints_identical_p): New function.
> 	(constraints_equivalent_p): Use it for FOLD_CONSTR.
> 	(add_constraint): Handle FOLD_CONSTR.
> 	(tsubst_parameter_mapping): Undo template_parm_to_arg returning
> 	parameter packs if inside fold expanded constraint.  Formatting fix
> 	in wrapper.
> 	(satisfy_fold): New function.
> 	(satisfy_constraint_r): Handle FOLD_CONSTR.
> 	* error.cc (dump_expr): Likewise.
> 	* logic.cc (struct clause): Add m_fold member.
> 	(clause::clause): Handle FOLD_CONSTR, for copy ctor copy over m_fold.
> 	(clause::replace, clause::insert): Handle FOLD_CONSTR.
> 	(clause::folds): New method.
> 	(atomic_p): Also return true for FOLD_CONSTR.
> 	(decompose_atom): Adjust function comment.
> 	(derive_fold_proof): New function.
> 	(derive_proof): Handle FOLD_CONSTR.  Change default: case to
> 	case ATOMIC_CONSTR, for default: add gcc_unreachable ().  Formatting
> 	fixes.
> 	(subsumes_constraints_nonnull): Use auto_cond_timevar instead of
> 	auto_timevar.
> 	* cxx-pretty-print.cc (cxx_pretty_printer::expression): Handle
> 	FOLD_CONSTR.
> 	(pp_cxx_fold_expanded_constraint): New function.
> 	(pp_cxx_constraint): Handle FOLD_CONSTR.
> gcc/testsuite/
> 	* g++.dg/concepts/diagnostic3.C: Guard diagnostics on c++23_down.
> 	* g++.dg/concepts/variadic2.C: Likewise.
> 	* g++.dg/concepts/variadic4.C: Likewise.
> 	* g++.dg/cpp2a/concepts-requires33.C: Expect another error for c++26.
> 	* g++.dg/cpp26/fold-constr1.C: New test.
> 	* g++.dg/cpp26/fold-constr2.C: New test.
> 	* g++.dg/cpp26/fold-constr3.C: New test.
> 	* g++.dg/cpp26/fold-constr4.C: New test.
> 	* g++.dg/cpp26/fold-constr5.C: New test.
> 	* g++.dg/cpp26/fold-constr6.C: New test.
> 	* g++.dg/cpp26/fold-constr7.C: New test.
> 	* g++.dg/cpp26/fold-constr8.C: New test.
> 	* g++.dg/cpp26/fold-constr9.C: New test.
> 	* g++.dg/cpp26/fold-constr10.C: New test.
> 
> --- gcc/cp/cp-tree.def.jj	2024-07-25 21:34:46.791268760 +0200
> +++ gcc/cp/cp-tree.def	2024-07-26 09:20:05.256197019 +0200
> @@ -538,6 +538,12 @@ DEFTREECODE (ATOMIC_CONSTR, "atomic_cons
>  DEFTREECODE (CONJ_CONSTR, "conj_constr", tcc_expression, 2)
>  DEFTREECODE (DISJ_CONSTR, "disj_constr", tcc_expression, 2)
>  
> +/* Fold expanded constraint.
> +   CONSTR_INFO provides source info to support diagnostics.
> +   FOLD_CONSTR_EXPR is the constraint embedded in it,
> +   FOLD_CONSTR_PACKS are the packs expanded by it.  */
> +DEFTREECODE (FOLD_CONSTR, "fold_constr", tcc_expression, 2)
> +
>  /* The co_await expression is used to support coroutines.
>  
>    Op 0 is the cast expresssion (potentially modified by the
> --- gcc/cp/cp-tree.h.jj	2024-07-26 08:34:18.104160097 +0200
> +++ gcc/cp/cp-tree.h	2024-07-26 09:19:10.626908903 +0200
> @@ -1679,11 +1679,12 @@ check_constraint_info (tree t)
>  #define CONSTR_P(NODE)                  \
>    (TREE_CODE (NODE) == ATOMIC_CONSTR    \
>     || TREE_CODE (NODE) == CONJ_CONSTR   \
> -   || TREE_CODE (NODE) == DISJ_CONSTR)
> +   || TREE_CODE (NODE) == DISJ_CONSTR	\
> +   || TREE_CODE (NODE) == FOLD_CONSTR)
>  
>  /* Valid for any normalized constraint.  */
>  #define CONSTR_CHECK(NODE) \
> -  TREE_CHECK3 (NODE, ATOMIC_CONSTR, CONJ_CONSTR, DISJ_CONSTR)
> +  TREE_CHECK4 (NODE, ATOMIC_CONSTR, CONJ_CONSTR, DISJ_CONSTR, FOLD_CONSTR)
>  
>  /* The CONSTR_INFO stores normalization data for a constraint. It refers to
>     the original expression and the expression or declaration
> @@ -1724,6 +1725,18 @@ check_constraint_info (tree t)
>  #define ATOMIC_CONSTR_EXPR(NODE) \
>    CONSTR_EXPR (ATOMIC_CONSTR_CHECK (NODE))
>  
> +/* The constraint embedded in FOLD_CONSTR.  */
> +#define FOLD_CONSTR_EXPR(NODE) \
> +  TREE_OPERAND (FOLD_CONSTR_CHECK (NODE), 0)
> +
> +/* List of packs expanded by it.  */
> +#define FOLD_CONSTR_PACKS(NODE) \
> +  TREE_OPERAND (FOLD_CONSTR_CHECK (NODE), 1)
> +
> +/* True if FOLD_CONSTR has fold-operator ||, false for &&.  */
> +#define FOLD_CONSTR_DISJ_P(NODE) \
> +  TREE_STATIC (FOLD_CONSTR_CHECK (NODE))
> +
>  /* Whether a PARM_DECL represents a local parameter in a
>     requires-expression.  */
>  #define CONSTRAINT_VAR_P(NODE) \
> --- gcc/cp/cp-objcp-common.cc.jj	2024-07-25 21:34:46.791268760 +0200
> +++ gcc/cp/cp-objcp-common.cc	2024-07-26 08:34:40.532867127 +0200
> @@ -705,6 +705,7 @@ cp_common_init_ts (void)
>    MARK_TS_EXP (CONJ_CONSTR);
>    MARK_TS_EXP (DISJ_CONSTR);
>    MARK_TS_EXP (ATOMIC_CONSTR);
> +  MARK_TS_EXP (FOLD_CONSTR);
>    MARK_TS_EXP (NESTED_REQ);
>    MARK_TS_EXP (REQUIRES_EXPR);
>    MARK_TS_EXP (SIMPLE_REQ);
> --- gcc/cp/constraint.cc.jj	2024-07-25 21:34:46.791268760 +0200
> +++ gcc/cp/constraint.cc	2024-07-26 15:03:04.438445216 +0200
> @@ -852,6 +852,39 @@ normalize_atom (tree t, tree args, norm_
>    return atom;
>  }
>  
> +/* Normalize {UNARY,BINARY}_{LEFT,RIGHT}_FOLD_EXPR.  */
> +
> +static tree
> +normalize_fold_expr (tree t, tree args, norm_info info)
> +{
> +  if (cxx_dialect < cxx26
> +      || (FOLD_EXPR_OP (t) != TRUTH_ANDIF_EXPR
> +	  && FOLD_EXPR_OP (t) != TRUTH_ORIF_EXPR)
> +      || FOLD_EXPR_MODIFY_P (t))
> +    return normalize_atom (t, args, info);
> +
> +  tree norm
> +    = normalize_expression (PACK_EXPANSION_PATTERN (FOLD_EXPR_PACK (t)),
> +			    args, info);
> +  tree ci = (info.generate_diagnostics
> +	     ? build_tree_list (t, info.context) : NULL_TREE);
> +  tree params = PACK_EXPANSION_PARAMETER_PACKS (FOLD_EXPR_PACK (t));
> +  tree ret = build2 (FOLD_CONSTR, ci, norm, params);
> +  if (FOLD_EXPR_OP (t) == TRUTH_ORIF_EXPR)
> +    FOLD_CONSTR_DISJ_P (ret) = 1;
> +  if (TREE_CODE (t) == BINARY_LEFT_FOLD_EXPR
> +      || TREE_CODE (t) == BINARY_RIGHT_FOLD_EXPR)
> +    {
> +      tree init = normalize_expression (FOLD_EXPR_INIT (t), args, info);
> +      tree_code code
> +	= FOLD_EXPR_OP (t) == TRUTH_ANDIF_EXPR ? CONJ_CONSTR : DISJ_CONSTR;
> +      if (TREE_CODE (t) == BINARY_LEFT_FOLD_EXPR)
> +	std::swap (ret, init);
> +      return build2 (code, ci, ret, init);
> +    }
> +  return ret;
> +}
> +
>  /* Returns the normal form of an expression.  */
>  
>  static tree
> @@ -869,6 +902,11 @@ normalize_expression (tree t, tree args,
>        return normalize_logical_operation (t, args, CONJ_CONSTR, info);
>      case TRUTH_ORIF_EXPR:
>        return normalize_logical_operation (t, args, DISJ_CONSTR, info);
> +    case UNARY_LEFT_FOLD_EXPR:
> +    case UNARY_RIGHT_FOLD_EXPR:
> +    case BINARY_LEFT_FOLD_EXPR:
> +    case BINARY_RIGHT_FOLD_EXPR:
> +      return normalize_fold_expr (t, args, info);
>      default:
>        return normalize_atom (t, args, info);
>      }
> @@ -1055,6 +1093,28 @@ atomic_constraints_identical_p (tree t1,
>    return true;
>  }
>  
> +/* Compare two fold expanded constraints T1 and T2.  */
> +
> +static bool
> +fold_constraints_identical_p (tree t1, tree t2)
> +{
> +  gcc_assert (TREE_CODE (t1) == FOLD_CONSTR);
> +  gcc_assert (TREE_CODE (t2) == FOLD_CONSTR);
> +
> +  if (FOLD_CONSTR_DISJ_P (t1) != FOLD_CONSTR_DISJ_P (t2))
> +    return false;
> +
> +  tree p1 = FOLD_CONSTR_PACKS (t1);
> +  tree p2 = FOLD_CONSTR_PACKS (t2);
> +  for (; p1 && p2; p1 = TREE_CHAIN (p1), p2 = TREE_CHAIN (p2))
> +    if (!template_args_equal (TREE_VALUE (p1), TREE_VALUE (p2)))
> +      return false;
> +  if (p1 || p2)
> +    return false;
> +  return constraints_equivalent_p (FOLD_CONSTR_EXPR (t1),
> +				   FOLD_CONSTR_EXPR (t2));
> +}
> +
>  /* True if T1 and T2 are equivalent, meaning they have the same syntactic
>     structure and all corresponding constraints are identical.  */
>  
> @@ -1082,6 +1142,10 @@ constraints_equivalent_p (tree t1, tree
>        if (!atomic_constraints_identical_p (t1, t2))
>  	return false;
>        break;
> +    case FOLD_CONSTR:
> +      if (!fold_constraints_identical_p (t1, t2))
> +	return false;
> +      break;
>      default:
>        gcc_unreachable ();
>      }
> @@ -1126,6 +1190,10 @@ add_constraint (tree t, hash& h)
>      case ATOMIC_CONSTR:
>        h.merge_hash (hash_atomic_constraint (t));
>        break;
> +    case FOLD_CONSTR:
> +      h.add_int (FOLD_CONSTR_DISJ_P (t));
> +      add_constraint (FOLD_CONSTR_EXPR (t), h);
> +      break;
>      default:
>        gcc_unreachable ();
>      }
> @@ -2163,6 +2231,29 @@ tsubst_parameter_mapping (tree map, tree
>        tree parm = TREE_VALUE (p);
>        tree arg = TREE_PURPOSE (p);
>        tree new_arg;
> +      if (cxx_dialect >= cxx26 && ARGUMENT_PACK_P (arg))
> +	{
> +	  /* template_parm_to_arg for packs wraps the template parm
> +	     with {,NON}TYPE_ARGUMENT_PACK with pack expansion.
> +	     For packs expanded by fold expanded constraint undo this
> +	     here.  */
> +	  tree v = ARGUMENT_PACK_ARGS (arg);
> +	  if (TREE_VEC_LENGTH (v) == 1
> +	      && PACK_EXPANSION_P (TREE_VEC_ELT (v, 0)))
> +	    {
> +	      tree t = PACK_EXPANSION_PATTERN (TREE_VEC_ELT (v, 0));
> +	      tree e = STRIP_REFERENCE_REF (t);
> +	      if (TEMPLATE_PARM_P (e))
> +		{
> +		  int level;
> +		  int index;
> +		  template_parm_level_and_index (e, &level, &index);
> +		  tree a = TMPL_ARG (args, level, index);
> +		  if (!ARGUMENT_PACK_P (a))
> +		    arg = t;
> +		}
> +	    }
> +	}
>        if (ARGUMENT_PACK_P (arg))
>  	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
>        else
> @@ -2187,7 +2278,8 @@ tsubst_parameter_mapping (tree map, tree
>  }
>  
>  tree
> -tsubst_parameter_mapping (tree map, tree args, tsubst_flags_t complain, tree in_decl)
> +tsubst_parameter_mapping (tree map, tree args, tsubst_flags_t complain,
> +			  tree in_decl)
>  {
>    return tsubst_parameter_mapping (map, args, subst_info (complain, in_decl));
>  }
> @@ -2831,6 +2924,101 @@ satisfy_atom (tree t, tree args, sat_inf
>    return cache.save (inst_cache.save (result));
>  }
>  
> +/* Compute the satisfaction of a fold expanded constraint.  */
> +
> +static tree
> +satisfy_fold (tree t, tree args, sat_info info)
> +{
> +  tree orig_args = args;
> +  int len = -1;
> +  auto_vec <int, 8> indices;
> +  for (tree p = FOLD_CONSTR_PACKS (t); p; p = TREE_CHAIN (p))
> +    {
> +      int level, index;
> +      template_parm_level_and_index (TREE_VALUE (p), &level, &index);
> +      tree a = TMPL_ARG (orig_args, level, index);
> +      int this_len = TREE_VEC_LENGTH (ARGUMENT_PACK_ARGS (a));
> +      if (len == -1)
> +	len = this_len;
> +      else if (this_len != len)
> +	{
> +	  if (info.diagnose_unsatisfaction_p ())
> +	    {
> +	      diagnosing_failed_constraint failure (t, args, info.noisy ());
> +	      tree first = TREE_VALUE (FOLD_CONSTR_PACKS (t));
> +	      cp_expr fold_expr = CONSTR_EXPR (t);
> +	      inform (fold_expr.get_location (),
> +		      "fold expanded constraint not satisfied because "
> +		      "of pack length mismatch");
> +	      if (TREE_CODE (first) == TYPE_PACK_EXPANSION)
> +		inform (fold_expr.get_location (),
> +			"%qT has length %d", first, len);
> +	      else
> +		inform (fold_expr.get_location (),
> +			"%qE has length %d", first, len);
> +	      if (TREE_CODE (TREE_VALUE (p)) == TYPE_PACK_EXPANSION)
> +		inform (fold_expr.get_location (),
> +			"%qT has length %d", TREE_VALUE (p), this_len);
> +	      else
> +		inform (fold_expr.get_location (),
> +			"%qE has length %d", TREE_VALUE (p), this_len);
> +	    }
> +	  return error_mark_node;
> +	}
> +      if (len != 0)
> +	{
> +	  indices.safe_push (level);
> +	  indices.safe_push (index);
> +	}
> +    }
> +  gcc_checking_assert (len != -1);
> +  if (len == 0)
> +    {
> +      /* For N = 0 fold expanded constraint with && fold operator is
> +	 satisfied.  */
> +      if (!FOLD_CONSTR_DISJ_P (t))
> +	return boolean_true_node;
> +      if (info.diagnose_unsatisfaction_p ())
> +	{
> +	  diagnosing_failed_constraint failure (t, args, info.noisy ());
> +	  cp_expr fold_expr = CONSTR_EXPR (t);
> +	  inform (fold_expr.get_location (),
> +		  "fold expanded constraint not satisfied because "
> +		  "of empty pack with %<||%> fold operator");
> +	}
> +      return boolean_false_node;
> +    }
> +  for (int i = 0; i < len; ++i)
> +    {
> +      unsigned j;
> +      int level, index;
> +      args = orig_args;
> +      FOR_EACH_VEC_ELT (indices, j, level)
> +	{
> +	  ++j;
> +	  indices.iterate (j, &index);
> +	  if (orig_args == args)
> +	    args = copy_node (orig_args);
> +	  if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (orig_args))
> +	    {
> +	      tree v = TMPL_ARGS_LEVEL (orig_args, level);
> +	      if (TMPL_ARGS_LEVEL (args, level) == v)
> +		SET_TMPL_ARGS_LEVEL (args, level, copy_node (v));
> +	    }

IIUC you can use copy_template_args here.  And sadly I think you may
need to make a copy at each iteration, not just once, because the
satisfaction cache assumes args are immutable when adding a new
entry in the cache :/

Not sure how big of a memory leak this would be in practice,
but one way to mitigate this might be to ggc_free this copy
if in the recursive call to satisfy_constraint_r there was
no cache miss.

> +	  tree a = TMPL_ARG (orig_args, level, index);
> +	  TMPL_ARG (args, level, index)
> +	    = TREE_VEC_ELT (ARGUMENT_PACK_ARGS (a), i);
> +	}
> +      tree result = satisfy_constraint_r (FOLD_CONSTR_EXPR (t), args, info);
> +      if (result == error_mark_node)
> +	return result;
> +      if (result == (FOLD_CONSTR_DISJ_P (t)
> +		     ? boolean_true_node : boolean_false_node))
> +	return result;
> +    }
> +  return FOLD_CONSTR_DISJ_P (t) ? boolean_false_node : boolean_true_node;
> +}
> +
>  /* Determine if the normalized constraint T is satisfied.
>     Returns boolean_true_node if the expression/constraint is
>     satisfied, boolean_false_node if not, and error_mark_node
> @@ -2856,6 +3044,8 @@ satisfy_constraint_r (tree t, tree args,
>        return satisfy_disjunction (t, args, info);
>      case ATOMIC_CONSTR:
>        return satisfy_atom (t, args, info);
> +    case FOLD_CONSTR:
> +      return satisfy_fold (t, args, info);
>      default:
>        gcc_unreachable ();
>      }
> --- gcc/cp/error.cc.jj	2024-07-25 21:34:46.793268734 +0200
> +++ gcc/cp/error.cc	2024-07-26 08:41:23.555602719 +0200
> @@ -3097,6 +3097,7 @@ dump_expr (cxx_pretty_printer *pp, tree
>      case ATOMIC_CONSTR:
>      case CONJ_CONSTR:
>      case DISJ_CONSTR:
> +    case FOLD_CONSTR:
>        {
>          pp_cxx_constraint (cxx_pp, t);
>          break;
> --- gcc/cp/logic.cc.jj	2024-07-25 21:34:46.793268734 +0200
> +++ gcc/cp/logic.cc	2024-07-26 09:22:29.047325077 +0200
> @@ -65,6 +65,8 @@ struct clause
>      m_terms.push_back (t);
>      if (TREE_CODE (t) == ATOMIC_CONSTR)
>        m_set.add (t);
> +    else if (TREE_CODE (t) == FOLD_CONSTR)
> +      m_fold.safe_push (t);
>  
>      m_current = m_terms.begin ();
>    }
> @@ -74,8 +76,11 @@ struct clause
>       copied list of terms.  */
>  
>    clause (clause const& c)
> -    : m_terms (c.m_terms), m_set (c.m_set), m_current (m_terms.begin ())
> +    : m_terms (c.m_terms), m_set (c.m_set), m_fold (c.m_fold.length ()),
> +      m_current (m_terms.begin ())
>    {
> +    for (auto v : &c.m_fold)
> +      m_fold.quick_push (v);
>      std::advance (m_current, std::distance (c.begin (), c.current ()));
>    }
>  
> @@ -109,6 +114,8 @@ struct clause
>  	if (m_set.add (t))
>  	  return std::make_pair (m_terms.erase (iter), true);
>        }
> +    else if (TREE_CODE (t) == FOLD_CONSTR)
> +      m_fold.safe_push (t);
>      *iter = t;
>      return std::make_pair (iter, false);
>    }
> @@ -126,6 +133,8 @@ struct clause
>  	if (m_set.add (t))
>  	  return std::make_pair (iter, false);
>        }
> +    else if (TREE_CODE (t) == FOLD_CONSTR)
> +      m_fold.safe_push (t);
>      return std::make_pair (m_terms.insert (iter, t), true);
>    }
>  
> @@ -166,6 +175,12 @@ struct clause
>      return m_set.contains (t);
>    }
>  
> +  /* Returns vector of FOLD_CONSTR terms.  */
> +
> +  auto_vec<tree> &folds ()
> +  {
> +    return m_fold;
> +  }
>  
>    /* Returns an iterator to the first clause in the formula.  */
>  
> @@ -204,6 +219,7 @@ struct clause
>  
>    std::list<tree> m_terms; /* The list of terms.  */
>    hash_set<tree, false, atom_hasher> m_set; /* The set of atomic constraints.  */
> +  auto_vec<tree> m_fold; /* The vector of fold expanded constraints.  */
>    iterator m_current; /* The current term.  */
>  };
>  
> @@ -340,7 +356,7 @@ conjunction_p (tree t)
>  static inline bool
>  atomic_p (tree t)
>  {
> -  return TREE_CODE (t) == ATOMIC_CONSTR;
> +  return TREE_CODE (t) == ATOMIC_CONSTR || TREE_CODE (t) == FOLD_CONSTR;
>  }
>  
>  /* Recursively count the number of clauses produced when converting T
> @@ -626,7 +642,7 @@ decompose_disjunction (formula& f, claus
>      branch_clause (f, c, t);
>  }
>  
> -/* An atomic constraint is already decomposed.  */
> +/* An atomic or fold expanded constraint is already decomposed.  */
>  inline void
>  decompose_atom (clause& c)
>  {
> @@ -691,13 +707,48 @@ derive_atomic_proof (clause& c, tree t)
>    return c.contains (t);
>  }
>  
> +/* Derive a proof of the fold expanded constraint T in clause C.  */
> +
> +static bool
> +derive_fold_proof (clause& c, tree t, rules r)
> +{
> +  auto_vec<tree> &folds = c.folds ();
> +  for (auto v : &folds)
> +    /* [temp.constr.order]/1 - a fold expanded constraint A subsumes
> +       another fold expanded constraint B if they are compatible for
> +       subsumption, have the same fold-operator, and the constraint
> +       of A subsumes that of B.  */
> +    if (FOLD_CONSTR_DISJ_P (v) == FOLD_CONSTR_DISJ_P (t))
> +      {
> +	bool compat = false;
> +	for (tree p1 = FOLD_CONSTR_PACKS (t); p1 && !compat;
> +	     p1 = TREE_CHAIN (p1))
> +	  for (tree p2 = FOLD_CONSTR_PACKS (v); p2;
> +	       p2 = TREE_CHAIN (p2))
> +	    /* [temp.constr.fold]/5 - Two fold expanded constraints are
> +	       compatible for subsumption if their respective constraints
> +	       both contain an equivalent unexpanded pack.  */
> +	    if (template_args_equal (TREE_VALUE (p1), TREE_VALUE (p2)))
> +	      {
> +		compat = true;
> +		break;
> +	      }
> +	if (compat
> +	    && (r == left
> +		? subsumes (FOLD_CONSTR_EXPR (v), FOLD_CONSTR_EXPR (t))
> +		: subsumes (FOLD_CONSTR_EXPR (t), FOLD_CONSTR_EXPR (v))))
> +	  return true;
> +      }
> +  return false;
> +}
> +
>  /* Derive a proof of T from the terms in C.  */
>  
>  static bool
>  derive_proof (clause& c, tree t, rules r)
>  {
>    switch (TREE_CODE (t))
> -  {
> +    {
>      case CONJ_CONSTR:
>        if (r == left)
>          return derive_proof_for_both_operands (c, t, r);
> @@ -708,9 +759,13 @@ derive_proof (clause& c, tree t, rules r
>          return derive_proof_for_either_operand (c, t, r);
>        else
>  	return derive_proof_for_both_operands (c, t, r);
> -    default:
> +    case ATOMIC_CONSTR:
>        return derive_atomic_proof (c, t);
> -  }
> +    case FOLD_CONSTR:
> +      return derive_fold_proof (c, t, r);
> +    default:
> +      gcc_unreachable ();
> +    }
>  }
>  
>  /* Key/value pair for caching subsumption results.  This associates a pair of
> @@ -787,7 +842,7 @@ save_subsumption (tree t1, tree t2, bool
>  static bool
>  subsumes_constraints_nonnull (tree lhs, tree rhs)
>  {
> -  auto_timevar time (TV_CONSTRAINT_SUB);
> +  auto_cond_timevar time (TV_CONSTRAINT_SUB);
>  
>    if (bool *b = lookup_subsumption (lhs, rhs))
>      return *b;
> --- gcc/cp/cxx-pretty-print.cc.jj	2024-07-25 21:34:46.793268734 +0200
> +++ gcc/cp/cxx-pretty-print.cc	2024-07-26 14:19:27.435984262 +0200
> @@ -1259,6 +1259,7 @@ cxx_pretty_printer::expression (tree t)
>      case ATOMIC_CONSTR:
>      case CONJ_CONSTR:
>      case DISJ_CONSTR:
> +    case FOLD_CONSTR:
>        pp_cxx_constraint (this, t);
>        break;
>  
> @@ -2882,6 +2883,19 @@ pp_cxx_disjunction (cxx_pretty_printer *
>  }
>  
>  void
> +pp_cxx_fold_expanded_constraint (cxx_pretty_printer *pp, tree t)
> +{
> +  pp_left_paren (pp);
> +  pp_cxx_constraint (pp, FOLD_CONSTR_EXPR (t));
> +  pp_space (pp);
> +  if (FOLD_CONSTR_DISJ_P (t))
> +    pp_string (pp, "\\/");
> +  else
> +    pp_string (pp, "/\\");
> +  pp_string (pp, " ...)");
> +}
> +
> +void
>  pp_cxx_constraint (cxx_pretty_printer *pp, tree t)
>  {
>    if (t == error_mark_node)
> @@ -2901,6 +2915,10 @@ pp_cxx_constraint (cxx_pretty_printer *p
>        pp_cxx_disjunction (pp, t);
>        break;
>  
> +    case FOLD_CONSTR:
> +      pp_cxx_fold_expanded_constraint (pp, t);
> +      break;
> +
>      case EXPR_PACK_EXPANSION:
>        pp->expression (TREE_OPERAND (t, 0));
>        break;
> --- gcc/testsuite/g++.dg/concepts/diagnostic3.C.jj	2023-10-16 17:25:32.456781462 +0200
> +++ gcc/testsuite/g++.dg/concepts/diagnostic3.C	2024-07-26 09:32:49.042262192 +0200
> @@ -1,4 +1,4 @@
> -// { dg-do compile { target c++2a } }
> +// { dg-do compile { target c++20 } }
>  
>  template<typename T>
>    inline constexpr bool foo_v = false;
> @@ -7,7 +7,7 @@ template<typename T>
>    concept foo = (bool)(foo_v<T> | foo_v<T&>);
>  
>  template<typename... Ts>
> -requires (foo<Ts> && ...) // { dg-message "19:with Ts = .int, char... evaluated to .false." }
> +requires (foo<Ts> && ...) // { dg-message "19:with Ts = .int, char... evaluated to .false." "" { target c++23_down } }
>  void
>  bar()
>  { }
> @@ -16,7 +16,7 @@ template<int>
>  struct S { };
>  
>  template<int... Is>
> -requires (foo<S<Is>> && ...) // { dg-message "22:with Is = .2, 3, 4... evaluated to .false." }
> +requires (foo<S<Is>> && ...) // { dg-message "22:with Is = .2, 3, 4... evaluated to .false." "" { target c++23_down } }
>  void
>  baz()
>  { }
> --- gcc/testsuite/g++.dg/concepts/variadic2.C.jj	2024-07-25 21:34:46.809268529 +0200
> +++ gcc/testsuite/g++.dg/concepts/variadic2.C	2024-07-26 08:34:40.535867087 +0200
> @@ -13,6 +13,7 @@ constexpr int f(Ts...) { return 1; }
>  
>  int main()
>  {
> -  static_assert(f(42) == 1); // { dg-error "ambiguous" }
> -  // The associated constraints of the two functions are incomparable.
> +  static_assert(f(42) == 1); // { dg-error "ambiguous" "" { target c++23_down } }
> +  // The associated constraints of the two functions are incomparable before
> +  // C++26.
>  }
> --- gcc/testsuite/g++.dg/concepts/variadic4.C.jj	2024-07-25 21:34:46.809268529 +0200
> +++ gcc/testsuite/g++.dg/concepts/variadic4.C	2024-07-26 08:34:40.535867087 +0200
> @@ -12,9 +12,9 @@ struct zip;
>  
>  template<Sequence... Seqs>
>      requires requires { typename list<Seqs...>; } // && (Sequence<Seqs> && ...)
> -struct zip<Seqs...> {}; // { dg-error "does not specialize" }
> +struct zip<Seqs...> {}; // { dg-error "does not specialize" "" { target c++23_down } }
>  // The constraints of the specialization and the sequence are not
> -// comparable; the specializations are unordered.
> +// comparable before C++26; the specializations are unordered.
>  
>  int main()
>  {
> --- gcc/testsuite/g++.dg/cpp2a/concepts-requires33.C	2022-12-05 11:10:37.712671571 +0100
> +++ gcc/testsuite/g++.dg/cpp2a/concepts-requires33.C	2024-07-26 18:37:41.867864077 +0200
> @@ -2,7 +2,7 @@
>  // { dg-do compile { target c++20 } }
>  
>  template<class... T>
> -void f() requires (requires (T x) { true; } && ...);
> +void f() requires (requires (T x) { true; } && ...); // { dg-error "invalid parameter type 'void'" "" { target c++26 } }
>  
>  int main() {
>    f<int>();
> --- gcc/testsuite/g++.dg/cpp26/fold-constr1.C.jj	2024-07-26 08:34:40.535867087 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr1.C	2024-07-26 08:34:40.535867087 +0200
> @@ -0,0 +1,37 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +template <class U> concept isint = __is_same (U, int);
> +
> +template <class... V> requires (isint<V> && ...)
> +constexpr int foo (V...) { return 1; };
> +
> +template <class... U> requires (... || isint<U>)
> +constexpr int bar (U...) { return 1; };
> +
> +template <class T, class... S> requires (isint<S> && ... && isint<T>)
> +constexpr int baz (T, S...) { return 1; }
> +
> +template <class T, class... R> requires (isint<T> || ... || isint<R>)
> +constexpr int qux (T, R...) { return 1; }
> +
> +int v1 = foo ();
> +int v2 = bar ();			// { dg-error "no matching function for call to" }
> +int v3 = foo (1, 2);
> +int v4 = bar (1, 2);
> +int v5 = foo (1L, 2);			// { dg-error "no matching function for call to" }
> +int v6 = foo (1, 2L);			// { dg-error "no matching function for call to" }
> +int v7 = bar (1L, 2);
> +int v8 = bar (2L, 3.0, 4, 5.0);
> +int v9 = bar (2LL, 3.0f, 5.0, 6ULL, 2U);// { dg-error "no matching function for call to" }
> +int v10 = baz ();			// { dg-error "no matching function for call to" }
> +int v11 = baz (1);
> +int v12 = baz (1L);			// { dg-error "no matching function for call to" }
> +int v13 = baz (1, 2, 3, 4, 5);
> +int v14 = baz (1, 2, 3L, 4, 5);		// { dg-error "no matching function for call to" }
> +int v15 = qux ();			// { dg-error "no matching function for call to" }
> +int v16 = qux (1);
> +int v17 = qux (1L);			// { dg-error "no matching function for call to" }
> +int v18 = qux (1, 2.0, 3LL);
> +int v19 = qux (1L, 2.0f, 3, 4ULL);
> +int v20 = qux (0.0f, 1L, 2.0, 3L);	// { dg-error "no matching function for call to" }
> --- gcc/testsuite/g++.dg/cpp26/fold-constr2.C.jj	2024-07-26 08:34:40.535867087 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr2.C	2024-07-26 08:34:40.535867087 +0200
> @@ -0,0 +1,56 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +template <class T> concept C1 = true;
> +template <class T> concept C2 = C1<T> && true;
> +template <class T> concept C3 = C1<T> && __is_same (T, int);
> +
> +template <class T> requires (C1<T>)
> +constexpr bool foo (T) { return false; };
> +template <class... T> requires (C2<T> && ...)
> +constexpr bool foo (T...) { return true; };
> +
> +static_assert (!foo (0));
> +static_assert (!foo (1));
> +
> +template <class... T> requires (C1<T> && ...)
> +constexpr bool bar (T...) { return false; };
> +template <class... T> requires (C2<T> && ...)
> +constexpr bool bar (T...) { return true; };
> +
> +static_assert (bar (0));		// { dg-error "call of overloaded 'bar\\\(int\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (bar ());			// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (bar (1, 2));		// { dg-error "call of overloaded 'bar\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> +
> +template <class... T> requires (C1<T> && ...)
> +constexpr bool baz (T...) { return false; };
> +template <class... T> requires (... && (C1<T> && true))
> +constexpr bool baz (T...) { return true; };
> +
> +static_assert (baz (0));		// { dg-error "call of overloaded 'baz\\\(int\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (baz ());			// { dg-error "call of overloaded 'baz\\\(\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (baz (1, 2));		// { dg-error "call of overloaded 'baz\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> +
> +template <typename... T> requires (C1<T> || ... || true)
> +constexpr bool qux (T...) { return false; };
> +template <typename... T> requires (C2<T> && ... && true)
> +constexpr bool qux (T...) { return true; };
> +
> +static_assert (qux (0));		// { dg-error "call of overloaded 'qux\\\(int\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (qux ());			// { dg-error "call of overloaded 'qux\\\(\\\)' is ambiguous" "" { target c++23_down } }
> +
> +constexpr bool quux (C1 auto...) { return false; }
> +constexpr bool quux (C3 auto...) { return true; }
> +
> +static_assert (quux ());		// { dg-error "call of overloaded 'quux\\\(\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (quux (0, 0));		// { dg-error "call of overloaded 'quux\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (!quux (0L, 0));
> +
> +template <C1... T>
> +constexpr bool corge (C1 auto...) { return false; }
> +template <C3... T>
> +constexpr bool corge (C3 auto...) { return true; }
> +
> +static_assert (corge ());		// { dg-error "call of overloaded 'corge\\\(\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (corge (0, 0));		// { dg-error "call of overloaded 'corge\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (!corge (0L, 0));
> --- gcc/testsuite/g++.dg/cpp26/fold-constr3.C.jj	2024-07-26 08:34:40.535867087 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr3.C	2024-07-26 11:12:09.129830502 +0200
> @@ -0,0 +1,15 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +template <typename ...V> struct A;
> +struct Thingy {
> +  static constexpr int compare (const Thingy &) { return 1; }
> +};
> +template <typename ...T, typename ...U>
> +void f (A<T ...> *, A<U ...> *)
> +requires (T::compare (U{}) && ...);	// { dg-error "has type 'int', not 'bool'" "" { target c++26 } }
> +void
> +g (A<Thingy, Thingy> *ap)
> +{
> +  f (ap, ap);				// { dg-error "no matching function for call to" "" { target c++26 } }
> +}
> --- gcc/testsuite/g++.dg/cpp26/fold-constr4.C.jj	2024-07-26 08:34:40.535867087 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr4.C	2024-07-26 08:34:40.535867087 +0200
> @@ -0,0 +1,48 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +template <class T> concept C1 = true;
> +template <class T> concept C2 = C1<T> && true;
> +
> +template <class... T> requires (C1<T> && ...)
> +constexpr bool foo (T...) { return false; };
> +template <class... T> requires (C2<T> || ...)
> +constexpr bool foo (T...) { return true; };
> +
> +static_assert (foo (0));	// { dg-error "call of overloaded 'foo\\\(int\\\)' is ambiguous" }
> +
> +template <class... T> requires (C1<T> || ...)
> +constexpr bool bar (T...) { return false; };
> +template <class... T> requires (C2<T> && ...)
> +constexpr bool bar (T...) { return true; };
> +
> +static_assert (bar (0));	// { dg-error "call of overloaded 'bar\\\(int\\\)' is ambiguous" }
> +
> +template <class... T> requires (C1<T> || ...)
> +constexpr bool baz (T...) { return false; };
> +template <class... T> requires (C2<T> || ...)
> +constexpr bool baz (T...) { return true; };
> +
> +static_assert (baz (0));	// { dg-error "call of overloaded 'baz\\\(int\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (baz ());		// { dg-error "no matching function for call to 'baz\\\(\\\)'" }
> +static_assert (baz (1, 2));	// { dg-error "call of overloaded 'baz\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> +
> +template <class... T>
> +struct U {
> +  template <class... V> requires (... && C1<V>)
> +  static constexpr bool foo () { return false; }
> +  template <class... V> requires (... && C2<V>)
> +  static constexpr bool foo () { return true; }
> +  template <class... V> requires (... && C1<T>)
> +  static constexpr bool bar () { return false; }
> +  template <class... V> requires (... && C2<T>)
> +  static constexpr bool bar () { return true; }
> +  template <class... V> requires (... && C1<V>)
> +  static constexpr bool baz () { return false; }
> +  template <class... V> requires (... && C2<T>)
> +  static constexpr bool baz () { return true; }
> +};
> +
> +static_assert (U<int>::foo<int> ());	// { dg-error "call of overloaded 'foo\\\(\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (U<int>::bar<int> ());	// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (U<int>::baz<int> ());	// { dg-error "call of overloaded 'baz\\\(\\\)' is ambiguous" }
> --- gcc/testsuite/g++.dg/cpp26/fold-constr5.C.jj	2024-07-26 08:34:40.535867087 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr5.C	2024-07-26 13:37:12.802463309 +0200
> @@ -0,0 +1,77 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +struct A {
> +  using type = int;
> +};
> +struct B {
> +  using type = long;
> +};
> +
> +template <class T> concept C = sizeof (T) < sizeof (int) * 64;
> +
> +template <class... T> requires (C<typename T::type> && ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool foo () { return true; };
> +
> +static_assert (foo <> ());
> +static_assert (foo <A> ());
> +static_assert (foo <B, A, A, B> ());
> +static_assert (foo <int> ());					// { dg-error "no matching function for call" }
> +static_assert (foo <B, long> ());				// { dg-error "no matching function for call" }
> +static_assert (foo <unsigned, A, A> ());			// { dg-error "no matching function for call" }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +
> +template <class T, class... U> requires (C<typename U::type> && ... && C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool bar () { return true; };
> +
> +static_assert (bar <A> ());
> +static_assert (bar <B, A, A, B> ());
> +static_assert (bar <int> ());					// { dg-error "no matching function for call" }
> +static_assert (bar <B, long> ());				// { dg-error "no matching function for call" }
> +static_assert (bar <unsigned, A, A> ());			// { dg-error "no matching function for call" }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +
> +template <class T, class... U> requires (C<typename T::type> && ... && C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool baz () { return true; };
> +
> +static_assert (baz <A> ());
> +static_assert (baz <B, A, A, B> ());
> +static_assert (baz <int> ());					// { dg-error "no matching function for call" }
> +static_assert (baz <B, long> ());				// { dg-error "no matching function for call" }
> +static_assert (baz <unsigned, A, A> ());			// { dg-error "no matching function for call" }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +
> +template <class... T> requires (C<typename T::type> || ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool qux () { return true; };
> +
> +static_assert (qux <> ());					// { dg-error "no matching function for call" }
> +static_assert (qux <A> ());
> +static_assert (qux <B, A, A, B> ());
> +static_assert (qux <int> ());					// { dg-error "no matching function for call" }
> +static_assert (qux <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (qux <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> +
> +template <class T, class... U> requires (C<typename U::type> || ... || C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool corge () { return true; };
> +
> +static_assert (corge <A> ());
> +static_assert (corge <B, A, A, B> ());
> +static_assert (corge <int> ());					// { dg-error "no matching function for call" }
> +static_assert (corge <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (corge <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> +
> +template <class T, class... U> requires (C<typename T::type> || ... || C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool garply () { return true; };
> +
> +static_assert (garply <A> ());
> +static_assert (garply <B, A, A, B> ());
> +static_assert (garply <int> ());				// { dg-error "no matching function for call" }
> +static_assert (garply <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (garply <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> --- gcc/testsuite/g++.dg/cpp26/fold-constr6.C.jj	2024-07-26 08:34:40.535867087 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr6.C	2024-07-26 14:32:28.121132795 +0200
> @@ -0,0 +1,20 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +template <class T> concept C1 = true;
> +template <class T> concept C2 = C1<T> && true;
> +
> +template <class... T>
> +struct U {
> +  template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
> +  static constexpr bool foo () { return false; }
> +  template <class... V> requires ((C2<T> && ...) && ... && C2<V>)
> +  static constexpr bool foo () { return true; }
> +  template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
> +  static constexpr bool bar () { return false; }
> +  template <class... V> requires ((C2<V> && ...) && ... && C2<T>)
> +  static constexpr bool bar () { return true; }
> +};
> +
> +static_assert (U<int>::foo<int> ());	// { dg-error "call of overloaded 'foo\\\(\\\)' is ambiguous" "" { target c++23_down } }
> +static_assert (U<int>::bar<int> ());	// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
> --- gcc/testsuite/g++.dg/cpp26/fold-constr7.C.jj	2024-07-26 11:35:01.499085336 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr7.C	2024-07-26 15:11:46.186674513 +0200
> @@ -0,0 +1,11 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +template <class T> concept C1 = true;
> +template <class T> concept C2 = C1<T> && true;
> +
> +template <class... T> requires ((((sizeof (T) + ...) < 8 * sizeof (int)) && C2<T>) && ...)
> +constexpr bool foo (T...) { return true; };
> +
> +static_assert (foo (0));
> +static_assert (foo (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); // { dg-error "no matching function for call" }
> --- gcc/testsuite/g++.dg/cpp26/fold-constr8.C.jj	2024-07-26 15:05:59.636183243 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr8.C	2024-07-26 15:05:15.027759668 +0200
> @@ -0,0 +1,22 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +template <class T> concept C = __is_same (T, int);
> +
> +template <class ...T>
> +struct A {};
> +
> +template <class ...T, class ...U> requires ((C<T> && C<U>) && ...)	// { dg-error "mismatched argument pack lengths while expanding '\\\(C<T> \\\&\\\& C<U>\\\)'" "" { target c++23_down } }
> +constexpr bool foo (A<T...>, A<U...>) { return true; };
> +// { dg-message "fold expanded constraint not satisfied because of pack length mismatch" "" { target c++26 } .-2 }
> +// { dg-message "'U' has length 3" "" { target c++26 } .-3 }
> +// { dg-message "'T' has length 2" "" { target c++26 } .-4 }
> +
> +static_assert (foo (A<int, int, int> {}, A<int, int, int> {}));
> +static_assert (foo (A<int, int> {}, A<int, int, int> {}));	// { dg-error "no matching function for call to" }
> +
> +template <class ...T> requires (C<T> || ...)			// { dg-message "fold expanded constraint not satisfied because of empty pack with '||' fold operator" "" { target c++26 } }
> +constexpr bool bar (T...) { return true; };
> +
> +static_assert (bar (0));
> +static_assert (bar ());						// { dg-error "no matching function for call to" }
> --- gcc/testsuite/g++.dg/cpp26/fold-constr9.C.jj	2024-07-26 15:12:12.570331261 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr9.C	2024-07-26 15:12:06.613408758 +0200
> @@ -0,0 +1,11 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +template <class T> concept C = __is_same (T, int);
> +
> +template <class... T> requires ((C<T> && ...) && ... && C<T>)
> +constexpr bool foo (T...) { return true; }
> +
> +static_assert (foo ());
> +static_assert (foo (1, 2, 3));
> +static_assert (foo (1L, 2L)); // { dg-error "no matching function for call" }
> --- gcc/testsuite/g++.dg/cpp26/fold-constr10.C.jj	2024-07-26 16:02:26.099421284 +0200
> +++ gcc/testsuite/g++.dg/cpp26/fold-constr10.C	2024-07-26 16:03:35.256534023 +0200
> @@ -0,0 +1,67 @@
> +// P2963R3 - Ordering of constraints involving fold expressions
> +// { dg-do compile { target c++20 } }
> +
> +struct A {
> +  using type = int;
> +};
> +struct B {
> +  using type = long;
> +};
> +
> +template <class T> concept C = true;
> +
> +template <class... T> requires (C<typename T::type> && ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool foo () { return true; };
> +
> +static_assert (foo <> ());
> +static_assert (foo <A> ());
> +static_assert (foo <B, A, A, B> ());
> +static_assert (foo <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (foo <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (foo <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> +
> +template <class T, class... U> requires (C<typename U::type> && ... && C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool bar () { return true; };
> +
> +static_assert (bar <A> ());
> +static_assert (bar <B, A, A, B> ());
> +static_assert (bar <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (bar <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (bar <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> +
> +template <class T, class... U> requires (C<typename T::type> && ... && C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool baz () { return true; };
> +
> +static_assert (baz <A> ());
> +static_assert (baz <B, A, A, B> ());
> +static_assert (baz <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (baz <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (baz <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> +
> +template <class... T> requires (C<typename T::type> || ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool qux () { return true; };
> +
> +static_assert (qux <> ());					// { dg-error "no matching function for call" }
> +static_assert (qux <A> ());
> +static_assert (qux <B, A, A, B> ());
> +static_assert (qux <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (qux <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (qux <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> +
> +template <class T, class... U> requires (C<typename U::type> || ... || C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool corge () { return true; };
> +
> +static_assert (corge <A> ());
> +static_assert (corge <B, A, A, B> ());
> +static_assert (corge <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (corge <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (corge <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> +
> +template <class T, class... U> requires (C<typename T::type> || ... || C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> +constexpr bool garply () { return true; };
> +
> +static_assert (garply <A> ());
> +static_assert (garply <B, A, A, B> ());
> +static_assert (garply <int> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (garply <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> +static_assert (garply <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> 
> 	Jakub
> 
>
Patrick Palka July 26, 2024, 6:35 p.m. UTC | #2
On Fri, 26 Jul 2024, Patrick Palka wrote:

> On Fri, 26 Jul 2024, Jakub Jelinek wrote:
> 
> > Hi!
> > 
> > I've tried to implement the C++26 fold expanded constraints paper but ran
> > into issues (see below).  Would appreciate some guidance/help, I'm afraid
> > I'm stuck.
> > 
> > The patch introduces a FOLD_CONSTR tree to represent fold expanded
> > constraints, normalizes for C++26 some {U,BI}NARY_{LEFT,RIGHT}_FOLD_EXPR
> > into those (+ CONJ_CONSTR or DISJ_CONSTR for the binary ones), attempts
> > to handle their satisfaction (that is the unresolved issue) and handle it
> > in the subsumption checking code.
> > 
> > Most of the newly added tests pass, compared to what clang++ trunk (which
> > claims to implement this paper) there are some differences:
> > static_assert (bar <int> ()); in cpp26/fold-constr5.C is accepted
> > by clang in C++26 mode and rejected by GCC.  I believe GCC is right,
> > C<typename U::type> && ... && C<typename T::type>
> > where U is an empty pack should be normalized to
> > (C<typename U::type> /\ ...) /\ C<typename T::type>
> > so I think (C<typename U::type> /\ ...) is satisfied but
> > C<typename T::type> should not as int::type is invalid, so the overall
> > result should be not satisfied.
> > Another difference is
> > static_assert (U<int>::bar<int> ()); in cpp26/fold-constr6.C, which is
> > rejected by clang in C++26 mode and accepted by GCC.
> > This is for
> >   template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
> >   static constexpr bool bar () { return false; }
> >   template <class... V> requires ((C2<V> && ...) && ... && C2<T>)
> >   static constexpr bool bar () { return true; }
> > where both T and V are packs, I believe the first should be normalized to
> > (C1<T> /\ ...) && (C1<V> /\ ...)
> > and the second to
> > (C2<V> /\ ...) && (C2<T> /\ ...)
> > where C2<int> subsumes C1<int>.  clang++ accepts when it is written in
> > the other order, so when normalized to
> > (C2<T> /\ ...) && (C2<V> /\ ...)
> > and in that case GCC and clang agree that the latter subsumes the former.
> > C2<V> /\ ... subsumes C1<V> /\ ...
> > and
> > C2<T> /\ ... subsumes C1<T> /\ ...
> > and
> > C2<T> /\ ... does not subsume C1<V> /\ ...
> > and
> > C2<V> /\ ... does not subsume C1<T> /\ ...
> > but overall I believe in either order the result is subsumes.
> > Another difference is in the cpp26/fold-constr10.C testcase, which is
> > essentially PR116106 and Patrick claims that unused template parameters in
> > the mapping aren't substituted into.
> > Also, the new code affects not just explicitly written fold expressions
> > in the constraints, but also implicitly created ones (say for
> > template <Concept... T> and similar), but here the GCC patch agrees with
> > clang trunk.
> > 
> > Anyway, the way I've attempted to implement satisfy_fold is through updating
> > the args vector or vectors (COW) to change parameter pack into parameter
> > pack's element which is currently checked for satisfaction.
> > That works most of the time, when all the occurrences of the template
> > parameter(s) recorded in PACK_EXPANSION_PARAMETER_PACKS (FOLD_EXPR_PACK (t))
> > of the original *_FOLD_EXPR are to be replaced by the pack element, which
> > should be fine for any such occurences unless they appear in another
> > expansion pack with the same template parameter.
> > The instantiation of those nested {TYPE,EXPR}_PACK_EXPANSION need to be able
> > to access the original whole pack rather than just one of its elements.
> > And some atomic constraints e.g. can refer to both; I've tried to
> > write this in cpp26/fold-constr7.C testcase (which is the only one
> > from newly added that FAILs with C++26):
> > template <class... T> requires ((((sizeof (T) + ...) < 8 * sizeof (int)) && C2<T>) && ...)
> > constexpr bool foo (T...) { return true; };
> > here actually the ((sizeof (T) + ...) < 8 * sizeof (int)) atomic constraint
> > only needs the whole T pack and C2<T> atomic constraint only needs the T's
> > pack element, but guess it can be also changed so that one atomic constraint
> > needs both whole pack and pack element.
> > Do you have some suggestions on how to handle this instead?
> 
> IIUC the way gen_elem_of_pack_expansion_instantiation handles this for
> ordinary pack expnasions is by replacing each ARGUMENT_PACK with an
> ARGUMENT_PACK_SELECT.  This ARGUMENT_PACK_SELECT contains the entire
> pack as well as the current index into the pack, and it essentially
> acts as an overloaded template argument that's treated either as a
> single pack element or as the entire pack itself as needed.

Ah but by using ARGUMENT_PACK_SELECT I bet you'll run into an
iterative_hash_template_arg ICE when the satisfaction cache tries to
hash it.

IMHO we should allow hashing/comparing ARGUMENT_PACK_SELECT, at least
"immutable" ones whose index isn't expected to change from under us,
accordin to some flag maybe.  (the ARGUMENT_PACK_SELECTs created from
gen_elem_of_pack_expansion_instantiation are reused/modified across
iterations so those wouldn't be safe to store in a hash table.)

> 
> On that note I wonder if there's any opportunity for code reuse from
> gen_elem_of_pack_expansion_instantiation / tsubst_pack_expansion?
> 
> > Besides
> > +FAIL: g++.dg/cpp26/fold-constr7.C  -std=c++26 (test for excess errors)
> > I spoke about there are some regressions on existing tests:
> > +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 36)
> > +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 38)
> > +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 43)
> > +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 45)
> > +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26  (test for errors, line 47)
> > +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26 (internal compiler error: tree check: expected type_argument_pack or nontype_argument_pack, have integer_type in satisfy_fold, at cp/constraint.cc:2940)
> > +FAIL: g++.dg/cpp2a/concepts-fn3.C  -std=c++26 (test for excess errors)
> > +FAIL: g++.dg/cpp2a/concepts-pr67860.C  -std=c++26 (internal compiler error: tree check: expected type_argument_pack or nontype_argument_pack, have integer_type in satisfy_fold, at cp/constraint.cc:2940)
> > +FAIL: g++.dg/cpp2a/concepts-pr67860.C  -std=c++26 (test for excess errors)
> > +FAIL: g++.dg/warn/Wdangling-reference17.C  -std=gnu++26 (internal compiler error: in keep_template_parm, at cp/pt.cc:10975)
> > +FAIL: g++.dg/warn/Wdangling-reference17.C  -std=gnu++26 (test for excess errors)
> > there concepts-fn3.C, concepts-pr67860.C are one ICE clearly caused by the
> > problem with the need to use whole pack inside of something that needs to
> > use just pack element inside of an fold expanded constraint,
> > Wdangling-reference17.C is an ICE that repeats then in
> > FAIL: 24_iterators/const_iterator/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: 24_iterators/const_iterator/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: 24_iterators/move_iterator/lwg3391.cc  -std=gnu++26 (test for excess errors)
> > FAIL: 25_algorithms/fold_left/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: 25_algorithms/fold_left/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: 25_algorithms/fold_right/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: 25_algorithms/fold_right/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/100479.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/100479.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 105)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 106)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 108)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 110)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 112)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 113)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 115)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 117)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 119)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 120)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 121)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 122)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 91)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 92)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 93)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 94)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 96)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 97)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 98)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26  (test for errors, line 99)
> > FAIL: std/ranges/adaptors/100577.cc  -std=gnu++26 (test for excess errors)
> > FAIL: std/ranges/adaptors/116038.cc  -std=gnu++26 (test for excess errors)
> > FAIL: std/ranges/adaptors/95322.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/95322.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/99433.cc  -std=gnu++26 (test for excess errors)
> > FAIL: std/ranges/adaptors/adjacent/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/adjacent/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/adjacent_transform/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/adjacent_transform/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/all.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/all.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/as_const/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/as_const/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/as_rvalue/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/as_rvalue/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/chunk/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/chunk/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/chunk_by/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/chunk_by/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/conditionally_borrowed.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/conditionally_borrowed.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/drop.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/drop.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/drop_while.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/drop_while.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/elements.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/elements.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/filter.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/filter.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/join.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/join.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/join_with/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/join_with/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/lazy_split.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/lazy_split.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26  (test for errors, line 33)
> > FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26  (test for errors, line 41)
> > FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26  (test for errors, line 42)
> > FAIL: std/ranges/adaptors/lazy_split_neg.cc  -std=gnu++26 (test for excess errors)
> > FAIL: std/ranges/adaptors/lwg3286.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/lwg3286.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/lwg3715.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/lwg3715.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/p1739.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/p1739.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/p2281.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/p2281.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/p2770r0.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/p2770r0.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/reverse.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/reverse.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/slide/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/slide/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/split.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/split.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/stride/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/stride/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/take.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/take.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/take_while.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/take_while.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/adaptors/transform.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/adaptors/transform.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/cartesian_product/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/cartesian_product/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/concat/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/concat/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 434)
> > FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 435)
> > FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 436)
> > FAIL: std/ranges/conv/1.cc  -std=gnu++26  (test for warnings, line 437)
> > FAIL: std/ranges/conv/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/conv/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/istream_view.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/istream_view.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/p2259.cc  -std=gnu++26 (test for excess errors)
> > FAIL: std/ranges/p2325.cc  -std=gnu++26 (test for excess errors)
> > FAIL: std/ranges/p2367.cc  -std=gnu++26 (test for excess errors)
> > FAIL: std/ranges/range_adaptor_closure.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/range_adaptor_closure.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/repeat/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/repeat/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/zip/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/zip/1.cc  -std=gnu++26 compilation failed to produce executable
> > FAIL: std/ranges/zip_transform/1.cc  -std=gnu++26 (test for excess errors)
> > UNRESOLVED: std/ranges/zip_transform/1.cc  -std=gnu++26 compilation failed to produce executable
> > inside of libstdc++ all at c++26 and I think is most likely the same
> > problem, just different exact ICE.
> > 
> > Bootstrapped/regtested on x86_64-linux and i686-linux with the above
> > mentioned regressions.
> > 
> > 2024-07-26  Jakub Jelinek  <jakub@redhat.com>
> > 
> > 	PR c++/115746
> > gcc/cp/
> > 	* cp-tree.def: Implement C++26 P2963R3 - Ordering of constraints
> > 	involving fold expressions.
> > 	(FOLD_CONSTR): New tree code.
> > 	* cp-tree.h (CONSTR_P): Handle FOLD_CONSTR.
> > 	(CONSTR_CHECK): Include FOLD_CONSTR.
> > 	(FOLD_CONSTR_EXPR): Define.
> > 	(FOLD_CONSTR_PACKS): Define.
> > 	(FOLD_CONSTR_DISJ_P): Define.
> > 	* cp-objcp-common.cc (cp_common_init_ts): Handle FOLD_CONSTR.
> > 	* constraint.cc (normalize_fold_expr): New function.
> > 	(normalize_expression): Use it for {U,BI}NARY_{LEFT,RIGHT}_FOLD_EXPR.
> > 	(fold_constraints_identical_p): New function.
> > 	(constraints_equivalent_p): Use it for FOLD_CONSTR.
> > 	(add_constraint): Handle FOLD_CONSTR.
> > 	(tsubst_parameter_mapping): Undo template_parm_to_arg returning
> > 	parameter packs if inside fold expanded constraint.  Formatting fix
> > 	in wrapper.
> > 	(satisfy_fold): New function.
> > 	(satisfy_constraint_r): Handle FOLD_CONSTR.
> > 	* error.cc (dump_expr): Likewise.
> > 	* logic.cc (struct clause): Add m_fold member.
> > 	(clause::clause): Handle FOLD_CONSTR, for copy ctor copy over m_fold.
> > 	(clause::replace, clause::insert): Handle FOLD_CONSTR.
> > 	(clause::folds): New method.
> > 	(atomic_p): Also return true for FOLD_CONSTR.
> > 	(decompose_atom): Adjust function comment.
> > 	(derive_fold_proof): New function.
> > 	(derive_proof): Handle FOLD_CONSTR.  Change default: case to
> > 	case ATOMIC_CONSTR, for default: add gcc_unreachable ().  Formatting
> > 	fixes.
> > 	(subsumes_constraints_nonnull): Use auto_cond_timevar instead of
> > 	auto_timevar.
> > 	* cxx-pretty-print.cc (cxx_pretty_printer::expression): Handle
> > 	FOLD_CONSTR.
> > 	(pp_cxx_fold_expanded_constraint): New function.
> > 	(pp_cxx_constraint): Handle FOLD_CONSTR.
> > gcc/testsuite/
> > 	* g++.dg/concepts/diagnostic3.C: Guard diagnostics on c++23_down.
> > 	* g++.dg/concepts/variadic2.C: Likewise.
> > 	* g++.dg/concepts/variadic4.C: Likewise.
> > 	* g++.dg/cpp2a/concepts-requires33.C: Expect another error for c++26.
> > 	* g++.dg/cpp26/fold-constr1.C: New test.
> > 	* g++.dg/cpp26/fold-constr2.C: New test.
> > 	* g++.dg/cpp26/fold-constr3.C: New test.
> > 	* g++.dg/cpp26/fold-constr4.C: New test.
> > 	* g++.dg/cpp26/fold-constr5.C: New test.
> > 	* g++.dg/cpp26/fold-constr6.C: New test.
> > 	* g++.dg/cpp26/fold-constr7.C: New test.
> > 	* g++.dg/cpp26/fold-constr8.C: New test.
> > 	* g++.dg/cpp26/fold-constr9.C: New test.
> > 	* g++.dg/cpp26/fold-constr10.C: New test.
> > 
> > --- gcc/cp/cp-tree.def.jj	2024-07-25 21:34:46.791268760 +0200
> > +++ gcc/cp/cp-tree.def	2024-07-26 09:20:05.256197019 +0200
> > @@ -538,6 +538,12 @@ DEFTREECODE (ATOMIC_CONSTR, "atomic_cons
> >  DEFTREECODE (CONJ_CONSTR, "conj_constr", tcc_expression, 2)
> >  DEFTREECODE (DISJ_CONSTR, "disj_constr", tcc_expression, 2)
> >  
> > +/* Fold expanded constraint.
> > +   CONSTR_INFO provides source info to support diagnostics.
> > +   FOLD_CONSTR_EXPR is the constraint embedded in it,
> > +   FOLD_CONSTR_PACKS are the packs expanded by it.  */
> > +DEFTREECODE (FOLD_CONSTR, "fold_constr", tcc_expression, 2)
> > +
> >  /* The co_await expression is used to support coroutines.
> >  
> >    Op 0 is the cast expresssion (potentially modified by the
> > --- gcc/cp/cp-tree.h.jj	2024-07-26 08:34:18.104160097 +0200
> > +++ gcc/cp/cp-tree.h	2024-07-26 09:19:10.626908903 +0200
> > @@ -1679,11 +1679,12 @@ check_constraint_info (tree t)
> >  #define CONSTR_P(NODE)                  \
> >    (TREE_CODE (NODE) == ATOMIC_CONSTR    \
> >     || TREE_CODE (NODE) == CONJ_CONSTR   \
> > -   || TREE_CODE (NODE) == DISJ_CONSTR)
> > +   || TREE_CODE (NODE) == DISJ_CONSTR	\
> > +   || TREE_CODE (NODE) == FOLD_CONSTR)
> >  
> >  /* Valid for any normalized constraint.  */
> >  #define CONSTR_CHECK(NODE) \
> > -  TREE_CHECK3 (NODE, ATOMIC_CONSTR, CONJ_CONSTR, DISJ_CONSTR)
> > +  TREE_CHECK4 (NODE, ATOMIC_CONSTR, CONJ_CONSTR, DISJ_CONSTR, FOLD_CONSTR)
> >  
> >  /* The CONSTR_INFO stores normalization data for a constraint. It refers to
> >     the original expression and the expression or declaration
> > @@ -1724,6 +1725,18 @@ check_constraint_info (tree t)
> >  #define ATOMIC_CONSTR_EXPR(NODE) \
> >    CONSTR_EXPR (ATOMIC_CONSTR_CHECK (NODE))
> >  
> > +/* The constraint embedded in FOLD_CONSTR.  */
> > +#define FOLD_CONSTR_EXPR(NODE) \
> > +  TREE_OPERAND (FOLD_CONSTR_CHECK (NODE), 0)
> > +
> > +/* List of packs expanded by it.  */
> > +#define FOLD_CONSTR_PACKS(NODE) \
> > +  TREE_OPERAND (FOLD_CONSTR_CHECK (NODE), 1)
> > +
> > +/* True if FOLD_CONSTR has fold-operator ||, false for &&.  */
> > +#define FOLD_CONSTR_DISJ_P(NODE) \
> > +  TREE_STATIC (FOLD_CONSTR_CHECK (NODE))
> > +
> >  /* Whether a PARM_DECL represents a local parameter in a
> >     requires-expression.  */
> >  #define CONSTRAINT_VAR_P(NODE) \
> > --- gcc/cp/cp-objcp-common.cc.jj	2024-07-25 21:34:46.791268760 +0200
> > +++ gcc/cp/cp-objcp-common.cc	2024-07-26 08:34:40.532867127 +0200
> > @@ -705,6 +705,7 @@ cp_common_init_ts (void)
> >    MARK_TS_EXP (CONJ_CONSTR);
> >    MARK_TS_EXP (DISJ_CONSTR);
> >    MARK_TS_EXP (ATOMIC_CONSTR);
> > +  MARK_TS_EXP (FOLD_CONSTR);
> >    MARK_TS_EXP (NESTED_REQ);
> >    MARK_TS_EXP (REQUIRES_EXPR);
> >    MARK_TS_EXP (SIMPLE_REQ);
> > --- gcc/cp/constraint.cc.jj	2024-07-25 21:34:46.791268760 +0200
> > +++ gcc/cp/constraint.cc	2024-07-26 15:03:04.438445216 +0200
> > @@ -852,6 +852,39 @@ normalize_atom (tree t, tree args, norm_
> >    return atom;
> >  }
> >  
> > +/* Normalize {UNARY,BINARY}_{LEFT,RIGHT}_FOLD_EXPR.  */
> > +
> > +static tree
> > +normalize_fold_expr (tree t, tree args, norm_info info)
> > +{
> > +  if (cxx_dialect < cxx26
> > +      || (FOLD_EXPR_OP (t) != TRUTH_ANDIF_EXPR
> > +	  && FOLD_EXPR_OP (t) != TRUTH_ORIF_EXPR)
> > +      || FOLD_EXPR_MODIFY_P (t))
> > +    return normalize_atom (t, args, info);
> > +
> > +  tree norm
> > +    = normalize_expression (PACK_EXPANSION_PATTERN (FOLD_EXPR_PACK (t)),
> > +			    args, info);
> > +  tree ci = (info.generate_diagnostics
> > +	     ? build_tree_list (t, info.context) : NULL_TREE);
> > +  tree params = PACK_EXPANSION_PARAMETER_PACKS (FOLD_EXPR_PACK (t));
> > +  tree ret = build2 (FOLD_CONSTR, ci, norm, params);
> > +  if (FOLD_EXPR_OP (t) == TRUTH_ORIF_EXPR)
> > +    FOLD_CONSTR_DISJ_P (ret) = 1;
> > +  if (TREE_CODE (t) == BINARY_LEFT_FOLD_EXPR
> > +      || TREE_CODE (t) == BINARY_RIGHT_FOLD_EXPR)
> > +    {
> > +      tree init = normalize_expression (FOLD_EXPR_INIT (t), args, info);
> > +      tree_code code
> > +	= FOLD_EXPR_OP (t) == TRUTH_ANDIF_EXPR ? CONJ_CONSTR : DISJ_CONSTR;
> > +      if (TREE_CODE (t) == BINARY_LEFT_FOLD_EXPR)
> > +	std::swap (ret, init);
> > +      return build2 (code, ci, ret, init);
> > +    }
> > +  return ret;
> > +}
> > +
> >  /* Returns the normal form of an expression.  */
> >  
> >  static tree
> > @@ -869,6 +902,11 @@ normalize_expression (tree t, tree args,
> >        return normalize_logical_operation (t, args, CONJ_CONSTR, info);
> >      case TRUTH_ORIF_EXPR:
> >        return normalize_logical_operation (t, args, DISJ_CONSTR, info);
> > +    case UNARY_LEFT_FOLD_EXPR:
> > +    case UNARY_RIGHT_FOLD_EXPR:
> > +    case BINARY_LEFT_FOLD_EXPR:
> > +    case BINARY_RIGHT_FOLD_EXPR:
> > +      return normalize_fold_expr (t, args, info);
> >      default:
> >        return normalize_atom (t, args, info);
> >      }
> > @@ -1055,6 +1093,28 @@ atomic_constraints_identical_p (tree t1,
> >    return true;
> >  }
> >  
> > +/* Compare two fold expanded constraints T1 and T2.  */
> > +
> > +static bool
> > +fold_constraints_identical_p (tree t1, tree t2)
> > +{
> > +  gcc_assert (TREE_CODE (t1) == FOLD_CONSTR);
> > +  gcc_assert (TREE_CODE (t2) == FOLD_CONSTR);
> > +
> > +  if (FOLD_CONSTR_DISJ_P (t1) != FOLD_CONSTR_DISJ_P (t2))
> > +    return false;
> > +
> > +  tree p1 = FOLD_CONSTR_PACKS (t1);
> > +  tree p2 = FOLD_CONSTR_PACKS (t2);
> > +  for (; p1 && p2; p1 = TREE_CHAIN (p1), p2 = TREE_CHAIN (p2))
> > +    if (!template_args_equal (TREE_VALUE (p1), TREE_VALUE (p2)))
> > +      return false;
> > +  if (p1 || p2)
> > +    return false;
> > +  return constraints_equivalent_p (FOLD_CONSTR_EXPR (t1),
> > +				   FOLD_CONSTR_EXPR (t2));
> > +}
> > +
> >  /* True if T1 and T2 are equivalent, meaning they have the same syntactic
> >     structure and all corresponding constraints are identical.  */
> >  
> > @@ -1082,6 +1142,10 @@ constraints_equivalent_p (tree t1, tree
> >        if (!atomic_constraints_identical_p (t1, t2))
> >  	return false;
> >        break;
> > +    case FOLD_CONSTR:
> > +      if (!fold_constraints_identical_p (t1, t2))
> > +	return false;
> > +      break;
> >      default:
> >        gcc_unreachable ();
> >      }
> > @@ -1126,6 +1190,10 @@ add_constraint (tree t, hash& h)
> >      case ATOMIC_CONSTR:
> >        h.merge_hash (hash_atomic_constraint (t));
> >        break;
> > +    case FOLD_CONSTR:
> > +      h.add_int (FOLD_CONSTR_DISJ_P (t));
> > +      add_constraint (FOLD_CONSTR_EXPR (t), h);
> > +      break;
> >      default:
> >        gcc_unreachable ();
> >      }
> > @@ -2163,6 +2231,29 @@ tsubst_parameter_mapping (tree map, tree
> >        tree parm = TREE_VALUE (p);
> >        tree arg = TREE_PURPOSE (p);
> >        tree new_arg;
> > +      if (cxx_dialect >= cxx26 && ARGUMENT_PACK_P (arg))
> > +	{
> > +	  /* template_parm_to_arg for packs wraps the template parm
> > +	     with {,NON}TYPE_ARGUMENT_PACK with pack expansion.
> > +	     For packs expanded by fold expanded constraint undo this
> > +	     here.  */
> > +	  tree v = ARGUMENT_PACK_ARGS (arg);
> > +	  if (TREE_VEC_LENGTH (v) == 1
> > +	      && PACK_EXPANSION_P (TREE_VEC_ELT (v, 0)))
> > +	    {
> > +	      tree t = PACK_EXPANSION_PATTERN (TREE_VEC_ELT (v, 0));
> > +	      tree e = STRIP_REFERENCE_REF (t);
> > +	      if (TEMPLATE_PARM_P (e))
> > +		{
> > +		  int level;
> > +		  int index;
> > +		  template_parm_level_and_index (e, &level, &index);
> > +		  tree a = TMPL_ARG (args, level, index);
> > +		  if (!ARGUMENT_PACK_P (a))
> > +		    arg = t;
> > +		}
> > +	    }
> > +	}
> >        if (ARGUMENT_PACK_P (arg))
> >  	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
> >        else
> > @@ -2187,7 +2278,8 @@ tsubst_parameter_mapping (tree map, tree
> >  }
> >  
> >  tree
> > -tsubst_parameter_mapping (tree map, tree args, tsubst_flags_t complain, tree in_decl)
> > +tsubst_parameter_mapping (tree map, tree args, tsubst_flags_t complain,
> > +			  tree in_decl)
> >  {
> >    return tsubst_parameter_mapping (map, args, subst_info (complain, in_decl));
> >  }
> > @@ -2831,6 +2924,101 @@ satisfy_atom (tree t, tree args, sat_inf
> >    return cache.save (inst_cache.save (result));
> >  }
> >  
> > +/* Compute the satisfaction of a fold expanded constraint.  */
> > +
> > +static tree
> > +satisfy_fold (tree t, tree args, sat_info info)
> > +{
> > +  tree orig_args = args;
> > +  int len = -1;
> > +  auto_vec <int, 8> indices;
> > +  for (tree p = FOLD_CONSTR_PACKS (t); p; p = TREE_CHAIN (p))
> > +    {
> > +      int level, index;
> > +      template_parm_level_and_index (TREE_VALUE (p), &level, &index);
> > +      tree a = TMPL_ARG (orig_args, level, index);
> > +      int this_len = TREE_VEC_LENGTH (ARGUMENT_PACK_ARGS (a));
> > +      if (len == -1)
> > +	len = this_len;
> > +      else if (this_len != len)
> > +	{
> > +	  if (info.diagnose_unsatisfaction_p ())
> > +	    {
> > +	      diagnosing_failed_constraint failure (t, args, info.noisy ());
> > +	      tree first = TREE_VALUE (FOLD_CONSTR_PACKS (t));
> > +	      cp_expr fold_expr = CONSTR_EXPR (t);
> > +	      inform (fold_expr.get_location (),
> > +		      "fold expanded constraint not satisfied because "
> > +		      "of pack length mismatch");
> > +	      if (TREE_CODE (first) == TYPE_PACK_EXPANSION)
> > +		inform (fold_expr.get_location (),
> > +			"%qT has length %d", first, len);
> > +	      else
> > +		inform (fold_expr.get_location (),
> > +			"%qE has length %d", first, len);
> > +	      if (TREE_CODE (TREE_VALUE (p)) == TYPE_PACK_EXPANSION)
> > +		inform (fold_expr.get_location (),
> > +			"%qT has length %d", TREE_VALUE (p), this_len);
> > +	      else
> > +		inform (fold_expr.get_location (),
> > +			"%qE has length %d", TREE_VALUE (p), this_len);
> > +	    }
> > +	  return error_mark_node;
> > +	}
> > +      if (len != 0)
> > +	{
> > +	  indices.safe_push (level);
> > +	  indices.safe_push (index);
> > +	}
> > +    }
> > +  gcc_checking_assert (len != -1);
> > +  if (len == 0)
> > +    {
> > +      /* For N = 0 fold expanded constraint with && fold operator is
> > +	 satisfied.  */
> > +      if (!FOLD_CONSTR_DISJ_P (t))
> > +	return boolean_true_node;
> > +      if (info.diagnose_unsatisfaction_p ())
> > +	{
> > +	  diagnosing_failed_constraint failure (t, args, info.noisy ());
> > +	  cp_expr fold_expr = CONSTR_EXPR (t);
> > +	  inform (fold_expr.get_location (),
> > +		  "fold expanded constraint not satisfied because "
> > +		  "of empty pack with %<||%> fold operator");
> > +	}
> > +      return boolean_false_node;
> > +    }
> > +  for (int i = 0; i < len; ++i)
> > +    {
> > +      unsigned j;
> > +      int level, index;
> > +      args = orig_args;
> > +      FOR_EACH_VEC_ELT (indices, j, level)
> > +	{
> > +	  ++j;
> > +	  indices.iterate (j, &index);
> > +	  if (orig_args == args)
> > +	    args = copy_node (orig_args);
> > +	  if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (orig_args))
> > +	    {
> > +	      tree v = TMPL_ARGS_LEVEL (orig_args, level);
> > +	      if (TMPL_ARGS_LEVEL (args, level) == v)
> > +		SET_TMPL_ARGS_LEVEL (args, level, copy_node (v));
> > +	    }
> 
> IIUC you can use copy_template_args here.  And sadly I think you may
> need to make a copy at each iteration, not just once, because the
> satisfaction cache assumes args are immutable when adding a new
> entry in the cache :/
> 
> Not sure how big of a memory leak this would be in practice,
> but one way to mitigate this might be to ggc_free this copy
> if in the recursive call to satisfy_constraint_r there was
> no cache miss.
> 
> > +	  tree a = TMPL_ARG (orig_args, level, index);
> > +	  TMPL_ARG (args, level, index)
> > +	    = TREE_VEC_ELT (ARGUMENT_PACK_ARGS (a), i);
> > +	}
> > +      tree result = satisfy_constraint_r (FOLD_CONSTR_EXPR (t), args, info);
> > +      if (result == error_mark_node)
> > +	return result;
> > +      if (result == (FOLD_CONSTR_DISJ_P (t)
> > +		     ? boolean_true_node : boolean_false_node))
> > +	return result;
> > +    }
> > +  return FOLD_CONSTR_DISJ_P (t) ? boolean_false_node : boolean_true_node;
> > +}
> > +
> >  /* Determine if the normalized constraint T is satisfied.
> >     Returns boolean_true_node if the expression/constraint is
> >     satisfied, boolean_false_node if not, and error_mark_node
> > @@ -2856,6 +3044,8 @@ satisfy_constraint_r (tree t, tree args,
> >        return satisfy_disjunction (t, args, info);
> >      case ATOMIC_CONSTR:
> >        return satisfy_atom (t, args, info);
> > +    case FOLD_CONSTR:
> > +      return satisfy_fold (t, args, info);
> >      default:
> >        gcc_unreachable ();
> >      }
> > --- gcc/cp/error.cc.jj	2024-07-25 21:34:46.793268734 +0200
> > +++ gcc/cp/error.cc	2024-07-26 08:41:23.555602719 +0200
> > @@ -3097,6 +3097,7 @@ dump_expr (cxx_pretty_printer *pp, tree
> >      case ATOMIC_CONSTR:
> >      case CONJ_CONSTR:
> >      case DISJ_CONSTR:
> > +    case FOLD_CONSTR:
> >        {
> >          pp_cxx_constraint (cxx_pp, t);
> >          break;
> > --- gcc/cp/logic.cc.jj	2024-07-25 21:34:46.793268734 +0200
> > +++ gcc/cp/logic.cc	2024-07-26 09:22:29.047325077 +0200
> > @@ -65,6 +65,8 @@ struct clause
> >      m_terms.push_back (t);
> >      if (TREE_CODE (t) == ATOMIC_CONSTR)
> >        m_set.add (t);
> > +    else if (TREE_CODE (t) == FOLD_CONSTR)
> > +      m_fold.safe_push (t);
> >  
> >      m_current = m_terms.begin ();
> >    }
> > @@ -74,8 +76,11 @@ struct clause
> >       copied list of terms.  */
> >  
> >    clause (clause const& c)
> > -    : m_terms (c.m_terms), m_set (c.m_set), m_current (m_terms.begin ())
> > +    : m_terms (c.m_terms), m_set (c.m_set), m_fold (c.m_fold.length ()),
> > +      m_current (m_terms.begin ())
> >    {
> > +    for (auto v : &c.m_fold)
> > +      m_fold.quick_push (v);
> >      std::advance (m_current, std::distance (c.begin (), c.current ()));
> >    }
> >  
> > @@ -109,6 +114,8 @@ struct clause
> >  	if (m_set.add (t))
> >  	  return std::make_pair (m_terms.erase (iter), true);
> >        }
> > +    else if (TREE_CODE (t) == FOLD_CONSTR)
> > +      m_fold.safe_push (t);
> >      *iter = t;
> >      return std::make_pair (iter, false);
> >    }
> > @@ -126,6 +133,8 @@ struct clause
> >  	if (m_set.add (t))
> >  	  return std::make_pair (iter, false);
> >        }
> > +    else if (TREE_CODE (t) == FOLD_CONSTR)
> > +      m_fold.safe_push (t);
> >      return std::make_pair (m_terms.insert (iter, t), true);
> >    }
> >  
> > @@ -166,6 +175,12 @@ struct clause
> >      return m_set.contains (t);
> >    }
> >  
> > +  /* Returns vector of FOLD_CONSTR terms.  */
> > +
> > +  auto_vec<tree> &folds ()
> > +  {
> > +    return m_fold;
> > +  }
> >  
> >    /* Returns an iterator to the first clause in the formula.  */
> >  
> > @@ -204,6 +219,7 @@ struct clause
> >  
> >    std::list<tree> m_terms; /* The list of terms.  */
> >    hash_set<tree, false, atom_hasher> m_set; /* The set of atomic constraints.  */
> > +  auto_vec<tree> m_fold; /* The vector of fold expanded constraints.  */
> >    iterator m_current; /* The current term.  */
> >  };
> >  
> > @@ -340,7 +356,7 @@ conjunction_p (tree t)
> >  static inline bool
> >  atomic_p (tree t)
> >  {
> > -  return TREE_CODE (t) == ATOMIC_CONSTR;
> > +  return TREE_CODE (t) == ATOMIC_CONSTR || TREE_CODE (t) == FOLD_CONSTR;
> >  }
> >  
> >  /* Recursively count the number of clauses produced when converting T
> > @@ -626,7 +642,7 @@ decompose_disjunction (formula& f, claus
> >      branch_clause (f, c, t);
> >  }
> >  
> > -/* An atomic constraint is already decomposed.  */
> > +/* An atomic or fold expanded constraint is already decomposed.  */
> >  inline void
> >  decompose_atom (clause& c)
> >  {
> > @@ -691,13 +707,48 @@ derive_atomic_proof (clause& c, tree t)
> >    return c.contains (t);
> >  }
> >  
> > +/* Derive a proof of the fold expanded constraint T in clause C.  */
> > +
> > +static bool
> > +derive_fold_proof (clause& c, tree t, rules r)
> > +{
> > +  auto_vec<tree> &folds = c.folds ();
> > +  for (auto v : &folds)
> > +    /* [temp.constr.order]/1 - a fold expanded constraint A subsumes
> > +       another fold expanded constraint B if they are compatible for
> > +       subsumption, have the same fold-operator, and the constraint
> > +       of A subsumes that of B.  */
> > +    if (FOLD_CONSTR_DISJ_P (v) == FOLD_CONSTR_DISJ_P (t))
> > +      {
> > +	bool compat = false;
> > +	for (tree p1 = FOLD_CONSTR_PACKS (t); p1 && !compat;
> > +	     p1 = TREE_CHAIN (p1))
> > +	  for (tree p2 = FOLD_CONSTR_PACKS (v); p2;
> > +	       p2 = TREE_CHAIN (p2))
> > +	    /* [temp.constr.fold]/5 - Two fold expanded constraints are
> > +	       compatible for subsumption if their respective constraints
> > +	       both contain an equivalent unexpanded pack.  */
> > +	    if (template_args_equal (TREE_VALUE (p1), TREE_VALUE (p2)))
> > +	      {
> > +		compat = true;
> > +		break;
> > +	      }
> > +	if (compat
> > +	    && (r == left
> > +		? subsumes (FOLD_CONSTR_EXPR (v), FOLD_CONSTR_EXPR (t))
> > +		: subsumes (FOLD_CONSTR_EXPR (t), FOLD_CONSTR_EXPR (v))))
> > +	  return true;
> > +      }
> > +  return false;
> > +}
> > +
> >  /* Derive a proof of T from the terms in C.  */
> >  
> >  static bool
> >  derive_proof (clause& c, tree t, rules r)
> >  {
> >    switch (TREE_CODE (t))
> > -  {
> > +    {
> >      case CONJ_CONSTR:
> >        if (r == left)
> >          return derive_proof_for_both_operands (c, t, r);
> > @@ -708,9 +759,13 @@ derive_proof (clause& c, tree t, rules r
> >          return derive_proof_for_either_operand (c, t, r);
> >        else
> >  	return derive_proof_for_both_operands (c, t, r);
> > -    default:
> > +    case ATOMIC_CONSTR:
> >        return derive_atomic_proof (c, t);
> > -  }
> > +    case FOLD_CONSTR:
> > +      return derive_fold_proof (c, t, r);
> > +    default:
> > +      gcc_unreachable ();
> > +    }
> >  }
> >  
> >  /* Key/value pair for caching subsumption results.  This associates a pair of
> > @@ -787,7 +842,7 @@ save_subsumption (tree t1, tree t2, bool
> >  static bool
> >  subsumes_constraints_nonnull (tree lhs, tree rhs)
> >  {
> > -  auto_timevar time (TV_CONSTRAINT_SUB);
> > +  auto_cond_timevar time (TV_CONSTRAINT_SUB);
> >  
> >    if (bool *b = lookup_subsumption (lhs, rhs))
> >      return *b;
> > --- gcc/cp/cxx-pretty-print.cc.jj	2024-07-25 21:34:46.793268734 +0200
> > +++ gcc/cp/cxx-pretty-print.cc	2024-07-26 14:19:27.435984262 +0200
> > @@ -1259,6 +1259,7 @@ cxx_pretty_printer::expression (tree t)
> >      case ATOMIC_CONSTR:
> >      case CONJ_CONSTR:
> >      case DISJ_CONSTR:
> > +    case FOLD_CONSTR:
> >        pp_cxx_constraint (this, t);
> >        break;
> >  
> > @@ -2882,6 +2883,19 @@ pp_cxx_disjunction (cxx_pretty_printer *
> >  }
> >  
> >  void
> > +pp_cxx_fold_expanded_constraint (cxx_pretty_printer *pp, tree t)
> > +{
> > +  pp_left_paren (pp);
> > +  pp_cxx_constraint (pp, FOLD_CONSTR_EXPR (t));
> > +  pp_space (pp);
> > +  if (FOLD_CONSTR_DISJ_P (t))
> > +    pp_string (pp, "\\/");
> > +  else
> > +    pp_string (pp, "/\\");
> > +  pp_string (pp, " ...)");
> > +}
> > +
> > +void
> >  pp_cxx_constraint (cxx_pretty_printer *pp, tree t)
> >  {
> >    if (t == error_mark_node)
> > @@ -2901,6 +2915,10 @@ pp_cxx_constraint (cxx_pretty_printer *p
> >        pp_cxx_disjunction (pp, t);
> >        break;
> >  
> > +    case FOLD_CONSTR:
> > +      pp_cxx_fold_expanded_constraint (pp, t);
> > +      break;
> > +
> >      case EXPR_PACK_EXPANSION:
> >        pp->expression (TREE_OPERAND (t, 0));
> >        break;
> > --- gcc/testsuite/g++.dg/concepts/diagnostic3.C.jj	2023-10-16 17:25:32.456781462 +0200
> > +++ gcc/testsuite/g++.dg/concepts/diagnostic3.C	2024-07-26 09:32:49.042262192 +0200
> > @@ -1,4 +1,4 @@
> > -// { dg-do compile { target c++2a } }
> > +// { dg-do compile { target c++20 } }
> >  
> >  template<typename T>
> >    inline constexpr bool foo_v = false;
> > @@ -7,7 +7,7 @@ template<typename T>
> >    concept foo = (bool)(foo_v<T> | foo_v<T&>);
> >  
> >  template<typename... Ts>
> > -requires (foo<Ts> && ...) // { dg-message "19:with Ts = .int, char... evaluated to .false." }
> > +requires (foo<Ts> && ...) // { dg-message "19:with Ts = .int, char... evaluated to .false." "" { target c++23_down } }
> >  void
> >  bar()
> >  { }
> > @@ -16,7 +16,7 @@ template<int>
> >  struct S { };
> >  
> >  template<int... Is>
> > -requires (foo<S<Is>> && ...) // { dg-message "22:with Is = .2, 3, 4... evaluated to .false." }
> > +requires (foo<S<Is>> && ...) // { dg-message "22:with Is = .2, 3, 4... evaluated to .false." "" { target c++23_down } }
> >  void
> >  baz()
> >  { }
> > --- gcc/testsuite/g++.dg/concepts/variadic2.C.jj	2024-07-25 21:34:46.809268529 +0200
> > +++ gcc/testsuite/g++.dg/concepts/variadic2.C	2024-07-26 08:34:40.535867087 +0200
> > @@ -13,6 +13,7 @@ constexpr int f(Ts...) { return 1; }
> >  
> >  int main()
> >  {
> > -  static_assert(f(42) == 1); // { dg-error "ambiguous" }
> > -  // The associated constraints of the two functions are incomparable.
> > +  static_assert(f(42) == 1); // { dg-error "ambiguous" "" { target c++23_down } }
> > +  // The associated constraints of the two functions are incomparable before
> > +  // C++26.
> >  }
> > --- gcc/testsuite/g++.dg/concepts/variadic4.C.jj	2024-07-25 21:34:46.809268529 +0200
> > +++ gcc/testsuite/g++.dg/concepts/variadic4.C	2024-07-26 08:34:40.535867087 +0200
> > @@ -12,9 +12,9 @@ struct zip;
> >  
> >  template<Sequence... Seqs>
> >      requires requires { typename list<Seqs...>; } // && (Sequence<Seqs> && ...)
> > -struct zip<Seqs...> {}; // { dg-error "does not specialize" }
> > +struct zip<Seqs...> {}; // { dg-error "does not specialize" "" { target c++23_down } }
> >  // The constraints of the specialization and the sequence are not
> > -// comparable; the specializations are unordered.
> > +// comparable before C++26; the specializations are unordered.
> >  
> >  int main()
> >  {
> > --- gcc/testsuite/g++.dg/cpp2a/concepts-requires33.C	2022-12-05 11:10:37.712671571 +0100
> > +++ gcc/testsuite/g++.dg/cpp2a/concepts-requires33.C	2024-07-26 18:37:41.867864077 +0200
> > @@ -2,7 +2,7 @@
> >  // { dg-do compile { target c++20 } }
> >  
> >  template<class... T>
> > -void f() requires (requires (T x) { true; } && ...);
> > +void f() requires (requires (T x) { true; } && ...); // { dg-error "invalid parameter type 'void'" "" { target c++26 } }
> >  
> >  int main() {
> >    f<int>();
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr1.C.jj	2024-07-26 08:34:40.535867087 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr1.C	2024-07-26 08:34:40.535867087 +0200
> > @@ -0,0 +1,37 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +template <class U> concept isint = __is_same (U, int);
> > +
> > +template <class... V> requires (isint<V> && ...)
> > +constexpr int foo (V...) { return 1; };
> > +
> > +template <class... U> requires (... || isint<U>)
> > +constexpr int bar (U...) { return 1; };
> > +
> > +template <class T, class... S> requires (isint<S> && ... && isint<T>)
> > +constexpr int baz (T, S...) { return 1; }
> > +
> > +template <class T, class... R> requires (isint<T> || ... || isint<R>)
> > +constexpr int qux (T, R...) { return 1; }
> > +
> > +int v1 = foo ();
> > +int v2 = bar ();			// { dg-error "no matching function for call to" }
> > +int v3 = foo (1, 2);
> > +int v4 = bar (1, 2);
> > +int v5 = foo (1L, 2);			// { dg-error "no matching function for call to" }
> > +int v6 = foo (1, 2L);			// { dg-error "no matching function for call to" }
> > +int v7 = bar (1L, 2);
> > +int v8 = bar (2L, 3.0, 4, 5.0);
> > +int v9 = bar (2LL, 3.0f, 5.0, 6ULL, 2U);// { dg-error "no matching function for call to" }
> > +int v10 = baz ();			// { dg-error "no matching function for call to" }
> > +int v11 = baz (1);
> > +int v12 = baz (1L);			// { dg-error "no matching function for call to" }
> > +int v13 = baz (1, 2, 3, 4, 5);
> > +int v14 = baz (1, 2, 3L, 4, 5);		// { dg-error "no matching function for call to" }
> > +int v15 = qux ();			// { dg-error "no matching function for call to" }
> > +int v16 = qux (1);
> > +int v17 = qux (1L);			// { dg-error "no matching function for call to" }
> > +int v18 = qux (1, 2.0, 3LL);
> > +int v19 = qux (1L, 2.0f, 3, 4ULL);
> > +int v20 = qux (0.0f, 1L, 2.0, 3L);	// { dg-error "no matching function for call to" }
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr2.C.jj	2024-07-26 08:34:40.535867087 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr2.C	2024-07-26 08:34:40.535867087 +0200
> > @@ -0,0 +1,56 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +template <class T> concept C1 = true;
> > +template <class T> concept C2 = C1<T> && true;
> > +template <class T> concept C3 = C1<T> && __is_same (T, int);
> > +
> > +template <class T> requires (C1<T>)
> > +constexpr bool foo (T) { return false; };
> > +template <class... T> requires (C2<T> && ...)
> > +constexpr bool foo (T...) { return true; };
> > +
> > +static_assert (!foo (0));
> > +static_assert (!foo (1));
> > +
> > +template <class... T> requires (C1<T> && ...)
> > +constexpr bool bar (T...) { return false; };
> > +template <class... T> requires (C2<T> && ...)
> > +constexpr bool bar (T...) { return true; };
> > +
> > +static_assert (bar (0));		// { dg-error "call of overloaded 'bar\\\(int\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (bar ());			// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (bar (1, 2));		// { dg-error "call of overloaded 'bar\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> > +
> > +template <class... T> requires (C1<T> && ...)
> > +constexpr bool baz (T...) { return false; };
> > +template <class... T> requires (... && (C1<T> && true))
> > +constexpr bool baz (T...) { return true; };
> > +
> > +static_assert (baz (0));		// { dg-error "call of overloaded 'baz\\\(int\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (baz ());			// { dg-error "call of overloaded 'baz\\\(\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (baz (1, 2));		// { dg-error "call of overloaded 'baz\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> > +
> > +template <typename... T> requires (C1<T> || ... || true)
> > +constexpr bool qux (T...) { return false; };
> > +template <typename... T> requires (C2<T> && ... && true)
> > +constexpr bool qux (T...) { return true; };
> > +
> > +static_assert (qux (0));		// { dg-error "call of overloaded 'qux\\\(int\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (qux ());			// { dg-error "call of overloaded 'qux\\\(\\\)' is ambiguous" "" { target c++23_down } }
> > +
> > +constexpr bool quux (C1 auto...) { return false; }
> > +constexpr bool quux (C3 auto...) { return true; }
> > +
> > +static_assert (quux ());		// { dg-error "call of overloaded 'quux\\\(\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (quux (0, 0));		// { dg-error "call of overloaded 'quux\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (!quux (0L, 0));
> > +
> > +template <C1... T>
> > +constexpr bool corge (C1 auto...) { return false; }
> > +template <C3... T>
> > +constexpr bool corge (C3 auto...) { return true; }
> > +
> > +static_assert (corge ());		// { dg-error "call of overloaded 'corge\\\(\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (corge (0, 0));		// { dg-error "call of overloaded 'corge\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (!corge (0L, 0));
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr3.C.jj	2024-07-26 08:34:40.535867087 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr3.C	2024-07-26 11:12:09.129830502 +0200
> > @@ -0,0 +1,15 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +template <typename ...V> struct A;
> > +struct Thingy {
> > +  static constexpr int compare (const Thingy &) { return 1; }
> > +};
> > +template <typename ...T, typename ...U>
> > +void f (A<T ...> *, A<U ...> *)
> > +requires (T::compare (U{}) && ...);	// { dg-error "has type 'int', not 'bool'" "" { target c++26 } }
> > +void
> > +g (A<Thingy, Thingy> *ap)
> > +{
> > +  f (ap, ap);				// { dg-error "no matching function for call to" "" { target c++26 } }
> > +}
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr4.C.jj	2024-07-26 08:34:40.535867087 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr4.C	2024-07-26 08:34:40.535867087 +0200
> > @@ -0,0 +1,48 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +template <class T> concept C1 = true;
> > +template <class T> concept C2 = C1<T> && true;
> > +
> > +template <class... T> requires (C1<T> && ...)
> > +constexpr bool foo (T...) { return false; };
> > +template <class... T> requires (C2<T> || ...)
> > +constexpr bool foo (T...) { return true; };
> > +
> > +static_assert (foo (0));	// { dg-error "call of overloaded 'foo\\\(int\\\)' is ambiguous" }
> > +
> > +template <class... T> requires (C1<T> || ...)
> > +constexpr bool bar (T...) { return false; };
> > +template <class... T> requires (C2<T> && ...)
> > +constexpr bool bar (T...) { return true; };
> > +
> > +static_assert (bar (0));	// { dg-error "call of overloaded 'bar\\\(int\\\)' is ambiguous" }
> > +
> > +template <class... T> requires (C1<T> || ...)
> > +constexpr bool baz (T...) { return false; };
> > +template <class... T> requires (C2<T> || ...)
> > +constexpr bool baz (T...) { return true; };
> > +
> > +static_assert (baz (0));	// { dg-error "call of overloaded 'baz\\\(int\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (baz ());		// { dg-error "no matching function for call to 'baz\\\(\\\)'" }
> > +static_assert (baz (1, 2));	// { dg-error "call of overloaded 'baz\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
> > +
> > +template <class... T>
> > +struct U {
> > +  template <class... V> requires (... && C1<V>)
> > +  static constexpr bool foo () { return false; }
> > +  template <class... V> requires (... && C2<V>)
> > +  static constexpr bool foo () { return true; }
> > +  template <class... V> requires (... && C1<T>)
> > +  static constexpr bool bar () { return false; }
> > +  template <class... V> requires (... && C2<T>)
> > +  static constexpr bool bar () { return true; }
> > +  template <class... V> requires (... && C1<V>)
> > +  static constexpr bool baz () { return false; }
> > +  template <class... V> requires (... && C2<T>)
> > +  static constexpr bool baz () { return true; }
> > +};
> > +
> > +static_assert (U<int>::foo<int> ());	// { dg-error "call of overloaded 'foo\\\(\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (U<int>::bar<int> ());	// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (U<int>::baz<int> ());	// { dg-error "call of overloaded 'baz\\\(\\\)' is ambiguous" }
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr5.C.jj	2024-07-26 08:34:40.535867087 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr5.C	2024-07-26 13:37:12.802463309 +0200
> > @@ -0,0 +1,77 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +struct A {
> > +  using type = int;
> > +};
> > +struct B {
> > +  using type = long;
> > +};
> > +
> > +template <class T> concept C = sizeof (T) < sizeof (int) * 64;
> > +
> > +template <class... T> requires (C<typename T::type> && ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool foo () { return true; };
> > +
> > +static_assert (foo <> ());
> > +static_assert (foo <A> ());
> > +static_assert (foo <B, A, A, B> ());
> > +static_assert (foo <int> ());					// { dg-error "no matching function for call" }
> > +static_assert (foo <B, long> ());				// { dg-error "no matching function for call" }
> > +static_assert (foo <unsigned, A, A> ());			// { dg-error "no matching function for call" }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +
> > +template <class T, class... U> requires (C<typename U::type> && ... && C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool bar () { return true; };
> > +
> > +static_assert (bar <A> ());
> > +static_assert (bar <B, A, A, B> ());
> > +static_assert (bar <int> ());					// { dg-error "no matching function for call" }
> > +static_assert (bar <B, long> ());				// { dg-error "no matching function for call" }
> > +static_assert (bar <unsigned, A, A> ());			// { dg-error "no matching function for call" }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +
> > +template <class T, class... U> requires (C<typename T::type> && ... && C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool baz () { return true; };
> > +
> > +static_assert (baz <A> ());
> > +static_assert (baz <B, A, A, B> ());
> > +static_assert (baz <int> ());					// { dg-error "no matching function for call" }
> > +static_assert (baz <B, long> ());				// { dg-error "no matching function for call" }
> > +static_assert (baz <unsigned, A, A> ());			// { dg-error "no matching function for call" }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +
> > +template <class... T> requires (C<typename T::type> || ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool qux () { return true; };
> > +
> > +static_assert (qux <> ());					// { dg-error "no matching function for call" }
> > +static_assert (qux <A> ());
> > +static_assert (qux <B, A, A, B> ());
> > +static_assert (qux <int> ());					// { dg-error "no matching function for call" }
> > +static_assert (qux <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (qux <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> > +// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
> > +
> > +template <class T, class... U> requires (C<typename U::type> || ... || C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool corge () { return true; };
> > +
> > +static_assert (corge <A> ());
> > +static_assert (corge <B, A, A, B> ());
> > +static_assert (corge <int> ());					// { dg-error "no matching function for call" }
> > +static_assert (corge <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (corge <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> > +
> > +template <class T, class... U> requires (C<typename T::type> || ... || C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool garply () { return true; };
> > +
> > +static_assert (garply <A> ());
> > +static_assert (garply <B, A, A, B> ());
> > +static_assert (garply <int> ());				// { dg-error "no matching function for call" }
> > +static_assert (garply <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (garply <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr6.C.jj	2024-07-26 08:34:40.535867087 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr6.C	2024-07-26 14:32:28.121132795 +0200
> > @@ -0,0 +1,20 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +template <class T> concept C1 = true;
> > +template <class T> concept C2 = C1<T> && true;
> > +
> > +template <class... T>
> > +struct U {
> > +  template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
> > +  static constexpr bool foo () { return false; }
> > +  template <class... V> requires ((C2<T> && ...) && ... && C2<V>)
> > +  static constexpr bool foo () { return true; }
> > +  template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
> > +  static constexpr bool bar () { return false; }
> > +  template <class... V> requires ((C2<V> && ...) && ... && C2<T>)
> > +  static constexpr bool bar () { return true; }
> > +};
> > +
> > +static_assert (U<int>::foo<int> ());	// { dg-error "call of overloaded 'foo\\\(\\\)' is ambiguous" "" { target c++23_down } }
> > +static_assert (U<int>::bar<int> ());	// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr7.C.jj	2024-07-26 11:35:01.499085336 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr7.C	2024-07-26 15:11:46.186674513 +0200
> > @@ -0,0 +1,11 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +template <class T> concept C1 = true;
> > +template <class T> concept C2 = C1<T> && true;
> > +
> > +template <class... T> requires ((((sizeof (T) + ...) < 8 * sizeof (int)) && C2<T>) && ...)
> > +constexpr bool foo (T...) { return true; };
> > +
> > +static_assert (foo (0));
> > +static_assert (foo (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); // { dg-error "no matching function for call" }
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr8.C.jj	2024-07-26 15:05:59.636183243 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr8.C	2024-07-26 15:05:15.027759668 +0200
> > @@ -0,0 +1,22 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +template <class T> concept C = __is_same (T, int);
> > +
> > +template <class ...T>
> > +struct A {};
> > +
> > +template <class ...T, class ...U> requires ((C<T> && C<U>) && ...)	// { dg-error "mismatched argument pack lengths while expanding '\\\(C<T> \\\&\\\& C<U>\\\)'" "" { target c++23_down } }
> > +constexpr bool foo (A<T...>, A<U...>) { return true; };
> > +// { dg-message "fold expanded constraint not satisfied because of pack length mismatch" "" { target c++26 } .-2 }
> > +// { dg-message "'U' has length 3" "" { target c++26 } .-3 }
> > +// { dg-message "'T' has length 2" "" { target c++26 } .-4 }
> > +
> > +static_assert (foo (A<int, int, int> {}, A<int, int, int> {}));
> > +static_assert (foo (A<int, int> {}, A<int, int, int> {}));	// { dg-error "no matching function for call to" }
> > +
> > +template <class ...T> requires (C<T> || ...)			// { dg-message "fold expanded constraint not satisfied because of empty pack with '||' fold operator" "" { target c++26 } }
> > +constexpr bool bar (T...) { return true; };
> > +
> > +static_assert (bar (0));
> > +static_assert (bar ());						// { dg-error "no matching function for call to" }
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr9.C.jj	2024-07-26 15:12:12.570331261 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr9.C	2024-07-26 15:12:06.613408758 +0200
> > @@ -0,0 +1,11 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +template <class T> concept C = __is_same (T, int);
> > +
> > +template <class... T> requires ((C<T> && ...) && ... && C<T>)
> > +constexpr bool foo (T...) { return true; }
> > +
> > +static_assert (foo ());
> > +static_assert (foo (1, 2, 3));
> > +static_assert (foo (1L, 2L)); // { dg-error "no matching function for call" }
> > --- gcc/testsuite/g++.dg/cpp26/fold-constr10.C.jj	2024-07-26 16:02:26.099421284 +0200
> > +++ gcc/testsuite/g++.dg/cpp26/fold-constr10.C	2024-07-26 16:03:35.256534023 +0200
> > @@ -0,0 +1,67 @@
> > +// P2963R3 - Ordering of constraints involving fold expressions
> > +// { dg-do compile { target c++20 } }
> > +
> > +struct A {
> > +  using type = int;
> > +};
> > +struct B {
> > +  using type = long;
> > +};
> > +
> > +template <class T> concept C = true;
> > +
> > +template <class... T> requires (C<typename T::type> && ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool foo () { return true; };
> > +
> > +static_assert (foo <> ());
> > +static_assert (foo <A> ());
> > +static_assert (foo <B, A, A, B> ());
> > +static_assert (foo <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (foo <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (foo <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> > +
> > +template <class T, class... U> requires (C<typename U::type> && ... && C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool bar () { return true; };
> > +
> > +static_assert (bar <A> ());
> > +static_assert (bar <B, A, A, B> ());
> > +static_assert (bar <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (bar <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (bar <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> > +
> > +template <class T, class... U> requires (C<typename T::type> && ... && C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool baz () { return true; };
> > +
> > +static_assert (baz <A> ());
> > +static_assert (baz <B, A, A, B> ());
> > +static_assert (baz <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (baz <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (baz <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> > +
> > +template <class... T> requires (C<typename T::type> || ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool qux () { return true; };
> > +
> > +static_assert (qux <> ());					// { dg-error "no matching function for call" }
> > +static_assert (qux <A> ());
> > +static_assert (qux <B, A, A, B> ());
> > +static_assert (qux <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (qux <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (qux <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> > +
> > +template <class T, class... U> requires (C<typename U::type> || ... || C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool corge () { return true; };
> > +
> > +static_assert (corge <A> ());
> > +static_assert (corge <B, A, A, B> ());
> > +static_assert (corge <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (corge <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (corge <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> > +
> > +template <class T, class... U> requires (C<typename T::type> || ... || C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
> > +constexpr bool garply () { return true; };
> > +
> > +static_assert (garply <A> ());
> > +static_assert (garply <B, A, A, B> ());
> > +static_assert (garply <int> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (garply <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
> > +static_assert (garply <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
> > 
> > 	Jakub
> > 
> > 
>
Jakub Jelinek July 26, 2024, 6:43 p.m. UTC | #3
On Fri, Jul 26, 2024 at 02:35:01PM -0400, Patrick Palka wrote:
> > IIUC the way gen_elem_of_pack_expansion_instantiation handles this for
> > ordinary pack expnasions is by replacing each ARGUMENT_PACK with an
> > ARGUMENT_PACK_SELECT.  This ARGUMENT_PACK_SELECT contains the entire
> > pack as well as the current index into the pack, and it essentially
> > acts as an overloaded template argument that's treated either as a
> > single pack element or as the entire pack itself as needed.
> 
> Ah but by using ARGUMENT_PACK_SELECT I bet you'll run into an
> iterative_hash_template_arg ICE when the satisfaction cache tries to
> hash it.

Yeah, I saw ARGUMENT_PACK_SELECT being used, but didn't notice that
important
      if (arg_pack && TREE_CODE (arg_pack) == ARGUMENT_PACK_SELECT)
        arg_pack = ARGUMENT_PACK_SELECT_FROM_PACK (arg_pack);
part of tsubst_pack_expansion where it picks the pack from
ARGUMENT_PACK_SELECT when needed.

I also saw the iterative_hash_template_arg case and was afraid to modify it
because of that reuse of ARGUMENT_PACK_SELECT tree.
But I guess you're right, if there is a flag added to it only set by
satisfy_fold which needs to create them immutable because there is the
satisfaction cache etc., it might work.  Will try that soon.
Thanks.

> IMHO we should allow hashing/comparing ARGUMENT_PACK_SELECT, at least
> "immutable" ones whose index isn't expected to change from under us,
> accordin to some flag maybe.  (the ARGUMENT_PACK_SELECTs created from
> gen_elem_of_pack_expansion_instantiation are reused/modified across
> iterations so those wouldn't be safe to store in a hash table.)
> 
> > 
> > On that note I wonder if there's any opportunity for code reuse from
> > gen_elem_of_pack_expansion_instantiation / tsubst_pack_expansion?

I think I'm not using too much code from those (yet) to make it worthwhile,
but am open to outline some helpers if needed.

	Jakub
Jakub Jelinek July 26, 2024, 7:49 p.m. UTC | #4
On Fri, Jul 26, 2024 at 08:43:44PM +0200, Jakub Jelinek wrote:
> Yeah, I saw ARGUMENT_PACK_SELECT being used, but didn't notice that
> important
>       if (arg_pack && TREE_CODE (arg_pack) == ARGUMENT_PACK_SELECT)
>         arg_pack = ARGUMENT_PACK_SELECT_FROM_PACK (arg_pack);
> part of tsubst_pack_expansion where it picks the pack from
> ARGUMENT_PACK_SELECT when needed.
> 
> I also saw the iterative_hash_template_arg case and was afraid to modify it
> because of that reuse of ARGUMENT_PACK_SELECT tree.
> But I guess you're right, if there is a flag added to it only set by
> satisfy_fold which needs to create them immutable because there is the
> satisfaction cache etc., it might work.  Will try that soon.

With following incremental patch I've made some progress, but still
something to debug...

--- gcc/cp/cp-tree.h	2024-07-26 09:19:10.626908903 +0200
+++ gcc/cp/cp-tree.h	2024-07-26 21:08:02.698018826 +0200
@@ -4100,6 +4100,11 @@
 #define ARGUMENT_PACK_SELECT_INDEX(NODE)				\
   (((struct tree_argument_pack_select *)ARGUMENT_PACK_SELECT_CHECK (NODE))->index)
 
+/* True in ARGUMENT_PACK_SELECT which is immutable, won't be modified
+   once it is created and so can be safely hashed.  */
+#define ARGUMENT_PACK_SELECT_IMMUTABLE_P(NODE) \
+  TREE_STATIC (ARGUMENT_PACK_SELECT_CHECK (NODE))
+
 #define FOLD_EXPR_CHECK(NODE)						\
   TREE_CHECK4 (NODE, UNARY_LEFT_FOLD_EXPR, UNARY_RIGHT_FOLD_EXPR,	\
 	       BINARY_LEFT_FOLD_EXPR, BINARY_RIGHT_FOLD_EXPR)
--- gcc/cp/constraint.cc	2024-07-26 15:03:04.438445216 +0200
+++ gcc/cp/constraint.cc	2024-07-26 21:38:36.447741106 +0200
@@ -2249,7 +2249,7 @@
 		  int index;
 		  template_parm_level_and_index (e, &level, &index);
 		  tree a = TMPL_ARG (args, level, index);
-		  if (!ARGUMENT_PACK_P (a))
+		  if (TREE_CODE (a) == ARGUMENT_PACK_SELECT)
 		    arg = t;
 		}
 	    }
@@ -2936,6 +2936,8 @@
       int level, index;
       template_parm_level_and_index (TREE_VALUE (p), &level, &index);
       tree a = TMPL_ARG (orig_args, level, index);
+      if (TREE_CODE (a) == ARGUMENT_PACK_SELECT)
+	a = ARGUMENT_PACK_SELECT_FROM_PACK (a);
       int this_len = TREE_VEC_LENGTH (ARGUMENT_PACK_ARGS (a));
       if (len == -1)
 	len = this_len;
@@ -3005,8 +3007,13 @@
 		SET_TMPL_ARGS_LEVEL (args, level, copy_node (v));
 	    }
 	  tree a = TMPL_ARG (orig_args, level, index);
-	  TMPL_ARG (args, level, index)
-	    = TREE_VEC_ELT (ARGUMENT_PACK_ARGS (a), i);
+	  if (TREE_CODE (a) == ARGUMENT_PACK_SELECT)
+	    a = ARGUMENT_PACK_SELECT_FROM_PACK (a);
+	  tree aps = make_node (ARGUMENT_PACK_SELECT);
+	  ARGUMENT_PACK_SELECT_FROM_PACK (aps) = a;
+	  ARGUMENT_PACK_SELECT_INDEX (aps) = i;
+	  ARGUMENT_PACK_SELECT_IMMUTABLE_P (aps) = 1;
+	  TMPL_ARG (args, level, index) = aps;
 	}
       tree result = satisfy_constraint_r (FOLD_CONSTR_EXPR (t), args, info);
       if (result == error_mark_node)
--- gcc/cp/pt.cc.jj	2024-07-26 08:34:18.114159967 +0200
+++ gcc/cp/pt.cc	2024-07-26 21:21:56.967434333 +0200
@@ -1763,8 +1763,17 @@ iterative_hash_template_arg (tree arg, h
       /* Getting here with an ARGUMENT_PACK_SELECT means we're probably
 	 preserving it in a hash table, which is bad because it will change
 	 meaning when gen_elem_of_pack_expansion_instantiation changes the
-	 ARGUMENT_PACK_SELECT_INDEX.  */
-      gcc_unreachable ();
+	 ARGUMENT_PACK_SELECT_INDEX.  The exception is immutable
+	 ARGUMENT_PACK_SELECTs created by constraint.cc (satisfy_fold).  */
+      gcc_assert (ARGUMENT_PACK_SELECT_IMMUTABLE_P (arg));
+      val = iterative_hash_template_arg (ARGUMENT_PACK_SELECT_FROM_PACK (arg),
+					 val);
+      if (sizeof (ARGUMENT_PACK_SELECT_INDEX (arg)) <= sizeof (hashval_t))
+	return iterative_hash_hashval_t (ARGUMENT_PACK_SELECT_INDEX (arg),
+					 val);
+      else
+	return iterative_hash_host_wide_int (ARGUMENT_PACK_SELECT_INDEX (arg),
+					     val);
 
     case ERROR_MARK:
       return val;
@@ -9464,7 +9473,14 @@ template_args_equal (tree ot, tree nt)
   else if (ARGUMENT_PACK_P (ot) || ARGUMENT_PACK_P (nt))
     return cp_tree_equal (ot, nt);
   else if (TREE_CODE (ot) == ARGUMENT_PACK_SELECT)
-    gcc_unreachable ();
+    {
+      gcc_assert (ARGUMENT_PACK_SELECT_IMMUTABLE_P (ot)
+		  && ARGUMENT_PACK_SELECT_IMMUTABLE_P (nt));
+      return (ARGUMENT_PACK_SELECT_INDEX (ot)
+	      == ARGUMENT_PACK_SELECT_INDEX (nt)
+	      && template_args_equal (ARGUMENT_PACK_SELECT_FROM_PACK (ot),
+				      ARGUMENT_PACK_SELECT_FROM_PACK (nt)));
+    }
   else if (TYPE_P (nt) || TYPE_P (ot))
     {
       if (!(TYPE_P (nt) && TYPE_P (ot)))


	Jakub
Jakub Jelinek July 26, 2024, 8:14 p.m. UTC | #5
On Fri, Jul 26, 2024 at 09:49:27PM +0200, Jakub Jelinek wrote:
> On Fri, Jul 26, 2024 at 08:43:44PM +0200, Jakub Jelinek wrote:
> > Yeah, I saw ARGUMENT_PACK_SELECT being used, but didn't notice that
> > important
> >       if (arg_pack && TREE_CODE (arg_pack) == ARGUMENT_PACK_SELECT)
> >         arg_pack = ARGUMENT_PACK_SELECT_FROM_PACK (arg_pack);
> > part of tsubst_pack_expansion where it picks the pack from
> > ARGUMENT_PACK_SELECT when needed.
> > 
> > I also saw the iterative_hash_template_arg case and was afraid to modify it
> > because of that reuse of ARGUMENT_PACK_SELECT tree.
> > But I guess you're right, if there is a flag added to it only set by
> > satisfy_fold which needs to create them immutable because there is the
> > satisfaction cache etc., it might work.  Will try that soon.
> 
> With following incremental patch I've made some progress, but still
> something to debug...

One thing which needs solving is

// P2963R3 - Ordering of constraints involving fold expressions
// { dg-do compile { target c++20 } }

template <class ...T> concept C = (__is_same (T, int) && ...);
template <typename V>
struct S {
  template <class ...U> requires (C<U...>)
  static constexpr bool foo () { return true; }
};

static_assert (S<void>::foo <int, int, int, int> ());

somehow the template parameter mapping needs to be remembered even for the
fold expanded constraint, right now the patch will see the pack is T,
which is level 1 index 0, but args aren't arguments of the C concept,
but of the foo function template.
One can also use requires (C<int, int, int>) etc., no?

	Jakub
Patrick Palka July 26, 2024, 8:42 p.m. UTC | #6
On Fri, 26 Jul 2024, Jakub Jelinek wrote:

> On Fri, Jul 26, 2024 at 09:49:27PM +0200, Jakub Jelinek wrote:
> > On Fri, Jul 26, 2024 at 08:43:44PM +0200, Jakub Jelinek wrote:
> > > Yeah, I saw ARGUMENT_PACK_SELECT being used, but didn't notice that
> > > important
> > >       if (arg_pack && TREE_CODE (arg_pack) == ARGUMENT_PACK_SELECT)
> > >         arg_pack = ARGUMENT_PACK_SELECT_FROM_PACK (arg_pack);
> > > part of tsubst_pack_expansion where it picks the pack from
> > > ARGUMENT_PACK_SELECT when needed.
> > > 
> > > I also saw the iterative_hash_template_arg case and was afraid to modify it
> > > because of that reuse of ARGUMENT_PACK_SELECT tree.
> > > But I guess you're right, if there is a flag added to it only set by
> > > satisfy_fold which needs to create them immutable because there is the
> > > satisfaction cache etc., it might work.  Will try that soon.
> > 
> > With following incremental patch I've made some progress, but still
> > something to debug...
> 
> One thing which needs solving is
> 
> // P2963R3 - Ordering of constraints involving fold expressions
> // { dg-do compile { target c++20 } }
> 
> template <class ...T> concept C = (__is_same (T, int) && ...);
> template <typename V>
> struct S {
>   template <class ...U> requires (C<U...>)
>   static constexpr bool foo () { return true; }
> };
> 
> static_assert (S<void>::foo <int, int, int, int> ());
> 
> somehow the template parameter mapping needs to be remembered even for the
> fold expanded constraint, right now the patch will see the pack is T,
> which is level 1 index 0, but args aren't arguments of the C concept,
> but of the foo function template.
> One can also use requires (C<int, int, int>) etc., no?

It seems the problem is FOLD_EXPR_PACKS is currently set to the
parameter packs used inside the non-normalized constraints, but I think
what we really need are the packs used in the normalized constraints,
specifically the packs used in the target of each parameter mapping of
each atomic constraint?
Jakub Jelinek July 26, 2024, 8:58 p.m. UTC | #7
On Fri, Jul 26, 2024 at 04:42:36PM -0400, Patrick Palka wrote:
> > // P2963R3 - Ordering of constraints involving fold expressions
> > // { dg-do compile { target c++20 } }
> > 
> > template <class ...T> concept C = (__is_same (T, int) && ...);
> > template <typename V>
> > struct S {
> >   template <class ...U> requires (C<U...>)
> >   static constexpr bool foo () { return true; }
> > };
> > 
> > static_assert (S<void>::foo <int, int, int, int> ());
> > 
> > somehow the template parameter mapping needs to be remembered even for the
> > fold expanded constraint, right now the patch will see the pack is T,
> > which is level 1 index 0, but args aren't arguments of the C concept,
> > but of the foo function template.
> > One can also use requires (C<int, int, int>) etc., no?
> 
> It seems the problem is FOLD_EXPR_PACKS is currently set to the
> parameter packs used inside the non-normalized constraints, but I think
> what we really need are the packs used in the normalized constraints,
> specifically the packs used in the target of each parameter mapping of
> each atomic constraint?

But in that case there might be no packs at all.

template <class T> C = true;
template <class ...U> requires (C<T> && ...)
constexpr bool foo () { return true; }

If normalized C<T> is just true, it doesn't use any packs.
But the [temp.constr.fold] wording assumes it is a pack expansion and that
there is at least one pack expansion parameter, otherwise N wouldn't be
defined.

	Jakub
Patrick Palka July 26, 2024, 10 p.m. UTC | #8
On Fri, 26 Jul 2024, Jakub Jelinek wrote:

> On Fri, Jul 26, 2024 at 04:42:36PM -0400, Patrick Palka wrote:
> > > // P2963R3 - Ordering of constraints involving fold expressions
> > > // { dg-do compile { target c++20 } }
> > > 
> > > template <class ...T> concept C = (__is_same (T, int) && ...);
> > > template <typename V>
> > > struct S {
> > >   template <class ...U> requires (C<U...>)
> > >   static constexpr bool foo () { return true; }
> > > };
> > > 
> > > static_assert (S<void>::foo <int, int, int, int> ());
> > > 
> > > somehow the template parameter mapping needs to be remembered even for the
> > > fold expanded constraint, right now the patch will see the pack is T,
> > > which is level 1 index 0, but args aren't arguments of the C concept,
> > > but of the foo function template.
> > > One can also use requires (C<int, int, int>) etc., no?
> > 
> > It seems the problem is FOLD_EXPR_PACKS is currently set to the
> > parameter packs used inside the non-normalized constraints, but I think
> > what we really need are the packs used in the normalized constraints,
> > specifically the packs used in the target of each parameter mapping of
> > each atomic constraint?
> 
> But in that case there might be no packs at all.
> 
> template <class T> C = true;
> template <class ...U> requires (C<T> && ...)
> constexpr bool foo () { return true; }
> 
> If normalized C<T> is just true, it doesn't use any packs.
> But the [temp.constr.fold] wording assumes it is a pack expansion and that
> there is at least one pack expansion parameter, otherwise N wouldn't be
> defined.

Hmm yeah, I see what you mean.  That seems to be an edge case that's not
fully accounted for by the wording.

One thing that's unclear to me in that wording is what are the pcak
expansion parameters of a fold expanded constraint.

In

  template<class... T> concept C = (__is_same (T, int) && ...);
  template<class U, class... V>
  void f() requires C<V...>;

is the pack expansion parameter T or V?  In

  template<class... T> concept C = (__is_same (T, int) && ...);
  template<class U>
  void g() requires C<U>;

it must be T.  So I guess in both cases it must be T.  But then I reckon
when [temp.constr.fold] mentions "pack expansion parameter(s)" what it
really means is "target of each pack expansion parameter within the
parameter mapping"...
Jakub Jelinek July 29, 2024, 1:47 p.m. UTC | #9
On Fri, Jul 26, 2024 at 06:00:12PM -0400, Patrick Palka wrote:
> On Fri, 26 Jul 2024, Jakub Jelinek wrote:
> 
> > On Fri, Jul 26, 2024 at 04:42:36PM -0400, Patrick Palka wrote:
> > > > // P2963R3 - Ordering of constraints involving fold expressions
> > > > // { dg-do compile { target c++20 } }
> > > > 
> > > > template <class ...T> concept C = (__is_same (T, int) && ...);
> > > > template <typename V>
> > > > struct S {
> > > >   template <class ...U> requires (C<U...>)
> > > >   static constexpr bool foo () { return true; }
> > > > };
> > > > 
> > > > static_assert (S<void>::foo <int, int, int, int> ());
> > > > 
> > > > somehow the template parameter mapping needs to be remembered even for the
> > > > fold expanded constraint, right now the patch will see the pack is T,
> > > > which is level 1 index 0, but args aren't arguments of the C concept,
> > > > but of the foo function template.
> > > > One can also use requires (C<int, int, int>) etc., no?
> > > 
> > > It seems the problem is FOLD_EXPR_PACKS is currently set to the
> > > parameter packs used inside the non-normalized constraints, but I think
> > > what we really need are the packs used in the normalized constraints,
> > > specifically the packs used in the target of each parameter mapping of
> > > each atomic constraint?
> > 
> > But in that case there might be no packs at all.
> > 
> > template <class T> C = true;
> > template <class ...U> requires (C<T> && ...)
> > constexpr bool foo () { return true; }
> > 
> > If normalized C<T> is just true, it doesn't use any packs.
> > But the [temp.constr.fold] wording assumes it is a pack expansion and that
> > there is at least one pack expansion parameter, otherwise N wouldn't be
> > defined.
> 
> Hmm yeah, I see what you mean.  That seems to be an edge case that's not
> fully accounted for by the wording.
> 
> One thing that's unclear to me in that wording is what are the pcak
> expansion parameters of a fold expanded constraint.
> 
> In
> 
>   template<class... T> concept C = (__is_same (T, int) && ...);
>   template<class U, class... V>
>   void f() requires C<V...>;
> 
> is the pack expansion parameter T or V?  In
> 
>   template<class... T> concept C = (__is_same (T, int) && ...);
>   template<class U>
>   void g() requires C<U>;
> 
> it must be T.  So I guess in both cases it must be T.  But then I reckon
> when [temp.constr.fold] mentions "pack expansion parameter(s)" what it
> really means is "target of each pack expansion parameter within the
> parameter mapping"...

So, shall we file some https://github.com/cplusplus/CWG/ issue about this?
Whether the packs [temp.constr.fold] talks about are the normalized ones
only (in that case what happens if there are no packs), or all packs
mentioned (in that case, whether there shouldn't be also template parameter
mappings on the fold expanded constraints like there are on the atomic
constraints (for the unexpanded packs only)?

Interesting testcases could be also:
struct A <class ...T> {};
template <class T> C = true;
template <class T> D = __is_same (T, int);
template <class ...U, class ... V> requires ((C<U> && D<V>) && ...)
constexpr bool foo (A<U...>, A<V...>) { return true; }
static_assert (foo (A<int, int>, A<int, int, int>));
// Is this valid because only V unexpanded pack from the normalized
// constraint is considered, or invalid because there are 2 packs
// and have different length?

Anyway, I'm afraid on the implementation side, ARGUMENT_PACK_SELECT
didn't help almost at all.  The problem e.g. on fold-constr7.C testcase
is that the ARGUMENT_PACK_SELECT is optimized away before it could be used.
tsubst_parameter_mapping (where I could remove the
      if (cxx_dialect >= cxx26 && ARGUMENT_PACK_P (arg))
hack without any behavior change) just tsubsts it into int type.
With the hack removed, it will go through
      if (ARGUMENT_PACK_P (arg))
        new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
but that still sets new_arg to int INTEGER_TYPE; while if a pack is used
in some nested pack expansion as well as outside of it, we'd need to arrange
to reconstruct ARGUMENT_PACK_SELECT in what tsubst_parameter_mapping
arranges.

	Jakub
Patrick Palka July 29, 2024, 9:32 p.m. UTC | #10
On Mon, 29 Jul 2024, Jakub Jelinek wrote:

> On Fri, Jul 26, 2024 at 06:00:12PM -0400, Patrick Palka wrote:
> > On Fri, 26 Jul 2024, Jakub Jelinek wrote:
> > 
> > > On Fri, Jul 26, 2024 at 04:42:36PM -0400, Patrick Palka wrote:
> > > > > // P2963R3 - Ordering of constraints involving fold expressions
> > > > > // { dg-do compile { target c++20 } }
> > > > > 
> > > > > template <class ...T> concept C = (__is_same (T, int) && ...);
> > > > > template <typename V>
> > > > > struct S {
> > > > >   template <class ...U> requires (C<U...>)
> > > > >   static constexpr bool foo () { return true; }
> > > > > };
> > > > > 
> > > > > static_assert (S<void>::foo <int, int, int, int> ());
> > > > > 
> > > > > somehow the template parameter mapping needs to be remembered even for the
> > > > > fold expanded constraint, right now the patch will see the pack is T,
> > > > > which is level 1 index 0, but args aren't arguments of the C concept,
> > > > > but of the foo function template.
> > > > > One can also use requires (C<int, int, int>) etc., no?
> > > > 
> > > > It seems the problem is FOLD_EXPR_PACKS is currently set to the
> > > > parameter packs used inside the non-normalized constraints, but I think
> > > > what we really need are the packs used in the normalized constraints,
> > > > specifically the packs used in the target of each parameter mapping of
> > > > each atomic constraint?
> > > 
> > > But in that case there might be no packs at all.
> > > 
> > > template <class T> C = true;
> > > template <class ...U> requires (C<T> && ...)
> > > constexpr bool foo () { return true; }
> > > 
> > > If normalized C<T> is just true, it doesn't use any packs.
> > > But the [temp.constr.fold] wording assumes it is a pack expansion and that
> > > there is at least one pack expansion parameter, otherwise N wouldn't be
> > > defined.
> > 
> > Hmm yeah, I see what you mean.  That seems to be an edge case that's not
> > fully accounted for by the wording.
> > 
> > One thing that's unclear to me in that wording is what are the pcak
> > expansion parameters of a fold expanded constraint.
> > 
> > In
> > 
> >   template<class... T> concept C = (__is_same (T, int) && ...);
> >   template<class U, class... V>
> >   void f() requires C<V...>;
> > 
> > is the pack expansion parameter T or V?  In
> > 
> >   template<class... T> concept C = (__is_same (T, int) && ...);
> >   template<class U>
> >   void g() requires C<U>;
> > 
> > it must be T.  So I guess in both cases it must be T.  But then I reckon
> > when [temp.constr.fold] mentions "pack expansion parameter(s)" what it
> > really means is "target of each pack expansion parameter within the
> > parameter mapping"...
> 
> So, shall we file some https://github.com/cplusplus/CWG/ issue about this?
> Whether the packs [temp.constr.fold] talks about are the normalized ones
> only (in that case what happens if there are no packs), or all packs
> mentioned (in that case, whether there shouldn't be also template parameter
> mappings on the fold expanded constraints like there are on the atomic
> constraints (for the unexpanded packs only)?

Seems worth submitting an issue, but I'm not 100% sure about my
understanding of the paper's wording..  I wonder what Jason thinks.

> 
> Interesting testcases could be also:
> struct A <class ...T> {};
> template <class T> C = true;
> template <class T> D = __is_same (T, int);
> template <class ...U, class ... V> requires ((C<U> && D<V>) && ...)
> constexpr bool foo (A<U...>, A<V...>) { return true; }
> static_assert (foo (A<int, int>, A<int, int, int>));
> // Is this valid because only V unexpanded pack from the normalized
> // constraint is considered, or invalid because there are 2 packs
> // and have different length?
> 
> Anyway, I'm afraid on the implementation side, ARGUMENT_PACK_SELECT
> didn't help almost at all.  The problem e.g. on fold-constr7.C testcase
> is that the ARGUMENT_PACK_SELECT is optimized away before it could be used.
> tsubst_parameter_mapping (where I could remove the
>       if (cxx_dialect >= cxx26 && ARGUMENT_PACK_P (arg))
> hack without any behavior change) just tsubsts it into int type.
> With the hack removed, it will go through
>       if (ARGUMENT_PACK_P (arg))
>         new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
> but that still sets new_arg to int INTEGER_TYPE; while if a pack is used
> in some nested pack expansion as well as outside of it, we'd need to arrange
> to reconstruct ARGUMENT_PACK_SELECT in what tsubst_parameter_mapping
> arranges.

Ah right, because of the double substitution -- first satisfy_atom
substitutes into the parameter mapping, and then it substitutes this
substituted parameter mapping into the atomic constraint expression.
So after the first substitution the APS might already have gotten
"resolved", I think..

IIUC the normal form of the constraint in fold-constr7.C will have
the identity parameter mapping Ts -> {Ts...}.  And you'll be passing
Ts=APS<{int,int,...}, 0> etc to the recursive satisfy_constraint_r call
in satisfy_fold.

Does it work if you wrap the ARGUMENT_PACK_SELECT in a single-element
TYPE/NONTYPE_ARGUMENT_PACK?
Jason Merrill July 30, 2024, 11:51 p.m. UTC | #11
On 7/29/24 5:32 PM, Patrick Palka wrote:
> On Mon, 29 Jul 2024, Jakub Jelinek wrote:
> 
>> On Fri, Jul 26, 2024 at 06:00:12PM -0400, Patrick Palka wrote:
>>> On Fri, 26 Jul 2024, Jakub Jelinek wrote:
>>>
>>>> On Fri, Jul 26, 2024 at 04:42:36PM -0400, Patrick Palka wrote:
>>>>>> // P2963R3 - Ordering of constraints involving fold expressions
>>>>>> // { dg-do compile { target c++20 } }
>>>>>>
>>>>>> template <class ...T> concept C = (__is_same (T, int) && ...);
>>>>>> template <typename V>
>>>>>> struct S {
>>>>>>    template <class ...U> requires (C<U...>)
>>>>>>    static constexpr bool foo () { return true; }
>>>>>> };
>>>>>>
>>>>>> static_assert (S<void>::foo <int, int, int, int> ());
>>>>>>
>>>>>> somehow the template parameter mapping needs to be remembered even for the
>>>>>> fold expanded constraint, right now the patch will see the pack is T,
>>>>>> which is level 1 index 0, but args aren't arguments of the C concept,
>>>>>> but of the foo function template.
>>>>>> One can also use requires (C<int, int, int>) etc., no?
>>>>>
>>>>> It seems the problem is FOLD_EXPR_PACKS is currently set to the
>>>>> parameter packs used inside the non-normalized constraints, but I think
>>>>> what we really need are the packs used in the normalized constraints,
>>>>> specifically the packs used in the target of each parameter mapping of
>>>>> each atomic constraint?
>>>>
>>>> But in that case there might be no packs at all.
>>>>
>>>> template <class T> C = true;
>>>> template <class ...U> requires (C<T> && ...)
>>>> constexpr bool foo () { return true; }
>>>>
>>>> If normalized C<T> is just true, it doesn't use any packs.
>>>> But the [temp.constr.fold] wording assumes it is a pack expansion and that
>>>> there is at least one pack expansion parameter, otherwise N wouldn't be
>>>> defined.
>>>
>>> Hmm yeah, I see what you mean.  That seems to be an edge case that's not
>>> fully accounted for by the wording.

I agree the wording is unclear, but it seems necessary to me that T is a 
pack expansion parameter, even if it isn't mentioned by the normalized 
constraint.

>>> One thing that's unclear to me in that wording is what are the pack
>>> expansion parameters of a fold expanded constraint.
>>>
>>> In
>>>
>>>    template<class... T> concept C = (__is_same (T, int) && ...);
>>>    template<class U, class... V>
>>>    void f() requires C<V...>;
>>>
>>> is the pack expansion parameter T or V?  In
>>>
>>>    template<class... T> concept C = (__is_same (T, int) && ...);
>>>    template<class U>
>>>    void g() requires C<U>;
>>>
>>> it must be T.  So I guess in both cases it must be T.  But then I reckon
>>> when [temp.constr.fold] mentions "pack expansion parameter(s)" what it
>>> really means is "target of each pack expansion parameter within the
>>> parameter mapping"...

Yeah.

In the paper a fold expanded constraint doesn't have a parameter 
mapping, only atomic constraints do.  Within the normal form of 
(__is_same (T, int) && ...) we have a single atomic constraint with 
parameter mapping T -> T, which only comes into play when we're checking 
satisfaction for each element.

But that doesn't specify how the packs are established.  For many cases 
it's a simple matter of connecting one pack to another, so you could 
kind of handwave it, but it isn't that hard to come up with a testcase 
that isn't so simple, say

template<class... T> concept C = (__is_same (T, int) && ...);
template <class...T> struct A { };
template <class...U, class...V>
void g(A<U...>, A<V...>) requires C<U..., V...>;

How is <U..., V...> expressed in the normalized constraints of g?

>> So, shall we file some https://github.com/cplusplus/CWG/ issue about this?
>> Whether the packs [temp.constr.fold] talks about are the normalized ones
>> only (in that case what happens if there are no packs), or all packs
>> mentioned (in that case, whether there shouldn't be also template parameter
>> mappings on the fold expanded constraints like there are on the atomic
>> constraints (for the unexpanded packs only)?

I think there should be parameter mappings for all parameter packs named 
in the fold-expression.  And I suppose for the other template parameters 
as well.

> Seems worth submitting an issue, but I'm not 100% sure about my
> understanding of the paper's wording..  I wonder what Jason thinks.
> 
>>
>> Interesting testcases could be also:
>> struct A <class ...T> {};
>> template <class T> C = true;
>> template <class T> D = __is_same (T, int);
>> template <class ...U, class ... V> requires ((C<U> && D<V>) && ...)
>> constexpr bool foo (A<U...>, A<V...>) { return true; }
>> static_assert (foo (A<int, int>, A<int, int, int>));
>> // Is this valid because only V unexpanded pack from the normalized
>> // constraint is considered, or invalid because there are 2 packs
>> // and have different length?

IMO ill-formed.

>> Anyway, I'm afraid on the implementation side, ARGUMENT_PACK_SELECT
>> didn't help almost at all.  The problem e.g. on fold-constr7.C testcase
>> is that the ARGUMENT_PACK_SELECT is optimized away before it could be used.
>> tsubst_parameter_mapping (where I could remove the
>>        if (cxx_dialect >= cxx26 && ARGUMENT_PACK_P (arg))
>> hack without any behavior change) just tsubsts it into int type.
>> With the hack removed, it will go through
>>        if (ARGUMENT_PACK_P (arg))
>>          new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
>> but that still sets new_arg to int INTEGER_TYPE; while if a pack is used
>> in some nested pack expansion as well as outside of it, we'd need to arrange
>> to reconstruct ARGUMENT_PACK_SELECT in what tsubst_parameter_mapping
>> arranges.
> 
> Ah right, because of the double substitution -- first satisfy_atom
> substitutes into the parameter mapping, and then it substitutes this
> substituted parameter mapping into the atomic constraint expression.
> So after the first substitution the APS might already have gotten
> "resolved", I think..
> 
> IIUC the normal form of the constraint in fold-constr7.C will have
> the identity parameter mapping Ts -> {Ts...}.  And you'll be passing
> Ts=APS<{int,int,...}, 0> etc to the recursive satisfy_constraint_r call
> in satisfy_fold.
> 
> Does it work if you wrap the ARGUMENT_PACK_SELECT in a single-element
> TYPE/NONTYPE_ARGUMENT_PACK?

I think trying to play games with APS in the normalized form is a 
mistake; I'd think we should only use it it when substituting elements 
of the argument pack into the atomic constraint's parameter mapping.

Jason
Jakub Jelinek July 31, 2024, 8:05 a.m. UTC | #12
On Tue, Jul 30, 2024 at 07:51:34PM -0400, Jason Merrill wrote:
> Yeah.
> 
> In the paper a fold expanded constraint doesn't have a parameter mapping,
> only atomic constraints do.  Within the normal form of (__is_same (T, int)
> && ...) we have a single atomic constraint with parameter mapping T -> T,
> which only comes into play when we're checking satisfaction for each
> element.

> > > So, shall we file some https://github.com/cplusplus/CWG/ issue about this?
> > > Whether the packs [temp.constr.fold] talks about are the normalized ones
> > > only (in that case what happens if there are no packs), or all packs
> > > mentioned (in that case, whether there shouldn't be also template parameter
> > > mappings on the fold expanded constraints like there are on the atomic
> > > constraints (for the unexpanded packs only)?
> 
> I think there should be parameter mappings for all parameter packs named in
> the fold-expression.  And I suppose for the other template parameters as
> well.

> > > Anyway, I'm afraid on the implementation side, ARGUMENT_PACK_SELECT
> > > didn't help almost at all.  The problem e.g. on fold-constr7.C testcase
> > > is that the ARGUMENT_PACK_SELECT is optimized away before it could be used.
> > > tsubst_parameter_mapping (where I could remove the
> > >        if (cxx_dialect >= cxx26 && ARGUMENT_PACK_P (arg))
> > > hack without any behavior change) just tsubsts it into int type.
> > > With the hack removed, it will go through
> > >        if (ARGUMENT_PACK_P (arg))
> > >          new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
> > > but that still sets new_arg to int INTEGER_TYPE; while if a pack is used
> > > in some nested pack expansion as well as outside of it, we'd need to arrange
> > > to reconstruct ARGUMENT_PACK_SELECT in what tsubst_parameter_mapping
> > > arranges.
> > 
> > Ah right, because of the double substitution -- first satisfy_atom
> > substitutes into the parameter mapping, and then it substitutes this
> > substituted parameter mapping into the atomic constraint expression.
> > So after the first substitution the APS might already have gotten
> > "resolved", I think..
> > 
> > IIUC the normal form of the constraint in fold-constr7.C will have
> > the identity parameter mapping Ts -> {Ts...}.  And you'll be passing
> > Ts=APS<{int,int,...}, 0> etc to the recursive satisfy_constraint_r call
> > in satisfy_fold.
> > 
> > Does it work if you wrap the ARGUMENT_PACK_SELECT in a single-element
> > TYPE/NONTYPE_ARGUMENT_PACK?
> 
> I think trying to play games with APS in the normalized form is a mistake;
> I'd think we should only use it it when substituting elements of the
> argument pack into the atomic constraint's parameter mapping.

Thanks to you both.

I'm still lost what to do exactly.
The building of parameter mappings for ATOMIC_CONSTR is done by
build_parameter_mapping but that already during the discover part
(find_template_parameters) uses ctx_parms, so I assume I can't just build
the parameter mappings for FOLD_CONSTR based on the already found unexpanded
parameter packs and just map_arguments the copy of the list or something
like that because that doesn't depend on ctx_parms.
Would satisfy_fold then need to tsubst_parameter_mappings somehow as well?

If satisfy_atom shouldn't use ARGUMENT_PACK_SELECT yet and it should be
only created during tsubst_parameter_mappings, I guess it needs some other
way how to tell tsubst_parameter_mappings of the ATOMIC_CONSTR which of the
parameter packs should have ARGUMENT_PACK_SELECT.  On the side argument
(like the packs TREE_LIST I had in the first version of the patch which
didn't really work), something else?  In any case, it needs to be something
that e.g. the satisfaction cache hash and equal can take into account.
And still am not sure how that can work.  Consider
template <class U> concept C = requires { U (1); };
template <class ...T> struct A { A (int) {} };
template <class ...V> requires ((C <A <V>> && C <V> && (sizeof (V) + ...) < 80) && ...)
constexpr bool foo () { return true; }
static_assert (foo <int, long, A <float>> ());
For the C <A <V>> case it needs to apply the ARGUMENT_PACK_SELECT right away
and just subst it into int (or later long and later A <float>), because
one needs to substitute A<int> into the ATOM_CONSTR expression, in the C <V>
case either that or could just add ARGUMENT_PACK_SELECT afterwards, and
for the last case it needs to be ARGUMENT_PACK_SELECT so that the pack
expansion expands it.

Also, if FOLD_CONSTR has the mappings, wonder if derive_fold_proof doesn't
need to change the way it checks whether the unexpanded packs are equal
(worried we could e.g. try to compare for subsumption FOLD_CONSTRs from
different templates, one from some concept check and another from a
different one or whatever, and not sure if template_args_equal will return
false if they are packs from different templates with the same index/level.

I've updated most of the testcases in the patch so that they actually use
the template argument (but left one with those).

In any case, while I've learned a lot of new stuff from C++ FE parts I
didn't even know they exist, I doubt I'd be able to finish this, do you
think you or Patrick could eventually take it over from where it is right
now (any time in the future, not necessarily now)?  I can attach it to the
PR as well.  Sorry.

2024-07-31  Jakub Jelinek  <jakub@redhat.com>

	PR c++/115746
gcc/cp/
	* cp-tree.def: Implement C++26 P2963R3 - Ordering of constraints
	involving fold expressions.
	(FOLD_CONSTR): New tree code.
	* cp-tree.h (CONSTR_P): Handle FOLD_CONSTR.
	(CONSTR_CHECK): Include FOLD_CONSTR.
	(FOLD_CONSTR_EXPR): Define.
	(FOLD_CONSTR_PACKS): Define.
	(FOLD_CONSTR_DISJ_P): Define.
	(ARGUMENT_PACK_SELECT_IMMUTABLE_P): Define.
	* cp-objcp-common.cc (cp_common_init_ts): Handle FOLD_CONSTR.
	* constraint.cc (normalize_fold_expr): New function.
	(normalize_expression): Use it for {U,BI}NARY_{LEFT,RIGHT}_FOLD_EXPR.
	(fold_constraints_identical_p): New function.
	(constraints_equivalent_p): Use it for FOLD_CONSTR.
	(add_constraint): Handle FOLD_CONSTR.
	(tsubst_parameter_mapping): Undo template_parm_to_arg returning
	parameter packs if inside fold expanded constraint.  Formatting fix
	in wrapper.
	(satisfy_fold): New function.
	(satisfy_constraint_r): Handle FOLD_CONSTR.
	* error.cc (dump_expr): Likewise.
	* logic.cc (struct clause): Add m_fold member.
	(clause::clause): Handle FOLD_CONSTR, for copy ctor copy over m_fold.
	(clause::replace, clause::insert): Handle FOLD_CONSTR.
	(clause::folds): New method.
	(atomic_p): Also return true for FOLD_CONSTR.
	(decompose_atom): Adjust function comment.
	(derive_fold_proof): New function.
	(derive_proof): Handle FOLD_CONSTR.  Change default: case to
	case ATOMIC_CONSTR, for default: add gcc_unreachable ().  Formatting
	fixes.
	(subsumes_constraints_nonnull): Use auto_cond_timevar instead of
	auto_timevar.
	* cxx-pretty-print.cc (cxx_pretty_printer::expression): Handle
	FOLD_CONSTR.
	(pp_cxx_fold_expanded_constraint): New function.
	(pp_cxx_constraint): Handle FOLD_CONSTR.
	* pt.cc (iterative_hash_template_arg, template_args_equal): Handle
	ARGUMENT_PACK_SELECT with ARGUMENT_PACK_SELECT_IMMUTABLE_P flag.
gcc/testsuite/
	* g++.dg/concepts/diagnostic3.C: Guard diagnostics on c++23_down.
	* g++.dg/concepts/variadic2.C: Likewise.
	* g++.dg/concepts/variadic4.C: Likewise.
	* g++.dg/cpp2a/concepts-requires33.C: Expect another error for c++26.
	* g++.dg/cpp26/fold-constr1.C: New test.
	* g++.dg/cpp26/fold-constr2.C: New test.
	* g++.dg/cpp26/fold-constr3.C: New test.
	* g++.dg/cpp26/fold-constr4.C: New test.
	* g++.dg/cpp26/fold-constr5.C: New test.
	* g++.dg/cpp26/fold-constr6.C: New test.
	* g++.dg/cpp26/fold-constr7.C: New test.
	* g++.dg/cpp26/fold-constr8.C: New test.
	* g++.dg/cpp26/fold-constr9.C: New test.
	* g++.dg/cpp26/fold-constr10.C: New test.

--- gcc/cp/cp-tree.def.jj	2024-07-29 09:15:52.634931633 +0200
+++ gcc/cp/cp-tree.def	2024-07-29 13:24:52.833379711 +0200
@@ -538,6 +538,12 @@ DEFTREECODE (ATOMIC_CONSTR, "atomic_cons
 DEFTREECODE (CONJ_CONSTR, "conj_constr", tcc_expression, 2)
 DEFTREECODE (DISJ_CONSTR, "disj_constr", tcc_expression, 2)
 
+/* Fold expanded constraint.
+   CONSTR_INFO provides source info to support diagnostics.
+   FOLD_CONSTR_EXPR is the constraint embedded in it,
+   FOLD_CONSTR_PACKS are the packs expanded by it.  */
+DEFTREECODE (FOLD_CONSTR, "fold_constr", tcc_expression, 2)
+
 /* The co_await expression is used to support coroutines.
 
   Op 0 is the cast expresssion (potentially modified by the
--- gcc/cp/cp-tree.h.jj	2024-07-29 09:15:52.688930926 +0200
+++ gcc/cp/cp-tree.h	2024-07-29 13:24:52.832379725 +0200
@@ -1679,11 +1679,12 @@ check_constraint_info (tree t)
 #define CONSTR_P(NODE)                  \
   (TREE_CODE (NODE) == ATOMIC_CONSTR    \
    || TREE_CODE (NODE) == CONJ_CONSTR   \
-   || TREE_CODE (NODE) == DISJ_CONSTR)
+   || TREE_CODE (NODE) == DISJ_CONSTR	\
+   || TREE_CODE (NODE) == FOLD_CONSTR)
 
 /* Valid for any normalized constraint.  */
 #define CONSTR_CHECK(NODE) \
-  TREE_CHECK3 (NODE, ATOMIC_CONSTR, CONJ_CONSTR, DISJ_CONSTR)
+  TREE_CHECK4 (NODE, ATOMIC_CONSTR, CONJ_CONSTR, DISJ_CONSTR, FOLD_CONSTR)
 
 /* The CONSTR_INFO stores normalization data for a constraint. It refers to
    the original expression and the expression or declaration
@@ -1724,6 +1725,18 @@ check_constraint_info (tree t)
 #define ATOMIC_CONSTR_EXPR(NODE) \
   CONSTR_EXPR (ATOMIC_CONSTR_CHECK (NODE))
 
+/* The constraint embedded in FOLD_CONSTR.  */
+#define FOLD_CONSTR_EXPR(NODE) \
+  TREE_OPERAND (FOLD_CONSTR_CHECK (NODE), 0)
+
+/* List of packs expanded by it.  */
+#define FOLD_CONSTR_PACKS(NODE) \
+  TREE_OPERAND (FOLD_CONSTR_CHECK (NODE), 1)
+
+/* True if FOLD_CONSTR has fold-operator ||, false for &&.  */
+#define FOLD_CONSTR_DISJ_P(NODE) \
+  TREE_STATIC (FOLD_CONSTR_CHECK (NODE))
+
 /* Whether a PARM_DECL represents a local parameter in a
    requires-expression.  */
 #define CONSTRAINT_VAR_P(NODE) \
@@ -4087,6 +4100,11 @@ struct GTY(()) lang_decl {
 #define ARGUMENT_PACK_SELECT_INDEX(NODE)				\
   (((struct tree_argument_pack_select *)ARGUMENT_PACK_SELECT_CHECK (NODE))->index)
 
+/* True in ARGUMENT_PACK_SELECT which is immutable, won't be modified
+   once it is created and so can be safely hashed.  */
+#define ARGUMENT_PACK_SELECT_IMMUTABLE_P(NODE) \
+  TREE_STATIC (ARGUMENT_PACK_SELECT_CHECK (NODE))
+
 #define FOLD_EXPR_CHECK(NODE)						\
   TREE_CHECK4 (NODE, UNARY_LEFT_FOLD_EXPR, UNARY_RIGHT_FOLD_EXPR,	\
 	       BINARY_LEFT_FOLD_EXPR, BINARY_RIGHT_FOLD_EXPR)
--- gcc/cp/cp-objcp-common.cc.jj	2024-07-29 09:15:52.578932366 +0200
+++ gcc/cp/cp-objcp-common.cc	2024-07-29 13:24:52.832379725 +0200
@@ -705,6 +705,7 @@ cp_common_init_ts (void)
   MARK_TS_EXP (CONJ_CONSTR);
   MARK_TS_EXP (DISJ_CONSTR);
   MARK_TS_EXP (ATOMIC_CONSTR);
+  MARK_TS_EXP (FOLD_CONSTR);
   MARK_TS_EXP (NESTED_REQ);
   MARK_TS_EXP (REQUIRES_EXPR);
   MARK_TS_EXP (SIMPLE_REQ);
--- gcc/cp/constraint.cc.jj	2024-07-29 09:15:52.501933374 +0200
+++ gcc/cp/constraint.cc	2024-07-31 09:17:28.707743674 +0200
@@ -852,6 +852,39 @@ normalize_atom (tree t, tree args, norm_
   return atom;
 }
 
+/* Normalize {UNARY,BINARY}_{LEFT,RIGHT}_FOLD_EXPR.  */
+
+static tree
+normalize_fold_expr (tree t, tree args, norm_info info)
+{
+  if (cxx_dialect < cxx26
+      || (FOLD_EXPR_OP (t) != TRUTH_ANDIF_EXPR
+	  && FOLD_EXPR_OP (t) != TRUTH_ORIF_EXPR)
+      || FOLD_EXPR_MODIFY_P (t))
+    return normalize_atom (t, args, info);
+
+  tree norm
+    = normalize_expression (PACK_EXPANSION_PATTERN (FOLD_EXPR_PACK (t)),
+			    args, info);
+  tree ci = (info.generate_diagnostics
+	     ? build_tree_list (t, info.context) : NULL_TREE);
+  tree params = PACK_EXPANSION_PARAMETER_PACKS (FOLD_EXPR_PACK (t));
+  tree ret = build2 (FOLD_CONSTR, ci, norm, params);
+  if (FOLD_EXPR_OP (t) == TRUTH_ORIF_EXPR)
+    FOLD_CONSTR_DISJ_P (ret) = 1;
+  if (TREE_CODE (t) == BINARY_LEFT_FOLD_EXPR
+      || TREE_CODE (t) == BINARY_RIGHT_FOLD_EXPR)
+    {
+      tree init = normalize_expression (FOLD_EXPR_INIT (t), args, info);
+      tree_code code
+	= FOLD_EXPR_OP (t) == TRUTH_ANDIF_EXPR ? CONJ_CONSTR : DISJ_CONSTR;
+      if (TREE_CODE (t) == BINARY_LEFT_FOLD_EXPR)
+	std::swap (ret, init);
+      return build2 (code, ci, ret, init);
+    }
+  return ret;
+}
+
 /* Returns the normal form of an expression.  */
 
 static tree
@@ -869,6 +902,11 @@ normalize_expression (tree t, tree args,
       return normalize_logical_operation (t, args, CONJ_CONSTR, info);
     case TRUTH_ORIF_EXPR:
       return normalize_logical_operation (t, args, DISJ_CONSTR, info);
+    case UNARY_LEFT_FOLD_EXPR:
+    case UNARY_RIGHT_FOLD_EXPR:
+    case BINARY_LEFT_FOLD_EXPR:
+    case BINARY_RIGHT_FOLD_EXPR:
+      return normalize_fold_expr (t, args, info);
     default:
       return normalize_atom (t, args, info);
     }
@@ -1055,6 +1093,28 @@ atomic_constraints_identical_p (tree t1,
   return true;
 }
 
+/* Compare two fold expanded constraints T1 and T2.  */
+
+static bool
+fold_constraints_identical_p (tree t1, tree t2)
+{
+  gcc_assert (TREE_CODE (t1) == FOLD_CONSTR);
+  gcc_assert (TREE_CODE (t2) == FOLD_CONSTR);
+
+  if (FOLD_CONSTR_DISJ_P (t1) != FOLD_CONSTR_DISJ_P (t2))
+    return false;
+
+  tree p1 = FOLD_CONSTR_PACKS (t1);
+  tree p2 = FOLD_CONSTR_PACKS (t2);
+  for (; p1 && p2; p1 = TREE_CHAIN (p1), p2 = TREE_CHAIN (p2))
+    if (!template_args_equal (TREE_VALUE (p1), TREE_VALUE (p2)))
+      return false;
+  if (p1 || p2)
+    return false;
+  return constraints_equivalent_p (FOLD_CONSTR_EXPR (t1),
+				   FOLD_CONSTR_EXPR (t2));
+}
+
 /* True if T1 and T2 are equivalent, meaning they have the same syntactic
    structure and all corresponding constraints are identical.  */
 
@@ -1082,6 +1142,10 @@ constraints_equivalent_p (tree t1, tree
       if (!atomic_constraints_identical_p (t1, t2))
 	return false;
       break;
+    case FOLD_CONSTR:
+      if (!fold_constraints_identical_p (t1, t2))
+	return false;
+      break;
     default:
       gcc_unreachable ();
     }
@@ -1126,6 +1190,10 @@ add_constraint (tree t, hash& h)
     case ATOMIC_CONSTR:
       h.merge_hash (hash_atomic_constraint (t));
       break;
+    case FOLD_CONSTR:
+      h.add_int (FOLD_CONSTR_DISJ_P (t));
+      add_constraint (FOLD_CONSTR_EXPR (t), h);
+      break;
     default:
       gcc_unreachable ();
     }
@@ -2163,6 +2231,29 @@ tsubst_parameter_mapping (tree map, tree
       tree parm = TREE_VALUE (p);
       tree arg = TREE_PURPOSE (p);
       tree new_arg;
+      if (cxx_dialect >= cxx26 && ARGUMENT_PACK_P (arg))
+	{
+	  /* template_parm_to_arg for packs wraps the template parm
+	     with {,NON}TYPE_ARGUMENT_PACK with pack expansion.
+	     For packs expanded by fold expanded constraint undo this
+	     here.  */
+	  tree v = ARGUMENT_PACK_ARGS (arg);
+	  if (TREE_VEC_LENGTH (v) == 1
+	      && PACK_EXPANSION_P (TREE_VEC_ELT (v, 0)))
+	    {
+	      tree t = PACK_EXPANSION_PATTERN (TREE_VEC_ELT (v, 0));
+	      tree e = STRIP_REFERENCE_REF (t);
+	      if (TEMPLATE_PARM_P (e))
+		{
+		  int level;
+		  int index;
+		  template_parm_level_and_index (e, &level, &index);
+		  tree a = TMPL_ARG (args, level, index);
+		  if (TREE_CODE (a) == ARGUMENT_PACK_SELECT)
+		    arg = t;
+		}
+	    }
+	}
       if (ARGUMENT_PACK_P (arg))
 	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
       else
@@ -2187,7 +2278,8 @@ tsubst_parameter_mapping (tree map, tree
 }
 
 tree
-tsubst_parameter_mapping (tree map, tree args, tsubst_flags_t complain, tree in_decl)
+tsubst_parameter_mapping (tree map, tree args, tsubst_flags_t complain,
+			  tree in_decl)
 {
   return tsubst_parameter_mapping (map, args, subst_info (complain, in_decl));
 }
@@ -2831,6 +2923,108 @@ satisfy_atom (tree t, tree args, sat_inf
   return cache.save (inst_cache.save (result));
 }
 
+/* Compute the satisfaction of a fold expanded constraint.  */
+
+static tree
+satisfy_fold (tree t, tree args, sat_info info)
+{
+  tree orig_args = args;
+  int len = -1;
+  auto_vec <int, 8> indices;
+  for (tree p = FOLD_CONSTR_PACKS (t); p; p = TREE_CHAIN (p))
+    {
+      int level, index;
+      template_parm_level_and_index (TREE_VALUE (p), &level, &index);
+      tree a = TMPL_ARG (orig_args, level, index);
+      if (TREE_CODE (a) == ARGUMENT_PACK_SELECT)
+	a = ARGUMENT_PACK_SELECT_FROM_PACK (a);
+      int this_len = TREE_VEC_LENGTH (ARGUMENT_PACK_ARGS (a));
+      if (len == -1)
+	len = this_len;
+      else if (this_len != len)
+	{
+	  if (info.diagnose_unsatisfaction_p ())
+	    {
+	      diagnosing_failed_constraint failure (t, args, info.noisy ());
+	      tree first = TREE_VALUE (FOLD_CONSTR_PACKS (t));
+	      cp_expr fold_expr = CONSTR_EXPR (t);
+	      inform (fold_expr.get_location (),
+		      "fold expanded constraint not satisfied because "
+		      "of pack length mismatch");
+	      if (TREE_CODE (first) == TYPE_PACK_EXPANSION)
+		inform (fold_expr.get_location (),
+			"%qT has length %d", first, len);
+	      else
+		inform (fold_expr.get_location (),
+			"%qE has length %d", first, len);
+	      if (TREE_CODE (TREE_VALUE (p)) == TYPE_PACK_EXPANSION)
+		inform (fold_expr.get_location (),
+			"%qT has length %d", TREE_VALUE (p), this_len);
+	      else
+		inform (fold_expr.get_location (),
+			"%qE has length %d", TREE_VALUE (p), this_len);
+	    }
+	  return error_mark_node;
+	}
+      if (len != 0)
+	{
+	  indices.safe_push (level);
+	  indices.safe_push (index);
+	}
+    }
+  gcc_checking_assert (len != -1);
+  if (len == 0)
+    {
+      /* For N = 0 fold expanded constraint with && fold operator is
+	 satisfied.  */
+      if (!FOLD_CONSTR_DISJ_P (t))
+	return boolean_true_node;
+      if (info.diagnose_unsatisfaction_p ())
+	{
+	  diagnosing_failed_constraint failure (t, args, info.noisy ());
+	  cp_expr fold_expr = CONSTR_EXPR (t);
+	  inform (fold_expr.get_location (),
+		  "fold expanded constraint not satisfied because "
+		  "of empty pack with %<||%> fold operator");
+	}
+      return boolean_false_node;
+    }
+  for (int i = 0; i < len; ++i)
+    {
+      unsigned j;
+      int level, index;
+      args = orig_args;
+      FOR_EACH_VEC_ELT (indices, j, level)
+	{
+	  ++j;
+	  indices.iterate (j, &index);
+	  if (orig_args == args)
+	    args = copy_node (orig_args);
+	  if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (orig_args))
+	    {
+	      tree v = TMPL_ARGS_LEVEL (orig_args, level);
+	      if (TMPL_ARGS_LEVEL (args, level) == v)
+		SET_TMPL_ARGS_LEVEL (args, level, copy_node (v));
+	    }
+	  tree a = TMPL_ARG (orig_args, level, index);
+	  if (TREE_CODE (a) == ARGUMENT_PACK_SELECT)
+	    a = ARGUMENT_PACK_SELECT_FROM_PACK (a);
+	  tree aps = make_node (ARGUMENT_PACK_SELECT);
+	  ARGUMENT_PACK_SELECT_FROM_PACK (aps) = a;
+	  ARGUMENT_PACK_SELECT_INDEX (aps) = i;
+	  ARGUMENT_PACK_SELECT_IMMUTABLE_P (aps) = 1;
+	  TMPL_ARG (args, level, index) = aps;
+	}
+      tree result = satisfy_constraint_r (FOLD_CONSTR_EXPR (t), args, info);
+      if (result == error_mark_node)
+	return result;
+      if (result == (FOLD_CONSTR_DISJ_P (t)
+		     ? boolean_true_node : boolean_false_node))
+	return result;
+    }
+  return FOLD_CONSTR_DISJ_P (t) ? boolean_false_node : boolean_true_node;
+}
+
 /* Determine if the normalized constraint T is satisfied.
    Returns boolean_true_node if the expression/constraint is
    satisfied, boolean_false_node if not, and error_mark_node
@@ -2856,6 +3050,8 @@ satisfy_constraint_r (tree t, tree args,
       return satisfy_disjunction (t, args, info);
     case ATOMIC_CONSTR:
       return satisfy_atom (t, args, info);
+    case FOLD_CONSTR:
+      return satisfy_fold (t, args, info);
     default:
       gcc_unreachable ();
     }
--- gcc/cp/error.cc.jj	2024-07-29 09:15:52.772929827 +0200
+++ gcc/cp/error.cc	2024-07-29 13:24:52.833379711 +0200
@@ -3097,6 +3097,7 @@ dump_expr (cxx_pretty_printer *pp, tree
     case ATOMIC_CONSTR:
     case CONJ_CONSTR:
     case DISJ_CONSTR:
+    case FOLD_CONSTR:
       {
         pp_cxx_constraint (cxx_pp, t);
         break;
--- gcc/cp/logic.cc.jj	2024-07-29 09:15:52.802929435 +0200
+++ gcc/cp/logic.cc	2024-07-29 13:24:52.834379698 +0200
@@ -65,6 +65,8 @@ struct clause
     m_terms.push_back (t);
     if (TREE_CODE (t) == ATOMIC_CONSTR)
       m_set.add (t);
+    else if (TREE_CODE (t) == FOLD_CONSTR)
+      m_fold.safe_push (t);
 
     m_current = m_terms.begin ();
   }
@@ -74,8 +76,11 @@ struct clause
      copied list of terms.  */
 
   clause (clause const& c)
-    : m_terms (c.m_terms), m_set (c.m_set), m_current (m_terms.begin ())
+    : m_terms (c.m_terms), m_set (c.m_set), m_fold (c.m_fold.length ()),
+      m_current (m_terms.begin ())
   {
+    for (auto v : &c.m_fold)
+      m_fold.quick_push (v);
     std::advance (m_current, std::distance (c.begin (), c.current ()));
   }
 
@@ -109,6 +114,8 @@ struct clause
 	if (m_set.add (t))
 	  return std::make_pair (m_terms.erase (iter), true);
       }
+    else if (TREE_CODE (t) == FOLD_CONSTR)
+      m_fold.safe_push (t);
     *iter = t;
     return std::make_pair (iter, false);
   }
@@ -126,6 +133,8 @@ struct clause
 	if (m_set.add (t))
 	  return std::make_pair (iter, false);
       }
+    else if (TREE_CODE (t) == FOLD_CONSTR)
+      m_fold.safe_push (t);
     return std::make_pair (m_terms.insert (iter, t), true);
   }
 
@@ -166,6 +175,12 @@ struct clause
     return m_set.contains (t);
   }
 
+  /* Returns vector of FOLD_CONSTR terms.  */
+
+  auto_vec<tree> &folds ()
+  {
+    return m_fold;
+  }
 
   /* Returns an iterator to the first clause in the formula.  */
 
@@ -204,6 +219,7 @@ struct clause
 
   std::list<tree> m_terms; /* The list of terms.  */
   hash_set<tree, false, atom_hasher> m_set; /* The set of atomic constraints.  */
+  auto_vec<tree> m_fold; /* The vector of fold expanded constraints.  */
   iterator m_current; /* The current term.  */
 };
 
@@ -340,7 +356,7 @@ conjunction_p (tree t)
 static inline bool
 atomic_p (tree t)
 {
-  return TREE_CODE (t) == ATOMIC_CONSTR;
+  return TREE_CODE (t) == ATOMIC_CONSTR || TREE_CODE (t) == FOLD_CONSTR;
 }
 
 /* Recursively count the number of clauses produced when converting T
@@ -626,7 +642,7 @@ decompose_disjunction (formula& f, claus
     branch_clause (f, c, t);
 }
 
-/* An atomic constraint is already decomposed.  */
+/* An atomic or fold expanded constraint is already decomposed.  */
 inline void
 decompose_atom (clause& c)
 {
@@ -691,13 +707,48 @@ derive_atomic_proof (clause& c, tree t)
   return c.contains (t);
 }
 
+/* Derive a proof of the fold expanded constraint T in clause C.  */
+
+static bool
+derive_fold_proof (clause& c, tree t, rules r)
+{
+  auto_vec<tree> &folds = c.folds ();
+  for (auto v : &folds)
+    /* [temp.constr.order]/1 - a fold expanded constraint A subsumes
+       another fold expanded constraint B if they are compatible for
+       subsumption, have the same fold-operator, and the constraint
+       of A subsumes that of B.  */
+    if (FOLD_CONSTR_DISJ_P (v) == FOLD_CONSTR_DISJ_P (t))
+      {
+	bool compat = false;
+	for (tree p1 = FOLD_CONSTR_PACKS (t); p1 && !compat;
+	     p1 = TREE_CHAIN (p1))
+	  for (tree p2 = FOLD_CONSTR_PACKS (v); p2;
+	       p2 = TREE_CHAIN (p2))
+	    /* [temp.constr.fold]/5 - Two fold expanded constraints are
+	       compatible for subsumption if their respective constraints
+	       both contain an equivalent unexpanded pack.  */
+	    if (template_args_equal (TREE_VALUE (p1), TREE_VALUE (p2)))
+	      {
+		compat = true;
+		break;
+	      }
+	if (compat
+	    && (r == left
+		? subsumes (FOLD_CONSTR_EXPR (v), FOLD_CONSTR_EXPR (t))
+		: subsumes (FOLD_CONSTR_EXPR (t), FOLD_CONSTR_EXPR (v))))
+	  return true;
+      }
+  return false;
+}
+
 /* Derive a proof of T from the terms in C.  */
 
 static bool
 derive_proof (clause& c, tree t, rules r)
 {
   switch (TREE_CODE (t))
-  {
+    {
     case CONJ_CONSTR:
       if (r == left)
         return derive_proof_for_both_operands (c, t, r);
@@ -708,9 +759,13 @@ derive_proof (clause& c, tree t, rules r
         return derive_proof_for_either_operand (c, t, r);
       else
 	return derive_proof_for_both_operands (c, t, r);
-    default:
+    case ATOMIC_CONSTR:
       return derive_atomic_proof (c, t);
-  }
+    case FOLD_CONSTR:
+      return derive_fold_proof (c, t, r);
+    default:
+      gcc_unreachable ();
+    }
 }
 
 /* Key/value pair for caching subsumption results.  This associates a pair of
@@ -787,7 +842,7 @@ save_subsumption (tree t1, tree t2, bool
 static bool
 subsumes_constraints_nonnull (tree lhs, tree rhs)
 {
-  auto_timevar time (TV_CONSTRAINT_SUB);
+  auto_cond_timevar time (TV_CONSTRAINT_SUB);
 
   if (bool *b = lookup_subsumption (lhs, rhs))
     return *b;
--- gcc/cp/cxx-pretty-print.cc.jj	2024-07-29 09:15:52.721930495 +0200
+++ gcc/cp/cxx-pretty-print.cc	2024-07-29 13:24:52.834379698 +0200
@@ -1259,6 +1259,7 @@ cxx_pretty_printer::expression (tree t)
     case ATOMIC_CONSTR:
     case CONJ_CONSTR:
     case DISJ_CONSTR:
+    case FOLD_CONSTR:
       pp_cxx_constraint (this, t);
       break;
 
@@ -2882,6 +2883,19 @@ pp_cxx_disjunction (cxx_pretty_printer *
 }
 
 void
+pp_cxx_fold_expanded_constraint (cxx_pretty_printer *pp, tree t)
+{
+  pp_left_paren (pp);
+  pp_cxx_constraint (pp, FOLD_CONSTR_EXPR (t));
+  pp_space (pp);
+  if (FOLD_CONSTR_DISJ_P (t))
+    pp_string (pp, "\\/");
+  else
+    pp_string (pp, "/\\");
+  pp_string (pp, " ...)");
+}
+
+void
 pp_cxx_constraint (cxx_pretty_printer *pp, tree t)
 {
   if (t == error_mark_node)
@@ -2901,6 +2915,10 @@ pp_cxx_constraint (cxx_pretty_printer *p
       pp_cxx_disjunction (pp, t);
       break;
 
+    case FOLD_CONSTR:
+      pp_cxx_fold_expanded_constraint (pp, t);
+      break;
+
     case EXPR_PACK_EXPANSION:
       pp->expression (TREE_OPERAND (t, 0));
       break;
--- gcc/cp/pt.cc.jj	2024-07-29 09:15:52.818929226 +0200
+++ gcc/cp/pt.cc	2024-07-29 13:24:52.831379738 +0200
@@ -1763,8 +1763,17 @@ iterative_hash_template_arg (tree arg, h
       /* Getting here with an ARGUMENT_PACK_SELECT means we're probably
 	 preserving it in a hash table, which is bad because it will change
 	 meaning when gen_elem_of_pack_expansion_instantiation changes the
-	 ARGUMENT_PACK_SELECT_INDEX.  */
-      gcc_unreachable ();
+	 ARGUMENT_PACK_SELECT_INDEX.  The exception is immutable
+	 ARGUMENT_PACK_SELECTs created by constraint.cc (satisfy_fold).  */
+      gcc_assert (ARGUMENT_PACK_SELECT_IMMUTABLE_P (arg));
+      val = iterative_hash_template_arg (ARGUMENT_PACK_SELECT_FROM_PACK (arg),
+					 val);
+      if (sizeof (ARGUMENT_PACK_SELECT_INDEX (arg)) <= sizeof (hashval_t))
+	return iterative_hash_hashval_t (ARGUMENT_PACK_SELECT_INDEX (arg),
+					 val);
+      else
+	return iterative_hash_host_wide_int (ARGUMENT_PACK_SELECT_INDEX (arg),
+					     val);
 
     case ERROR_MARK:
       return val;
@@ -9464,7 +9473,14 @@ template_args_equal (tree ot, tree nt)
   else if (ARGUMENT_PACK_P (ot) || ARGUMENT_PACK_P (nt))
     return cp_tree_equal (ot, nt);
   else if (TREE_CODE (ot) == ARGUMENT_PACK_SELECT)
-    gcc_unreachable ();
+    {
+      gcc_assert (ARGUMENT_PACK_SELECT_IMMUTABLE_P (ot)
+		  && ARGUMENT_PACK_SELECT_IMMUTABLE_P (nt));
+      return (ARGUMENT_PACK_SELECT_INDEX (ot)
+	      == ARGUMENT_PACK_SELECT_INDEX (nt)
+	      && template_args_equal (ARGUMENT_PACK_SELECT_FROM_PACK (ot),
+				      ARGUMENT_PACK_SELECT_FROM_PACK (nt)));
+    }
   else if (TYPE_P (nt) || TYPE_P (ot))
     {
       if (!(TYPE_P (nt) && TYPE_P (ot)))
--- gcc/testsuite/g++.dg/concepts/diagnostic3.C.jj	2024-07-29 09:15:52.989926988 +0200
+++ gcc/testsuite/g++.dg/concepts/diagnostic3.C	2024-07-29 13:24:52.872379199 +0200
@@ -1,4 +1,4 @@
-// { dg-do compile { target c++2a } }
+// { dg-do compile { target c++20 } }
 
 template<typename T>
   inline constexpr bool foo_v = false;
@@ -7,7 +7,7 @@ template<typename T>
   concept foo = (bool)(foo_v<T> | foo_v<T&>);
 
 template<typename... Ts>
-requires (foo<Ts> && ...) // { dg-message "19:with Ts = .int, char... evaluated to .false." }
+requires (foo<Ts> && ...) // { dg-message "19:with Ts = .int, char... evaluated to .false." "" { target c++23_down } }
 void
 bar()
 { }
@@ -16,7 +16,7 @@ template<int>
 struct S { };
 
 template<int... Is>
-requires (foo<S<Is>> && ...) // { dg-message "22:with Is = .2, 3, 4... evaluated to .false." }
+requires (foo<S<Is>> && ...) // { dg-message "22:with Is = .2, 3, 4... evaluated to .false." "" { target c++23_down } }
 void
 baz()
 { }
--- gcc/testsuite/g++.dg/concepts/variadic2.C.jj	2024-07-29 09:15:53.021926569 +0200
+++ gcc/testsuite/g++.dg/concepts/variadic2.C	2024-07-29 13:24:52.861379343 +0200
@@ -13,6 +13,7 @@ constexpr int f(Ts...) { return 1; }
 
 int main()
 {
-  static_assert(f(42) == 1); // { dg-error "ambiguous" }
-  // The associated constraints of the two functions are incomparable.
+  static_assert(f(42) == 1); // { dg-error "ambiguous" "" { target c++23_down } }
+  // The associated constraints of the two functions are incomparable before
+  // C++26.
 }
--- gcc/testsuite/g++.dg/concepts/variadic4.C.jj	2024-07-29 09:15:53.056926111 +0200
+++ gcc/testsuite/g++.dg/concepts/variadic4.C	2024-07-29 13:24:52.850379488 +0200
@@ -12,9 +12,9 @@ struct zip;
 
 template<Sequence... Seqs>
     requires requires { typename list<Seqs...>; } // && (Sequence<Seqs> && ...)
-struct zip<Seqs...> {}; // { dg-error "does not specialize" }
+struct zip<Seqs...> {}; // { dg-error "does not specialize" "" { target c++23_down } }
 // The constraints of the specialization and the sequence are not
-// comparable; the specializations are unordered.
+// comparable before C++26; the specializations are unordered.
 
 int main()
 {
--- gcc/testsuite/g++.dg/cpp2a/concepts-requires33.C.jj	2024-07-29 09:15:53.091925654 +0200
+++ gcc/testsuite/g++.dg/cpp2a/concepts-requires33.C	2024-07-29 13:24:52.872379199 +0200
@@ -2,7 +2,7 @@
 // { dg-do compile { target c++20 } }
 
 template<class... T>
-void f() requires (requires (T x) { true; } && ...);
+void f() requires (requires (T x) { true; } && ...); // { dg-error "invalid parameter type 'void'" "" { target c++26 } }
 
 int main() {
   f<int>();
--- gcc/testsuite/g++.dg/cpp26/fold-constr1.C.jj	2024-07-29 13:24:52.835379685 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr1.C	2024-07-29 13:24:52.835379685 +0200
@@ -0,0 +1,37 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class U> concept isint = __is_same (U, int);
+
+template <class... V> requires (isint<V> && ...)
+constexpr int foo (V...) { return 1; };
+
+template <class... U> requires (... || isint<U>)
+constexpr int bar (U...) { return 1; };
+
+template <class T, class... S> requires (isint<S> && ... && isint<T>)
+constexpr int baz (T, S...) { return 1; }
+
+template <class T, class... R> requires (isint<T> || ... || isint<R>)
+constexpr int qux (T, R...) { return 1; }
+
+int v1 = foo ();
+int v2 = bar ();			// { dg-error "no matching function for call to" }
+int v3 = foo (1, 2);
+int v4 = bar (1, 2);
+int v5 = foo (1L, 2);			// { dg-error "no matching function for call to" }
+int v6 = foo (1, 2L);			// { dg-error "no matching function for call to" }
+int v7 = bar (1L, 2);
+int v8 = bar (2L, 3.0, 4, 5.0);
+int v9 = bar (2LL, 3.0f, 5.0, 6ULL, 2U);// { dg-error "no matching function for call to" }
+int v10 = baz ();			// { dg-error "no matching function for call to" }
+int v11 = baz (1);
+int v12 = baz (1L);			// { dg-error "no matching function for call to" }
+int v13 = baz (1, 2, 3, 4, 5);
+int v14 = baz (1, 2, 3L, 4, 5);		// { dg-error "no matching function for call to" }
+int v15 = qux ();			// { dg-error "no matching function for call to" }
+int v16 = qux (1);
+int v17 = qux (1L);			// { dg-error "no matching function for call to" }
+int v18 = qux (1, 2.0, 3LL);
+int v19 = qux (1L, 2.0f, 3, 4ULL);
+int v20 = qux (0.0f, 1L, 2.0, 3L);	// { dg-error "no matching function for call to" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr2.C.jj	2024-07-29 13:24:52.835379685 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr2.C	2024-07-31 09:11:03.129942996 +0200
@@ -0,0 +1,56 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C1 = requires { T(1); };
+template <class T> concept C2 = C1<T> && true;
+template <class T> concept C3 = C1<T> && __is_same (T, int);
+
+template <class T> requires (C1<T>)
+constexpr bool foo (T) { return false; };
+template <class... T> requires (C2<T> && ...)
+constexpr bool foo (T...) { return true; };
+
+static_assert (!foo (0));
+static_assert (!foo (1));
+
+template <class... T> requires (C1<T> && ...)
+constexpr bool bar (T...) { return false; };
+template <class... T> requires (C2<T> && ...)
+constexpr bool bar (T...) { return true; };
+
+static_assert (bar (0));		// { dg-error "call of overloaded 'bar\\\(int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (bar ());			// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (bar (1, 2));		// { dg-error "call of overloaded 'bar\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+
+template <class... T> requires (C1<T> && ...)
+constexpr bool baz (T...) { return false; };
+template <class... T> requires (... && (C1<T> && true))
+constexpr bool baz (T...) { return true; };
+
+static_assert (baz (0));		// { dg-error "call of overloaded 'baz\\\(int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (baz ());			// { dg-error "call of overloaded 'baz\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (baz (1, 2));		// { dg-error "call of overloaded 'baz\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+
+template <typename... T> requires (C1<T> || ... || true)
+constexpr bool qux (T...) { return false; };
+template <typename... T> requires (C2<T> && ... && true)
+constexpr bool qux (T...) { return true; };
+
+static_assert (qux (0));		// { dg-error "call of overloaded 'qux\\\(int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (qux ());			// { dg-error "call of overloaded 'qux\\\(\\\)' is ambiguous" "" { target c++23_down } }
+
+constexpr bool quux (C1 auto...) { return false; }
+constexpr bool quux (C3 auto...) { return true; }
+
+static_assert (quux ());		// { dg-error "call of overloaded 'quux\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (quux (0, 0));		// { dg-error "call of overloaded 'quux\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (!quux (0L, 0));
+
+template <C1... T>
+constexpr bool corge (C1 auto...) { return false; }
+template <C3... T>
+constexpr bool corge (C3 auto...) { return true; }
+
+static_assert (corge ());		// { dg-error "call of overloaded 'corge\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (corge (0, 0));		// { dg-error "call of overloaded 'corge\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (!corge (0L, 0));
--- gcc/testsuite/g++.dg/cpp26/fold-constr3.C.jj	2024-07-29 13:24:52.835379685 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr3.C	2024-07-29 13:24:52.835379685 +0200
@@ -0,0 +1,15 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <typename ...V> struct A;
+struct Thingy {
+  static constexpr int compare (const Thingy &) { return 1; }
+};
+template <typename ...T, typename ...U>
+void f (A<T ...> *, A<U ...> *)
+requires (T::compare (U{}) && ...);	// { dg-error "has type 'int', not 'bool'" "" { target c++26 } }
+void
+g (A<Thingy, Thingy> *ap)
+{
+  f (ap, ap);				// { dg-error "no matching function for call to" "" { target c++26 } }
+}
--- gcc/testsuite/g++.dg/cpp26/fold-constr4.C.jj	2024-07-29 13:24:52.835379685 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr4.C	2024-07-31 09:11:58.544195765 +0200
@@ -0,0 +1,48 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C1 = requires { T(1); };
+template <class T> concept C2 = C1<T> && true;
+
+template <class... T> requires (C1<T> && ...)
+constexpr bool foo (T...) { return false; };
+template <class... T> requires (C2<T> || ...)
+constexpr bool foo (T...) { return true; };
+
+static_assert (foo (0));	// { dg-error "call of overloaded 'foo\\\(int\\\)' is ambiguous" }
+
+template <class... T> requires (C1<T> || ...)
+constexpr bool bar (T...) { return false; };
+template <class... T> requires (C2<T> && ...)
+constexpr bool bar (T...) { return true; };
+
+static_assert (bar (0));	// { dg-error "call of overloaded 'bar\\\(int\\\)' is ambiguous" }
+
+template <class... T> requires (C1<T> || ...)
+constexpr bool baz (T...) { return false; };
+template <class... T> requires (C2<T> || ...)
+constexpr bool baz (T...) { return true; };
+
+static_assert (baz (0));	// { dg-error "call of overloaded 'baz\\\(int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (baz ());		// { dg-error "no matching function for call to 'baz\\\(\\\)'" }
+static_assert (baz (1, 2));	// { dg-error "call of overloaded 'baz\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+
+template <class... T>
+struct U {
+  template <class... V> requires (... && C1<V>)
+  static constexpr bool foo () { return false; }
+  template <class... V> requires (... && C2<V>)
+  static constexpr bool foo () { return true; }
+  template <class... V> requires (... && C1<T>)
+  static constexpr bool bar () { return false; }
+  template <class... V> requires (... && C2<T>)
+  static constexpr bool bar () { return true; }
+  template <class... V> requires (... && C1<V>)
+  static constexpr bool baz () { return false; }
+  template <class... V> requires (... && C2<T>)
+  static constexpr bool baz () { return true; }
+};
+
+static_assert (U<int>::foo<int> ());	// { dg-error "call of overloaded 'foo\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (U<int>::bar<int> ());	// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (U<int>::baz<int> ());	// { dg-error "call of overloaded 'baz\\\(\\\)' is ambiguous" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr5.C.jj	2024-07-29 13:24:52.834379698 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr5.C	2024-07-29 13:24:52.834379698 +0200
@@ -0,0 +1,77 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+struct A {
+  using type = int;
+};
+struct B {
+  using type = long;
+};
+
+template <class T> concept C = sizeof (T) < sizeof (int) * 64;
+
+template <class... T> requires (C<typename T::type> && ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool foo () { return true; };
+
+static_assert (foo <> ());
+static_assert (foo <A> ());
+static_assert (foo <B, A, A, B> ());
+static_assert (foo <int> ());					// { dg-error "no matching function for call" }
+static_assert (foo <B, long> ());				// { dg-error "no matching function for call" }
+static_assert (foo <unsigned, A, A> ());			// { dg-error "no matching function for call" }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+
+template <class T, class... U> requires (C<typename U::type> && ... && C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool bar () { return true; };
+
+static_assert (bar <A> ());
+static_assert (bar <B, A, A, B> ());
+static_assert (bar <int> ());					// { dg-error "no matching function for call" }
+static_assert (bar <B, long> ());				// { dg-error "no matching function for call" }
+static_assert (bar <unsigned, A, A> ());			// { dg-error "no matching function for call" }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+
+template <class T, class... U> requires (C<typename T::type> && ... && C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool baz () { return true; };
+
+static_assert (baz <A> ());
+static_assert (baz <B, A, A, B> ());
+static_assert (baz <int> ());					// { dg-error "no matching function for call" }
+static_assert (baz <B, long> ());				// { dg-error "no matching function for call" }
+static_assert (baz <unsigned, A, A> ());			// { dg-error "no matching function for call" }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+
+template <class... T> requires (C<typename T::type> || ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool qux () { return true; };
+
+static_assert (qux <> ());					// { dg-error "no matching function for call" }
+static_assert (qux <A> ());
+static_assert (qux <B, A, A, B> ());
+static_assert (qux <int> ());					// { dg-error "no matching function for call" }
+static_assert (qux <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (qux <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+
+template <class T, class... U> requires (C<typename U::type> || ... || C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool corge () { return true; };
+
+static_assert (corge <A> ());
+static_assert (corge <B, A, A, B> ());
+static_assert (corge <int> ());					// { dg-error "no matching function for call" }
+static_assert (corge <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (corge <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename T::type> || ... || C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool garply () { return true; };
+
+static_assert (garply <A> ());
+static_assert (garply <B, A, A, B> ());
+static_assert (garply <int> ());				// { dg-error "no matching function for call" }
+static_assert (garply <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (garply <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
--- gcc/testsuite/g++.dg/cpp26/fold-constr6.C.jj	2024-07-29 13:24:52.834379698 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr6.C	2024-07-31 09:12:42.960596833 +0200
@@ -0,0 +1,20 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C1 = requires { T(1); };
+template <class T> concept C2 = C1<T> && true;
+
+template <class... T>
+struct U {
+  template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
+  static constexpr bool foo () { return false; }
+  template <class... V> requires ((C2<T> && ...) && ... && C2<V>)
+  static constexpr bool foo () { return true; }
+  template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
+  static constexpr bool bar () { return false; }
+  template <class... V> requires ((C2<V> && ...) && ... && C2<T>)
+  static constexpr bool bar () { return true; }
+};
+
+static_assert (U<int>::foo<int> ());	// { dg-error "call of overloaded 'foo\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (U<int>::bar<int> ());	// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
--- gcc/testsuite/g++.dg/cpp26/fold-constr7.C.jj	2024-07-29 13:24:52.834379698 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr7.C	2024-07-31 09:12:52.364470028 +0200
@@ -0,0 +1,11 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C1 = requires { T(1); };
+template <class T> concept C2 = C1<T> && true;
+
+template <class... T> requires ((((sizeof (T) + ...) < 8 * sizeof (int)) && C2<T>) && ...)
+constexpr bool foo (T...) { return true; };
+
+static_assert (foo (0));
+static_assert (foo (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); // { dg-error "no matching function for call" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr8.C.jj	2024-07-29 13:24:52.834379698 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr8.C	2024-07-29 13:24:52.834379698 +0200
@@ -0,0 +1,22 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C = __is_same (T, int);
+
+template <class ...T>
+struct A {};
+
+template <class ...T, class ...U> requires ((C<T> && C<U>) && ...)	// { dg-error "mismatched argument pack lengths while expanding '\\\(C<T> \\\&\\\& C<U>\\\)'" "" { target c++23_down } }
+constexpr bool foo (A<T...>, A<U...>) { return true; };
+// { dg-message "fold expanded constraint not satisfied because of pack length mismatch" "" { target c++26 } .-2 }
+// { dg-message "'U' has length 3" "" { target c++26 } .-3 }
+// { dg-message "'T' has length 2" "" { target c++26 } .-4 }
+
+static_assert (foo (A<int, int, int> {}, A<int, int, int> {}));
+static_assert (foo (A<int, int> {}, A<int, int, int> {}));	// { dg-error "no matching function for call to" }
+
+template <class ...T> requires (C<T> || ...)			// { dg-message "fold expanded constraint not satisfied because of empty pack with '||' fold operator" "" { target c++26 } }
+constexpr bool bar (T...) { return true; };
+
+static_assert (bar (0));
+static_assert (bar ());						// { dg-error "no matching function for call to" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr9.C.jj	2024-07-29 13:24:52.835379685 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr9.C	2024-07-29 13:24:52.835379685 +0200
@@ -0,0 +1,11 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C = __is_same (T, int);
+
+template <class... T> requires ((C<T> && ...) && ... && C<T>)
+constexpr bool foo (T...) { return true; }
+
+static_assert (foo ());
+static_assert (foo (1, 2, 3));
+static_assert (foo (1L, 2L)); // { dg-error "no matching function for call" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr10.C.jj	2024-07-29 13:24:52.835379685 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr10.C	2024-07-29 13:24:52.835379685 +0200
@@ -0,0 +1,67 @@
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+struct A {
+  using type = int;
+};
+struct B {
+  using type = long;
+};
+
+template <class T> concept C = true;
+
+template <class... T> requires (C<typename T::type> && ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool foo () { return true; };
+
+static_assert (foo <> ());
+static_assert (foo <A> ());
+static_assert (foo <B, A, A, B> ());
+static_assert (foo <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (foo <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (foo <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename U::type> && ... && C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool bar () { return true; };
+
+static_assert (bar <A> ());
+static_assert (bar <B, A, A, B> ());
+static_assert (bar <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (bar <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (bar <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename T::type> && ... && C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool baz () { return true; };
+
+static_assert (baz <A> ());
+static_assert (baz <B, A, A, B> ());
+static_assert (baz <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (baz <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (baz <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class... T> requires (C<typename T::type> || ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool qux () { return true; };
+
+static_assert (qux <> ());					// { dg-error "no matching function for call" }
+static_assert (qux <A> ());
+static_assert (qux <B, A, A, B> ());
+static_assert (qux <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (qux <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (qux <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename U::type> || ... || C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool corge () { return true; };
+
+static_assert (corge <A> ());
+static_assert (corge <B, A, A, B> ());
+static_assert (corge <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (corge <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (corge <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename T::type> || ... || C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool garply () { return true; };
+
+static_assert (garply <A> ());
+static_assert (garply <B, A, A, B> ());
+static_assert (garply <int> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (garply <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (garply <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }


	Jakub
Patrick Palka July 31, 2024, 3:39 p.m. UTC | #13
On Tue, 30 Jul 2024, Jason Merrill wrote:

> On 7/29/24 5:32 PM, Patrick Palka wrote:
> > On Mon, 29 Jul 2024, Jakub Jelinek wrote:
> > 
> > > On Fri, Jul 26, 2024 at 06:00:12PM -0400, Patrick Palka wrote:
> > > > On Fri, 26 Jul 2024, Jakub Jelinek wrote:
> > > > 
> > > > > On Fri, Jul 26, 2024 at 04:42:36PM -0400, Patrick Palka wrote:
> > > > > > > // P2963R3 - Ordering of constraints involving fold expressions
> > > > > > > // { dg-do compile { target c++20 } }
> > > > > > > 
> > > > > > > template <class ...T> concept C = (__is_same (T, int) && ...);
> > > > > > > template <typename V>
> > > > > > > struct S {
> > > > > > >    template <class ...U> requires (C<U...>)
> > > > > > >    static constexpr bool foo () { return true; }
> > > > > > > };
> > > > > > > 
> > > > > > > static_assert (S<void>::foo <int, int, int, int> ());
> > > > > > > 
> > > > > > > somehow the template parameter mapping needs to be remembered even
> > > > > > > for the
> > > > > > > fold expanded constraint, right now the patch will see the pack is
> > > > > > > T,
> > > > > > > which is level 1 index 0, but args aren't arguments of the C
> > > > > > > concept,
> > > > > > > but of the foo function template.
> > > > > > > One can also use requires (C<int, int, int>) etc., no?
> > > > > > 
> > > > > > It seems the problem is FOLD_EXPR_PACKS is currently set to the
> > > > > > parameter packs used inside the non-normalized constraints, but I
> > > > > > think
> > > > > > what we really need are the packs used in the normalized
> > > > > > constraints,
> > > > > > specifically the packs used in the target of each parameter mapping
> > > > > > of
> > > > > > each atomic constraint?
> > > > > 
> > > > > But in that case there might be no packs at all.
> > > > > 
> > > > > template <class T> C = true;
> > > > > template <class ...U> requires (C<T> && ...)
> > > > > constexpr bool foo () { return true; }
> > > > > 
> > > > > If normalized C<T> is just true, it doesn't use any packs.
> > > > > But the [temp.constr.fold] wording assumes it is a pack expansion and
> > > > > that
> > > > > there is at least one pack expansion parameter, otherwise N wouldn't
> > > > > be
> > > > > defined.
> > > > 
> > > > Hmm yeah, I see what you mean.  That seems to be an edge case that's not
> > > > fully accounted for by the wording.
> 
> I agree the wording is unclear, but it seems necessary to me that T is a pack
> expansion parameter, even if it isn't mentioned by the normalized constraint.
> 
> > > > One thing that's unclear to me in that wording is what are the pack
> > > > expansion parameters of a fold expanded constraint.
> > > > 
> > > > In
> > > > 
> > > >    template<class... T> concept C = (__is_same (T, int) && ...);
> > > >    template<class U, class... V>
> > > >    void f() requires C<V...>;
> > > > 
> > > > is the pack expansion parameter T or V?  In
> > > > 
> > > >    template<class... T> concept C = (__is_same (T, int) && ...);
> > > >    template<class U>
> > > >    void g() requires C<U>;
> > > > 
> > > > it must be T.  So I guess in both cases it must be T.  But then I reckon
> > > > when [temp.constr.fold] mentions "pack expansion parameter(s)" what it
> > > > really means is "target of each pack expansion parameter within the
> > > > parameter mapping"...
> 
> Yeah.
> 
> In the paper a fold expanded constraint doesn't have a parameter mapping, only
> atomic constraints do.  Within the normal form of (__is_same (T, int) && ...)
> we have a single atomic constraint with parameter mapping T -> T, which only
> comes into play when we're checking satisfaction for each element.
> 
> But that doesn't specify how the packs are established.  For many cases it's a
> simple matter of connecting one pack to another, so you could kind of handwave
> it, but it isn't that hard to come up with a testcase that isn't so simple,
> say
> 
> template<class... T> concept C = (__is_same (T, int) && ...);
> template <class...T> struct A { };
> template <class...U, class...V>
> void g(A<U...>, A<V...>) requires C<U..., V...>;
> 
> How is <U..., V...> expressed in the normalized constraints of g?

Couldn't the parameter mapping be just T -> {U..., V...}?  Ah but
then during satisfaction we somehow need to know to substitute the
elements of U and V serially instead of in parallel, i.e. not conflate
it with `requires (C<U, V>) && ...)', while also respecting short
circuiting and all that...

> 
> > > So, shall we file some https://github.com/cplusplus/CWG/ issue about this?
> > > Whether the packs [temp.constr.fold] talks about are the normalized ones
> > > only (in that case what happens if there are no packs), or all packs
> > > mentioned (in that case, whether there shouldn't be also template
> > > parameter
> > > mappings on the fold expanded constraints like there are on the atomic
> > > constraints (for the unexpanded packs only)?
> 
> I think there should be parameter mappings for all parameter packs named in
> the fold-expression.  And I suppose for the other template parameters as well.
> 
> > Seems worth submitting an issue, but I'm not 100% sure about my
> > understanding of the paper's wording..  I wonder what Jason thinks.
> > 
> > > 
> > > Interesting testcases could be also:
> > > struct A <class ...T> {};
> > > template <class T> C = true;
> > > template <class T> D = __is_same (T, int);
> > > template <class ...U, class ... V> requires ((C<U> && D<V>) && ...)
> > > constexpr bool foo (A<U...>, A<V...>) { return true; }
> > > static_assert (foo (A<int, int>, A<int, int, int>));
> > > // Is this valid because only V unexpanded pack from the normalized
> > > // constraint is considered, or invalid because there are 2 packs
> > > // and have different length?
> 
> IMO ill-formed.
> 
> > > Anyway, I'm afraid on the implementation side, ARGUMENT_PACK_SELECT
> > > didn't help almost at all.  The problem e.g. on fold-constr7.C testcase
> > > is that the ARGUMENT_PACK_SELECT is optimized away before it could be
> > > used.
> > > tsubst_parameter_mapping (where I could remove the
> > >        if (cxx_dialect >= cxx26 && ARGUMENT_PACK_P (arg))
> > > hack without any behavior change) just tsubsts it into int type.
> > > With the hack removed, it will go through
> > >        if (ARGUMENT_PACK_P (arg))
> > >          new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
> > > but that still sets new_arg to int INTEGER_TYPE; while if a pack is used
> > > in some nested pack expansion as well as outside of it, we'd need to
> > > arrange
> > > to reconstruct ARGUMENT_PACK_SELECT in what tsubst_parameter_mapping
> > > arranges.
> > 
> > Ah right, because of the double substitution -- first satisfy_atom
> > substitutes into the parameter mapping, and then it substitutes this
> > substituted parameter mapping into the atomic constraint expression.
> > So after the first substitution the APS might already have gotten
> > "resolved", I think..
> > 
> > IIUC the normal form of the constraint in fold-constr7.C will have
> > the identity parameter mapping Ts -> {Ts...}.  And you'll be passing
> > Ts=APS<{int,int,...}, 0> etc to the recursive satisfy_constraint_r call
> > in satisfy_fold.
> > 
> > Does it work if you wrap the ARGUMENT_PACK_SELECT in a single-element
> > TYPE/NONTYPE_ARGUMENT_PACK?
> 
> I think trying to play games with APS in the normalized form is a mistake; I'd
> think we should only use it it when substituting elements of the argument pack
> into the atomic constraint's parameter mapping.

Indeed I was thinking of using ARGUMENT_PACK_SELECT during satisfaction.
The problem with using a bare APS is that satisfaction needs to
substitute twice, first into the parameter mapping and then into the
constraint, and this first substitution could prematurely resolve the
APS.  To work around that we could wrap each APS in a single-element
argument pack and then treat the constraint as a standalone pack
expansion.  So e.g. for

  template<class... Ts>
  void f() requires ((2*Ts::value < (Ts::value + ...)) && ...)
  // normal form has the identity mapping Ts -> {Ts...}

  f<A, B, C>();

satisfy_fold would get called with Ts={A, B, C} (the template args)
satisfy_fold would recurse into satisfy_atom with Ts={APS<{A, B, C}, N} for N=0,1,2
satisfy_atom would substitute into the parameter mapping yielding
  the instantiated mapping Ts -> {APS<{A, B, C}, N>}
satisfy_atom would call make_pack_expansion on the constraint of
  the atom, turning it into a standalone pack expansion, and
  substitute into it with the instantiated mapping yielding
  {2*A::value < (A::value + B::value + C::value)}

That way the APS doesn't get prematurely resolved during the first
substitution in satisfy_atom.  Not sure how this would work in
more complicated examples, though...



By the way, for one of the examples from the paper

  template <typename X, typename... T>
  concept environment_of = (... && requires (X& x) { { get<T>(x) } -> std::same_as<T&>; } );
  auto f(sender auto&& s, environment_of<std::stop_token> auto env); // #1
  auto f(sender auto&& s, environment_of<std::stop_token, std::pmr::allocator> auto env); // #2

I don't see how the paper now allows #2 to be more constrained than #1.
Wouldn't determining that require expanding the fold during normalization
which is certainly not what the wording says?  Seems the Clang
implementation doesn't accept the example either:
https://godbolt.org/z/dG1Gc1h4a
diff mbox series

Patch

--- gcc/cp/cp-tree.def.jj	2024-07-25 21:34:46.791268760 +0200
+++ gcc/cp/cp-tree.def	2024-07-26 09:20:05.256197019 +0200
@@ -538,6 +538,12 @@  DEFTREECODE (ATOMIC_CONSTR, "atomic_cons
 DEFTREECODE (CONJ_CONSTR, "conj_constr", tcc_expression, 2)
 DEFTREECODE (DISJ_CONSTR, "disj_constr", tcc_expression, 2)
 
+/* Fold expanded constraint.
+   CONSTR_INFO provides source info to support diagnostics.
+   FOLD_CONSTR_EXPR is the constraint embedded in it,
+   FOLD_CONSTR_PACKS are the packs expanded by it.  */
+DEFTREECODE (FOLD_CONSTR, "fold_constr", tcc_expression, 2)
+
 /* The co_await expression is used to support coroutines.
 
   Op 0 is the cast expresssion (potentially modified by the
--- gcc/cp/cp-tree.h.jj	2024-07-26 08:34:18.104160097 +0200
+++ gcc/cp/cp-tree.h	2024-07-26 09:19:10.626908903 +0200
@@ -1679,11 +1679,12 @@  check_constraint_info (tree t)
 #define CONSTR_P(NODE)                  \
   (TREE_CODE (NODE) == ATOMIC_CONSTR    \
    || TREE_CODE (NODE) == CONJ_CONSTR   \
-   || TREE_CODE (NODE) == DISJ_CONSTR)
+   || TREE_CODE (NODE) == DISJ_CONSTR	\
+   || TREE_CODE (NODE) == FOLD_CONSTR)
 
 /* Valid for any normalized constraint.  */
 #define CONSTR_CHECK(NODE) \
-  TREE_CHECK3 (NODE, ATOMIC_CONSTR, CONJ_CONSTR, DISJ_CONSTR)
+  TREE_CHECK4 (NODE, ATOMIC_CONSTR, CONJ_CONSTR, DISJ_CONSTR, FOLD_CONSTR)
 
 /* The CONSTR_INFO stores normalization data for a constraint. It refers to
    the original expression and the expression or declaration
@@ -1724,6 +1725,18 @@  check_constraint_info (tree t)
 #define ATOMIC_CONSTR_EXPR(NODE) \
   CONSTR_EXPR (ATOMIC_CONSTR_CHECK (NODE))
 
+/* The constraint embedded in FOLD_CONSTR.  */
+#define FOLD_CONSTR_EXPR(NODE) \
+  TREE_OPERAND (FOLD_CONSTR_CHECK (NODE), 0)
+
+/* List of packs expanded by it.  */
+#define FOLD_CONSTR_PACKS(NODE) \
+  TREE_OPERAND (FOLD_CONSTR_CHECK (NODE), 1)
+
+/* True if FOLD_CONSTR has fold-operator ||, false for &&.  */
+#define FOLD_CONSTR_DISJ_P(NODE) \
+  TREE_STATIC (FOLD_CONSTR_CHECK (NODE))
+
 /* Whether a PARM_DECL represents a local parameter in a
    requires-expression.  */
 #define CONSTRAINT_VAR_P(NODE) \
--- gcc/cp/cp-objcp-common.cc.jj	2024-07-25 21:34:46.791268760 +0200
+++ gcc/cp/cp-objcp-common.cc	2024-07-26 08:34:40.532867127 +0200
@@ -705,6 +705,7 @@  cp_common_init_ts (void)
   MARK_TS_EXP (CONJ_CONSTR);
   MARK_TS_EXP (DISJ_CONSTR);
   MARK_TS_EXP (ATOMIC_CONSTR);
+  MARK_TS_EXP (FOLD_CONSTR);
   MARK_TS_EXP (NESTED_REQ);
   MARK_TS_EXP (REQUIRES_EXPR);
   MARK_TS_EXP (SIMPLE_REQ);
--- gcc/cp/constraint.cc.jj	2024-07-25 21:34:46.791268760 +0200
+++ gcc/cp/constraint.cc	2024-07-26 15:03:04.438445216 +0200
@@ -852,6 +852,39 @@  normalize_atom (tree t, tree args, norm_
   return atom;
 }
 
+/* Normalize {UNARY,BINARY}_{LEFT,RIGHT}_FOLD_EXPR.  */
+
+static tree
+normalize_fold_expr (tree t, tree args, norm_info info)
+{
+  if (cxx_dialect < cxx26
+      || (FOLD_EXPR_OP (t) != TRUTH_ANDIF_EXPR
+	  && FOLD_EXPR_OP (t) != TRUTH_ORIF_EXPR)
+      || FOLD_EXPR_MODIFY_P (t))
+    return normalize_atom (t, args, info);
+
+  tree norm
+    = normalize_expression (PACK_EXPANSION_PATTERN (FOLD_EXPR_PACK (t)),
+			    args, info);
+  tree ci = (info.generate_diagnostics
+	     ? build_tree_list (t, info.context) : NULL_TREE);
+  tree params = PACK_EXPANSION_PARAMETER_PACKS (FOLD_EXPR_PACK (t));
+  tree ret = build2 (FOLD_CONSTR, ci, norm, params);
+  if (FOLD_EXPR_OP (t) == TRUTH_ORIF_EXPR)
+    FOLD_CONSTR_DISJ_P (ret) = 1;
+  if (TREE_CODE (t) == BINARY_LEFT_FOLD_EXPR
+      || TREE_CODE (t) == BINARY_RIGHT_FOLD_EXPR)
+    {
+      tree init = normalize_expression (FOLD_EXPR_INIT (t), args, info);
+      tree_code code
+	= FOLD_EXPR_OP (t) == TRUTH_ANDIF_EXPR ? CONJ_CONSTR : DISJ_CONSTR;
+      if (TREE_CODE (t) == BINARY_LEFT_FOLD_EXPR)
+	std::swap (ret, init);
+      return build2 (code, ci, ret, init);
+    }
+  return ret;
+}
+
 /* Returns the normal form of an expression.  */
 
 static tree
@@ -869,6 +902,11 @@  normalize_expression (tree t, tree args,
       return normalize_logical_operation (t, args, CONJ_CONSTR, info);
     case TRUTH_ORIF_EXPR:
       return normalize_logical_operation (t, args, DISJ_CONSTR, info);
+    case UNARY_LEFT_FOLD_EXPR:
+    case UNARY_RIGHT_FOLD_EXPR:
+    case BINARY_LEFT_FOLD_EXPR:
+    case BINARY_RIGHT_FOLD_EXPR:
+      return normalize_fold_expr (t, args, info);
     default:
       return normalize_atom (t, args, info);
     }
@@ -1055,6 +1093,28 @@  atomic_constraints_identical_p (tree t1,
   return true;
 }
 
+/* Compare two fold expanded constraints T1 and T2.  */
+
+static bool
+fold_constraints_identical_p (tree t1, tree t2)
+{
+  gcc_assert (TREE_CODE (t1) == FOLD_CONSTR);
+  gcc_assert (TREE_CODE (t2) == FOLD_CONSTR);
+
+  if (FOLD_CONSTR_DISJ_P (t1) != FOLD_CONSTR_DISJ_P (t2))
+    return false;
+
+  tree p1 = FOLD_CONSTR_PACKS (t1);
+  tree p2 = FOLD_CONSTR_PACKS (t2);
+  for (; p1 && p2; p1 = TREE_CHAIN (p1), p2 = TREE_CHAIN (p2))
+    if (!template_args_equal (TREE_VALUE (p1), TREE_VALUE (p2)))
+      return false;
+  if (p1 || p2)
+    return false;
+  return constraints_equivalent_p (FOLD_CONSTR_EXPR (t1),
+				   FOLD_CONSTR_EXPR (t2));
+}
+
 /* True if T1 and T2 are equivalent, meaning they have the same syntactic
    structure and all corresponding constraints are identical.  */
 
@@ -1082,6 +1142,10 @@  constraints_equivalent_p (tree t1, tree
       if (!atomic_constraints_identical_p (t1, t2))
 	return false;
       break;
+    case FOLD_CONSTR:
+      if (!fold_constraints_identical_p (t1, t2))
+	return false;
+      break;
     default:
       gcc_unreachable ();
     }
@@ -1126,6 +1190,10 @@  add_constraint (tree t, hash& h)
     case ATOMIC_CONSTR:
       h.merge_hash (hash_atomic_constraint (t));
       break;
+    case FOLD_CONSTR:
+      h.add_int (FOLD_CONSTR_DISJ_P (t));
+      add_constraint (FOLD_CONSTR_EXPR (t), h);
+      break;
     default:
       gcc_unreachable ();
     }
@@ -2163,6 +2231,29 @@  tsubst_parameter_mapping (tree map, tree
       tree parm = TREE_VALUE (p);
       tree arg = TREE_PURPOSE (p);
       tree new_arg;
+      if (cxx_dialect >= cxx26 && ARGUMENT_PACK_P (arg))
+	{
+	  /* template_parm_to_arg for packs wraps the template parm
+	     with {,NON}TYPE_ARGUMENT_PACK with pack expansion.
+	     For packs expanded by fold expanded constraint undo this
+	     here.  */
+	  tree v = ARGUMENT_PACK_ARGS (arg);
+	  if (TREE_VEC_LENGTH (v) == 1
+	      && PACK_EXPANSION_P (TREE_VEC_ELT (v, 0)))
+	    {
+	      tree t = PACK_EXPANSION_PATTERN (TREE_VEC_ELT (v, 0));
+	      tree e = STRIP_REFERENCE_REF (t);
+	      if (TEMPLATE_PARM_P (e))
+		{
+		  int level;
+		  int index;
+		  template_parm_level_and_index (e, &level, &index);
+		  tree a = TMPL_ARG (args, level, index);
+		  if (!ARGUMENT_PACK_P (a))
+		    arg = t;
+		}
+	    }
+	}
       if (ARGUMENT_PACK_P (arg))
 	new_arg = tsubst_argument_pack (arg, args, complain, in_decl);
       else
@@ -2187,7 +2278,8 @@  tsubst_parameter_mapping (tree map, tree
 }
 
 tree
-tsubst_parameter_mapping (tree map, tree args, tsubst_flags_t complain, tree in_decl)
+tsubst_parameter_mapping (tree map, tree args, tsubst_flags_t complain,
+			  tree in_decl)
 {
   return tsubst_parameter_mapping (map, args, subst_info (complain, in_decl));
 }
@@ -2831,6 +2924,101 @@  satisfy_atom (tree t, tree args, sat_inf
   return cache.save (inst_cache.save (result));
 }
 
+/* Compute the satisfaction of a fold expanded constraint.  */
+
+static tree
+satisfy_fold (tree t, tree args, sat_info info)
+{
+  tree orig_args = args;
+  int len = -1;
+  auto_vec <int, 8> indices;
+  for (tree p = FOLD_CONSTR_PACKS (t); p; p = TREE_CHAIN (p))
+    {
+      int level, index;
+      template_parm_level_and_index (TREE_VALUE (p), &level, &index);
+      tree a = TMPL_ARG (orig_args, level, index);
+      int this_len = TREE_VEC_LENGTH (ARGUMENT_PACK_ARGS (a));
+      if (len == -1)
+	len = this_len;
+      else if (this_len != len)
+	{
+	  if (info.diagnose_unsatisfaction_p ())
+	    {
+	      diagnosing_failed_constraint failure (t, args, info.noisy ());
+	      tree first = TREE_VALUE (FOLD_CONSTR_PACKS (t));
+	      cp_expr fold_expr = CONSTR_EXPR (t);
+	      inform (fold_expr.get_location (),
+		      "fold expanded constraint not satisfied because "
+		      "of pack length mismatch");
+	      if (TREE_CODE (first) == TYPE_PACK_EXPANSION)
+		inform (fold_expr.get_location (),
+			"%qT has length %d", first, len);
+	      else
+		inform (fold_expr.get_location (),
+			"%qE has length %d", first, len);
+	      if (TREE_CODE (TREE_VALUE (p)) == TYPE_PACK_EXPANSION)
+		inform (fold_expr.get_location (),
+			"%qT has length %d", TREE_VALUE (p), this_len);
+	      else
+		inform (fold_expr.get_location (),
+			"%qE has length %d", TREE_VALUE (p), this_len);
+	    }
+	  return error_mark_node;
+	}
+      if (len != 0)
+	{
+	  indices.safe_push (level);
+	  indices.safe_push (index);
+	}
+    }
+  gcc_checking_assert (len != -1);
+  if (len == 0)
+    {
+      /* For N = 0 fold expanded constraint with && fold operator is
+	 satisfied.  */
+      if (!FOLD_CONSTR_DISJ_P (t))
+	return boolean_true_node;
+      if (info.diagnose_unsatisfaction_p ())
+	{
+	  diagnosing_failed_constraint failure (t, args, info.noisy ());
+	  cp_expr fold_expr = CONSTR_EXPR (t);
+	  inform (fold_expr.get_location (),
+		  "fold expanded constraint not satisfied because "
+		  "of empty pack with %<||%> fold operator");
+	}
+      return boolean_false_node;
+    }
+  for (int i = 0; i < len; ++i)
+    {
+      unsigned j;
+      int level, index;
+      args = orig_args;
+      FOR_EACH_VEC_ELT (indices, j, level)
+	{
+	  ++j;
+	  indices.iterate (j, &index);
+	  if (orig_args == args)
+	    args = copy_node (orig_args);
+	  if (TMPL_ARGS_HAVE_MULTIPLE_LEVELS (orig_args))
+	    {
+	      tree v = TMPL_ARGS_LEVEL (orig_args, level);
+	      if (TMPL_ARGS_LEVEL (args, level) == v)
+		SET_TMPL_ARGS_LEVEL (args, level, copy_node (v));
+	    }
+	  tree a = TMPL_ARG (orig_args, level, index);
+	  TMPL_ARG (args, level, index)
+	    = TREE_VEC_ELT (ARGUMENT_PACK_ARGS (a), i);
+	}
+      tree result = satisfy_constraint_r (FOLD_CONSTR_EXPR (t), args, info);
+      if (result == error_mark_node)
+	return result;
+      if (result == (FOLD_CONSTR_DISJ_P (t)
+		     ? boolean_true_node : boolean_false_node))
+	return result;
+    }
+  return FOLD_CONSTR_DISJ_P (t) ? boolean_false_node : boolean_true_node;
+}
+
 /* Determine if the normalized constraint T is satisfied.
    Returns boolean_true_node if the expression/constraint is
    satisfied, boolean_false_node if not, and error_mark_node
@@ -2856,6 +3044,8 @@  satisfy_constraint_r (tree t, tree args,
       return satisfy_disjunction (t, args, info);
     case ATOMIC_CONSTR:
       return satisfy_atom (t, args, info);
+    case FOLD_CONSTR:
+      return satisfy_fold (t, args, info);
     default:
       gcc_unreachable ();
     }
--- gcc/cp/error.cc.jj	2024-07-25 21:34:46.793268734 +0200
+++ gcc/cp/error.cc	2024-07-26 08:41:23.555602719 +0200
@@ -3097,6 +3097,7 @@  dump_expr (cxx_pretty_printer *pp, tree
     case ATOMIC_CONSTR:
     case CONJ_CONSTR:
     case DISJ_CONSTR:
+    case FOLD_CONSTR:
       {
         pp_cxx_constraint (cxx_pp, t);
         break;
--- gcc/cp/logic.cc.jj	2024-07-25 21:34:46.793268734 +0200
+++ gcc/cp/logic.cc	2024-07-26 09:22:29.047325077 +0200
@@ -65,6 +65,8 @@  struct clause
     m_terms.push_back (t);
     if (TREE_CODE (t) == ATOMIC_CONSTR)
       m_set.add (t);
+    else if (TREE_CODE (t) == FOLD_CONSTR)
+      m_fold.safe_push (t);
 
     m_current = m_terms.begin ();
   }
@@ -74,8 +76,11 @@  struct clause
      copied list of terms.  */
 
   clause (clause const& c)
-    : m_terms (c.m_terms), m_set (c.m_set), m_current (m_terms.begin ())
+    : m_terms (c.m_terms), m_set (c.m_set), m_fold (c.m_fold.length ()),
+      m_current (m_terms.begin ())
   {
+    for (auto v : &c.m_fold)
+      m_fold.quick_push (v);
     std::advance (m_current, std::distance (c.begin (), c.current ()));
   }
 
@@ -109,6 +114,8 @@  struct clause
 	if (m_set.add (t))
 	  return std::make_pair (m_terms.erase (iter), true);
       }
+    else if (TREE_CODE (t) == FOLD_CONSTR)
+      m_fold.safe_push (t);
     *iter = t;
     return std::make_pair (iter, false);
   }
@@ -126,6 +133,8 @@  struct clause
 	if (m_set.add (t))
 	  return std::make_pair (iter, false);
       }
+    else if (TREE_CODE (t) == FOLD_CONSTR)
+      m_fold.safe_push (t);
     return std::make_pair (m_terms.insert (iter, t), true);
   }
 
@@ -166,6 +175,12 @@  struct clause
     return m_set.contains (t);
   }
 
+  /* Returns vector of FOLD_CONSTR terms.  */
+
+  auto_vec<tree> &folds ()
+  {
+    return m_fold;
+  }
 
   /* Returns an iterator to the first clause in the formula.  */
 
@@ -204,6 +219,7 @@  struct clause
 
   std::list<tree> m_terms; /* The list of terms.  */
   hash_set<tree, false, atom_hasher> m_set; /* The set of atomic constraints.  */
+  auto_vec<tree> m_fold; /* The vector of fold expanded constraints.  */
   iterator m_current; /* The current term.  */
 };
 
@@ -340,7 +356,7 @@  conjunction_p (tree t)
 static inline bool
 atomic_p (tree t)
 {
-  return TREE_CODE (t) == ATOMIC_CONSTR;
+  return TREE_CODE (t) == ATOMIC_CONSTR || TREE_CODE (t) == FOLD_CONSTR;
 }
 
 /* Recursively count the number of clauses produced when converting T
@@ -626,7 +642,7 @@  decompose_disjunction (formula& f, claus
     branch_clause (f, c, t);
 }
 
-/* An atomic constraint is already decomposed.  */
+/* An atomic or fold expanded constraint is already decomposed.  */
 inline void
 decompose_atom (clause& c)
 {
@@ -691,13 +707,48 @@  derive_atomic_proof (clause& c, tree t)
   return c.contains (t);
 }
 
+/* Derive a proof of the fold expanded constraint T in clause C.  */
+
+static bool
+derive_fold_proof (clause& c, tree t, rules r)
+{
+  auto_vec<tree> &folds = c.folds ();
+  for (auto v : &folds)
+    /* [temp.constr.order]/1 - a fold expanded constraint A subsumes
+       another fold expanded constraint B if they are compatible for
+       subsumption, have the same fold-operator, and the constraint
+       of A subsumes that of B.  */
+    if (FOLD_CONSTR_DISJ_P (v) == FOLD_CONSTR_DISJ_P (t))
+      {
+	bool compat = false;
+	for (tree p1 = FOLD_CONSTR_PACKS (t); p1 && !compat;
+	     p1 = TREE_CHAIN (p1))
+	  for (tree p2 = FOLD_CONSTR_PACKS (v); p2;
+	       p2 = TREE_CHAIN (p2))
+	    /* [temp.constr.fold]/5 - Two fold expanded constraints are
+	       compatible for subsumption if their respective constraints
+	       both contain an equivalent unexpanded pack.  */
+	    if (template_args_equal (TREE_VALUE (p1), TREE_VALUE (p2)))
+	      {
+		compat = true;
+		break;
+	      }
+	if (compat
+	    && (r == left
+		? subsumes (FOLD_CONSTR_EXPR (v), FOLD_CONSTR_EXPR (t))
+		: subsumes (FOLD_CONSTR_EXPR (t), FOLD_CONSTR_EXPR (v))))
+	  return true;
+      }
+  return false;
+}
+
 /* Derive a proof of T from the terms in C.  */
 
 static bool
 derive_proof (clause& c, tree t, rules r)
 {
   switch (TREE_CODE (t))
-  {
+    {
     case CONJ_CONSTR:
       if (r == left)
         return derive_proof_for_both_operands (c, t, r);
@@ -708,9 +759,13 @@  derive_proof (clause& c, tree t, rules r
         return derive_proof_for_either_operand (c, t, r);
       else
 	return derive_proof_for_both_operands (c, t, r);
-    default:
+    case ATOMIC_CONSTR:
       return derive_atomic_proof (c, t);
-  }
+    case FOLD_CONSTR:
+      return derive_fold_proof (c, t, r);
+    default:
+      gcc_unreachable ();
+    }
 }
 
 /* Key/value pair for caching subsumption results.  This associates a pair of
@@ -787,7 +842,7 @@  save_subsumption (tree t1, tree t2, bool
 static bool
 subsumes_constraints_nonnull (tree lhs, tree rhs)
 {
-  auto_timevar time (TV_CONSTRAINT_SUB);
+  auto_cond_timevar time (TV_CONSTRAINT_SUB);
 
   if (bool *b = lookup_subsumption (lhs, rhs))
     return *b;
--- gcc/cp/cxx-pretty-print.cc.jj	2024-07-25 21:34:46.793268734 +0200
+++ gcc/cp/cxx-pretty-print.cc	2024-07-26 14:19:27.435984262 +0200
@@ -1259,6 +1259,7 @@  cxx_pretty_printer::expression (tree t)
     case ATOMIC_CONSTR:
     case CONJ_CONSTR:
     case DISJ_CONSTR:
+    case FOLD_CONSTR:
       pp_cxx_constraint (this, t);
       break;
 
@@ -2882,6 +2883,19 @@  pp_cxx_disjunction (cxx_pretty_printer *
 }
 
 void
+pp_cxx_fold_expanded_constraint (cxx_pretty_printer *pp, tree t)
+{
+  pp_left_paren (pp);
+  pp_cxx_constraint (pp, FOLD_CONSTR_EXPR (t));
+  pp_space (pp);
+  if (FOLD_CONSTR_DISJ_P (t))
+    pp_string (pp, "\\/");
+  else
+    pp_string (pp, "/\\");
+  pp_string (pp, " ...)");
+}
+
+void
 pp_cxx_constraint (cxx_pretty_printer *pp, tree t)
 {
   if (t == error_mark_node)
@@ -2901,6 +2915,10 @@  pp_cxx_constraint (cxx_pretty_printer *p
       pp_cxx_disjunction (pp, t);
       break;
 
+    case FOLD_CONSTR:
+      pp_cxx_fold_expanded_constraint (pp, t);
+      break;
+
     case EXPR_PACK_EXPANSION:
       pp->expression (TREE_OPERAND (t, 0));
       break;
--- gcc/testsuite/g++.dg/concepts/diagnostic3.C.jj	2023-10-16 17:25:32.456781462 +0200
+++ gcc/testsuite/g++.dg/concepts/diagnostic3.C	2024-07-26 09:32:49.042262192 +0200
@@ -1,4 +1,4 @@ 
-// { dg-do compile { target c++2a } }
+// { dg-do compile { target c++20 } }
 
 template<typename T>
   inline constexpr bool foo_v = false;
@@ -7,7 +7,7 @@  template<typename T>
   concept foo = (bool)(foo_v<T> | foo_v<T&>);
 
 template<typename... Ts>
-requires (foo<Ts> && ...) // { dg-message "19:with Ts = .int, char... evaluated to .false." }
+requires (foo<Ts> && ...) // { dg-message "19:with Ts = .int, char... evaluated to .false." "" { target c++23_down } }
 void
 bar()
 { }
@@ -16,7 +16,7 @@  template<int>
 struct S { };
 
 template<int... Is>
-requires (foo<S<Is>> && ...) // { dg-message "22:with Is = .2, 3, 4... evaluated to .false." }
+requires (foo<S<Is>> && ...) // { dg-message "22:with Is = .2, 3, 4... evaluated to .false." "" { target c++23_down } }
 void
 baz()
 { }
--- gcc/testsuite/g++.dg/concepts/variadic2.C.jj	2024-07-25 21:34:46.809268529 +0200
+++ gcc/testsuite/g++.dg/concepts/variadic2.C	2024-07-26 08:34:40.535867087 +0200
@@ -13,6 +13,7 @@  constexpr int f(Ts...) { return 1; }
 
 int main()
 {
-  static_assert(f(42) == 1); // { dg-error "ambiguous" }
-  // The associated constraints of the two functions are incomparable.
+  static_assert(f(42) == 1); // { dg-error "ambiguous" "" { target c++23_down } }
+  // The associated constraints of the two functions are incomparable before
+  // C++26.
 }
--- gcc/testsuite/g++.dg/concepts/variadic4.C.jj	2024-07-25 21:34:46.809268529 +0200
+++ gcc/testsuite/g++.dg/concepts/variadic4.C	2024-07-26 08:34:40.535867087 +0200
@@ -12,9 +12,9 @@  struct zip;
 
 template<Sequence... Seqs>
     requires requires { typename list<Seqs...>; } // && (Sequence<Seqs> && ...)
-struct zip<Seqs...> {}; // { dg-error "does not specialize" }
+struct zip<Seqs...> {}; // { dg-error "does not specialize" "" { target c++23_down } }
 // The constraints of the specialization and the sequence are not
-// comparable; the specializations are unordered.
+// comparable before C++26; the specializations are unordered.
 
 int main()
 {
--- gcc/testsuite/g++.dg/cpp2a/concepts-requires33.C	2022-12-05 11:10:37.712671571 +0100
+++ gcc/testsuite/g++.dg/cpp2a/concepts-requires33.C	2024-07-26 18:37:41.867864077 +0200
@@ -2,7 +2,7 @@ 
 // { dg-do compile { target c++20 } }
 
 template<class... T>
-void f() requires (requires (T x) { true; } && ...);
+void f() requires (requires (T x) { true; } && ...); // { dg-error "invalid parameter type 'void'" "" { target c++26 } }
 
 int main() {
   f<int>();
--- gcc/testsuite/g++.dg/cpp26/fold-constr1.C.jj	2024-07-26 08:34:40.535867087 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr1.C	2024-07-26 08:34:40.535867087 +0200
@@ -0,0 +1,37 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class U> concept isint = __is_same (U, int);
+
+template <class... V> requires (isint<V> && ...)
+constexpr int foo (V...) { return 1; };
+
+template <class... U> requires (... || isint<U>)
+constexpr int bar (U...) { return 1; };
+
+template <class T, class... S> requires (isint<S> && ... && isint<T>)
+constexpr int baz (T, S...) { return 1; }
+
+template <class T, class... R> requires (isint<T> || ... || isint<R>)
+constexpr int qux (T, R...) { return 1; }
+
+int v1 = foo ();
+int v2 = bar ();			// { dg-error "no matching function for call to" }
+int v3 = foo (1, 2);
+int v4 = bar (1, 2);
+int v5 = foo (1L, 2);			// { dg-error "no matching function for call to" }
+int v6 = foo (1, 2L);			// { dg-error "no matching function for call to" }
+int v7 = bar (1L, 2);
+int v8 = bar (2L, 3.0, 4, 5.0);
+int v9 = bar (2LL, 3.0f, 5.0, 6ULL, 2U);// { dg-error "no matching function for call to" }
+int v10 = baz ();			// { dg-error "no matching function for call to" }
+int v11 = baz (1);
+int v12 = baz (1L);			// { dg-error "no matching function for call to" }
+int v13 = baz (1, 2, 3, 4, 5);
+int v14 = baz (1, 2, 3L, 4, 5);		// { dg-error "no matching function for call to" }
+int v15 = qux ();			// { dg-error "no matching function for call to" }
+int v16 = qux (1);
+int v17 = qux (1L);			// { dg-error "no matching function for call to" }
+int v18 = qux (1, 2.0, 3LL);
+int v19 = qux (1L, 2.0f, 3, 4ULL);
+int v20 = qux (0.0f, 1L, 2.0, 3L);	// { dg-error "no matching function for call to" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr2.C.jj	2024-07-26 08:34:40.535867087 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr2.C	2024-07-26 08:34:40.535867087 +0200
@@ -0,0 +1,56 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C1 = true;
+template <class T> concept C2 = C1<T> && true;
+template <class T> concept C3 = C1<T> && __is_same (T, int);
+
+template <class T> requires (C1<T>)
+constexpr bool foo (T) { return false; };
+template <class... T> requires (C2<T> && ...)
+constexpr bool foo (T...) { return true; };
+
+static_assert (!foo (0));
+static_assert (!foo (1));
+
+template <class... T> requires (C1<T> && ...)
+constexpr bool bar (T...) { return false; };
+template <class... T> requires (C2<T> && ...)
+constexpr bool bar (T...) { return true; };
+
+static_assert (bar (0));		// { dg-error "call of overloaded 'bar\\\(int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (bar ());			// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (bar (1, 2));		// { dg-error "call of overloaded 'bar\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+
+template <class... T> requires (C1<T> && ...)
+constexpr bool baz (T...) { return false; };
+template <class... T> requires (... && (C1<T> && true))
+constexpr bool baz (T...) { return true; };
+
+static_assert (baz (0));		// { dg-error "call of overloaded 'baz\\\(int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (baz ());			// { dg-error "call of overloaded 'baz\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (baz (1, 2));		// { dg-error "call of overloaded 'baz\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+
+template <typename... T> requires (C1<T> || ... || true)
+constexpr bool qux (T...) { return false; };
+template <typename... T> requires (C2<T> && ... && true)
+constexpr bool qux (T...) { return true; };
+
+static_assert (qux (0));		// { dg-error "call of overloaded 'qux\\\(int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (qux ());			// { dg-error "call of overloaded 'qux\\\(\\\)' is ambiguous" "" { target c++23_down } }
+
+constexpr bool quux (C1 auto...) { return false; }
+constexpr bool quux (C3 auto...) { return true; }
+
+static_assert (quux ());		// { dg-error "call of overloaded 'quux\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (quux (0, 0));		// { dg-error "call of overloaded 'quux\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (!quux (0L, 0));
+
+template <C1... T>
+constexpr bool corge (C1 auto...) { return false; }
+template <C3... T>
+constexpr bool corge (C3 auto...) { return true; }
+
+static_assert (corge ());		// { dg-error "call of overloaded 'corge\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (corge (0, 0));		// { dg-error "call of overloaded 'corge\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (!corge (0L, 0));
--- gcc/testsuite/g++.dg/cpp26/fold-constr3.C.jj	2024-07-26 08:34:40.535867087 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr3.C	2024-07-26 11:12:09.129830502 +0200
@@ -0,0 +1,15 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <typename ...V> struct A;
+struct Thingy {
+  static constexpr int compare (const Thingy &) { return 1; }
+};
+template <typename ...T, typename ...U>
+void f (A<T ...> *, A<U ...> *)
+requires (T::compare (U{}) && ...);	// { dg-error "has type 'int', not 'bool'" "" { target c++26 } }
+void
+g (A<Thingy, Thingy> *ap)
+{
+  f (ap, ap);				// { dg-error "no matching function for call to" "" { target c++26 } }
+}
--- gcc/testsuite/g++.dg/cpp26/fold-constr4.C.jj	2024-07-26 08:34:40.535867087 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr4.C	2024-07-26 08:34:40.535867087 +0200
@@ -0,0 +1,48 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C1 = true;
+template <class T> concept C2 = C1<T> && true;
+
+template <class... T> requires (C1<T> && ...)
+constexpr bool foo (T...) { return false; };
+template <class... T> requires (C2<T> || ...)
+constexpr bool foo (T...) { return true; };
+
+static_assert (foo (0));	// { dg-error "call of overloaded 'foo\\\(int\\\)' is ambiguous" }
+
+template <class... T> requires (C1<T> || ...)
+constexpr bool bar (T...) { return false; };
+template <class... T> requires (C2<T> && ...)
+constexpr bool bar (T...) { return true; };
+
+static_assert (bar (0));	// { dg-error "call of overloaded 'bar\\\(int\\\)' is ambiguous" }
+
+template <class... T> requires (C1<T> || ...)
+constexpr bool baz (T...) { return false; };
+template <class... T> requires (C2<T> || ...)
+constexpr bool baz (T...) { return true; };
+
+static_assert (baz (0));	// { dg-error "call of overloaded 'baz\\\(int\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (baz ());		// { dg-error "no matching function for call to 'baz\\\(\\\)'" }
+static_assert (baz (1, 2));	// { dg-error "call of overloaded 'baz\\\(int, int\\\)' is ambiguous" "" { target c++23_down } }
+
+template <class... T>
+struct U {
+  template <class... V> requires (... && C1<V>)
+  static constexpr bool foo () { return false; }
+  template <class... V> requires (... && C2<V>)
+  static constexpr bool foo () { return true; }
+  template <class... V> requires (... && C1<T>)
+  static constexpr bool bar () { return false; }
+  template <class... V> requires (... && C2<T>)
+  static constexpr bool bar () { return true; }
+  template <class... V> requires (... && C1<V>)
+  static constexpr bool baz () { return false; }
+  template <class... V> requires (... && C2<T>)
+  static constexpr bool baz () { return true; }
+};
+
+static_assert (U<int>::foo<int> ());	// { dg-error "call of overloaded 'foo\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (U<int>::bar<int> ());	// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (U<int>::baz<int> ());	// { dg-error "call of overloaded 'baz\\\(\\\)' is ambiguous" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr5.C.jj	2024-07-26 08:34:40.535867087 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr5.C	2024-07-26 13:37:12.802463309 +0200
@@ -0,0 +1,77 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+struct A {
+  using type = int;
+};
+struct B {
+  using type = long;
+};
+
+template <class T> concept C = sizeof (T) < sizeof (int) * 64;
+
+template <class... T> requires (C<typename T::type> && ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool foo () { return true; };
+
+static_assert (foo <> ());
+static_assert (foo <A> ());
+static_assert (foo <B, A, A, B> ());
+static_assert (foo <int> ());					// { dg-error "no matching function for call" }
+static_assert (foo <B, long> ());				// { dg-error "no matching function for call" }
+static_assert (foo <unsigned, A, A> ());			// { dg-error "no matching function for call" }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+
+template <class T, class... U> requires (C<typename U::type> && ... && C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool bar () { return true; };
+
+static_assert (bar <A> ());
+static_assert (bar <B, A, A, B> ());
+static_assert (bar <int> ());					// { dg-error "no matching function for call" }
+static_assert (bar <B, long> ());				// { dg-error "no matching function for call" }
+static_assert (bar <unsigned, A, A> ());			// { dg-error "no matching function for call" }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+
+template <class T, class... U> requires (C<typename T::type> && ... && C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool baz () { return true; };
+
+static_assert (baz <A> ());
+static_assert (baz <B, A, A, B> ());
+static_assert (baz <int> ());					// { dg-error "no matching function for call" }
+static_assert (baz <B, long> ());				// { dg-error "no matching function for call" }
+static_assert (baz <unsigned, A, A> ());			// { dg-error "no matching function for call" }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+
+template <class... T> requires (C<typename T::type> || ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool qux () { return true; };
+
+static_assert (qux <> ());					// { dg-error "no matching function for call" }
+static_assert (qux <A> ());
+static_assert (qux <B, A, A, B> ());
+static_assert (qux <int> ());					// { dg-error "no matching function for call" }
+static_assert (qux <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (qux <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+// { dg-error "is not a class, struct, or union type" "" { target c++26 } .-3 }
+
+template <class T, class... U> requires (C<typename U::type> || ... || C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool corge () { return true; };
+
+static_assert (corge <A> ());
+static_assert (corge <B, A, A, B> ());
+static_assert (corge <int> ());					// { dg-error "no matching function for call" }
+static_assert (corge <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (corge <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename T::type> || ... || C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool garply () { return true; };
+
+static_assert (garply <A> ());
+static_assert (garply <B, A, A, B> ());
+static_assert (garply <int> ());				// { dg-error "no matching function for call" }
+static_assert (garply <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (garply <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
--- gcc/testsuite/g++.dg/cpp26/fold-constr6.C.jj	2024-07-26 08:34:40.535867087 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr6.C	2024-07-26 14:32:28.121132795 +0200
@@ -0,0 +1,20 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C1 = true;
+template <class T> concept C2 = C1<T> && true;
+
+template <class... T>
+struct U {
+  template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
+  static constexpr bool foo () { return false; }
+  template <class... V> requires ((C2<T> && ...) && ... && C2<V>)
+  static constexpr bool foo () { return true; }
+  template <class... V> requires ((C1<T> && ...) && ... && C1<V>)
+  static constexpr bool bar () { return false; }
+  template <class... V> requires ((C2<V> && ...) && ... && C2<T>)
+  static constexpr bool bar () { return true; }
+};
+
+static_assert (U<int>::foo<int> ());	// { dg-error "call of overloaded 'foo\\\(\\\)' is ambiguous" "" { target c++23_down } }
+static_assert (U<int>::bar<int> ());	// { dg-error "call of overloaded 'bar\\\(\\\)' is ambiguous" "" { target c++23_down } }
--- gcc/testsuite/g++.dg/cpp26/fold-constr7.C.jj	2024-07-26 11:35:01.499085336 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr7.C	2024-07-26 15:11:46.186674513 +0200
@@ -0,0 +1,11 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C1 = true;
+template <class T> concept C2 = C1<T> && true;
+
+template <class... T> requires ((((sizeof (T) + ...) < 8 * sizeof (int)) && C2<T>) && ...)
+constexpr bool foo (T...) { return true; };
+
+static_assert (foo (0));
+static_assert (foo (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); // { dg-error "no matching function for call" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr8.C.jj	2024-07-26 15:05:59.636183243 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr8.C	2024-07-26 15:05:15.027759668 +0200
@@ -0,0 +1,22 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C = __is_same (T, int);
+
+template <class ...T>
+struct A {};
+
+template <class ...T, class ...U> requires ((C<T> && C<U>) && ...)	// { dg-error "mismatched argument pack lengths while expanding '\\\(C<T> \\\&\\\& C<U>\\\)'" "" { target c++23_down } }
+constexpr bool foo (A<T...>, A<U...>) { return true; };
+// { dg-message "fold expanded constraint not satisfied because of pack length mismatch" "" { target c++26 } .-2 }
+// { dg-message "'U' has length 3" "" { target c++26 } .-3 }
+// { dg-message "'T' has length 2" "" { target c++26 } .-4 }
+
+static_assert (foo (A<int, int, int> {}, A<int, int, int> {}));
+static_assert (foo (A<int, int> {}, A<int, int, int> {}));	// { dg-error "no matching function for call to" }
+
+template <class ...T> requires (C<T> || ...)			// { dg-message "fold expanded constraint not satisfied because of empty pack with '||' fold operator" "" { target c++26 } }
+constexpr bool bar (T...) { return true; };
+
+static_assert (bar (0));
+static_assert (bar ());						// { dg-error "no matching function for call to" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr9.C.jj	2024-07-26 15:12:12.570331261 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr9.C	2024-07-26 15:12:06.613408758 +0200
@@ -0,0 +1,11 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+template <class T> concept C = __is_same (T, int);
+
+template <class... T> requires ((C<T> && ...) && ... && C<T>)
+constexpr bool foo (T...) { return true; }
+
+static_assert (foo ());
+static_assert (foo (1, 2, 3));
+static_assert (foo (1L, 2L)); // { dg-error "no matching function for call" }
--- gcc/testsuite/g++.dg/cpp26/fold-constr10.C.jj	2024-07-26 16:02:26.099421284 +0200
+++ gcc/testsuite/g++.dg/cpp26/fold-constr10.C	2024-07-26 16:03:35.256534023 +0200
@@ -0,0 +1,67 @@ 
+// P2963R3 - Ordering of constraints involving fold expressions
+// { dg-do compile { target c++20 } }
+
+struct A {
+  using type = int;
+};
+struct B {
+  using type = long;
+};
+
+template <class T> concept C = true;
+
+template <class... T> requires (C<typename T::type> && ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool foo () { return true; };
+
+static_assert (foo <> ());
+static_assert (foo <A> ());
+static_assert (foo <B, A, A, B> ());
+static_assert (foo <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (foo <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (foo <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename U::type> && ... && C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool bar () { return true; };
+
+static_assert (bar <A> ());
+static_assert (bar <B, A, A, B> ());
+static_assert (bar <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (bar <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (bar <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename T::type> && ... && C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool baz () { return true; };
+
+static_assert (baz <A> ());
+static_assert (baz <B, A, A, B> ());
+static_assert (baz <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (baz <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (baz <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class... T> requires (C<typename T::type> || ...)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool qux () { return true; };
+
+static_assert (qux <> ());					// { dg-error "no matching function for call" }
+static_assert (qux <A> ());
+static_assert (qux <B, A, A, B> ());
+static_assert (qux <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (qux <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (qux <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename U::type> || ... || C<typename T::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool corge () { return true; };
+
+static_assert (corge <A> ());
+static_assert (corge <B, A, A, B> ());
+static_assert (corge <int> ());					// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (corge <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (corge <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }
+
+template <class T, class... U> requires (C<typename T::type> || ... || C<typename U::type>)	// { dg-error "is not a class, struct, or union type" "" { target c++23_down } }
+constexpr bool garply () { return true; };
+
+static_assert (garply <A> ());
+static_assert (garply <B, A, A, B> ());
+static_assert (garply <int> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (garply <B, long> ());				// { dg-error "no matching function for call" "" { target c++23_down } }
+static_assert (garply <unsigned, A, A> ());			// { dg-error "no matching function for call" "" { target c++23_down } }