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 <ja...@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 > >