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?

Reply via email to