On Sat, 13 Jul 2024, Seyed Sajad Kahani wrote:

> On Fri, 12 Jul 2024 at 21:31, Patrick Palka <ppa...@redhat.com> wrote:
> >
> > Interesting, thanks for the detailed write-up!
> 
> Thank you very much.
> 
> > FWIW it should be much easier to obtain the partially specialised
> > tmpl/args of a class/variable specialization since GCC 14, by looking
> > at TI_PARTIAL_INFO of DECL_TEMPLATE_INFO (if it exists).
> >
> > Maybe passing the TI_PARTIAL_INFO args to do_auto_deduction where
> > appropriate might fix this PR too?
> 
> You are right, TI_PARTIAL_INFO does the job if it is used in decl.cc. I have
> done some basic tests and everything is fine with it too.
> 
> > Your approach indeed seems like a nice simplification, and while it
> > won't change the behavior of the vast majority of code, eager
> > substitution into constraints versus waiting until satisfaction
> > can cause us to reject otherwise valid code in some edge cases.
> > Consider
> >
> >   template<class T, class U>
> >   concept C = __is_same(T, int) || __is_same(T, U);
> >
> >   template<class T>
> >   void f(T t) {
> >     C<typename T::type> auto x = t;
> >   }
> >
> >   int main() {
> >     f(0);
> >   }
> >
> > With the eager approach we'd substitute T=int into 'typename T::type' during
> > instantiation of f<int>(), which fails and causes the program to be 
> > ill-formed
> > since we're not in a SFINAE context.
> >
> > With the non-eager approach we pass the original constraint C<auto, 
> > typename T::type>
> > and the full set of arguments to satisfaction, which instead substitutes and
> > evaluates each atom of the constraint in a SFINAE manner.  And since the 
> > first
> > term of the disjunction is satisfied, the entire constraint is satisfied, 
> > and
> > there's no error.
> >
> > This is basically the motivation for deferring substitution into 
> > constraints,
> > most importantly the associated constraints of template declaration.
> > Admittedly it's probably less important to do this for constrained autos, 
> > but
> > IIUC we do so anyway for QoI/consistency.  Clang and MSVC reject the above
> > testcase FWIW, indicating they do substitute into constrained autos eagerly.
> > I'm not sure if the standard is completely clear about what to do here.
> >
> > Jason would have more context, but IIUC we deliberately don't eagerly
> > substitute into constrained autos and accept the implementation
> > complexities that this approach entails.
> 
> What I can understand from 13.5.4.1.4 of N4950 is that SFINAE is not applied
> to the concepts. As it is stated: "If any such substitution results in an
> invalid type or expression, the program is ill-formed; no diagnostic is
> required." Adding that the following code is ill-formed:
> 
> template<typename T> concept A = T::value || true;
> template<typename U> concept B = A<U*>;
> template<typename V> concept C = B<V&>;
> 
> That we are rejecting it, if we add `static_assert(C<int>);` or `C auto x = 
> 1;`
> to the code.
> One could conclude that the following code is ill-formed too:
> 
> template<typename T> concept A = T::value || true;
> template<typename U> concept B = A<U*>;
> static_assert(A<int&>);

I guess you mean B<int&> here?

> 
> But we are accepting it. As far as I can understand, I cannot distinguish the
> difference between the two cases. I would appreciate if you could elaborate it
> for me.

Ah, that's because the substitution failure in the first example occurs
during constraint _normalization_, and in second example it occurs
during atomic constraint _satisfaction_.  Substitution failure during
constraint normalization is indeed a hard error according to
https://eel.is/c++draft/temp.constr.normal#1.4.sentence-2 as you cited.
But substitution failure during satisfaction just causes the current
atomic constraint to silently evaluate to false.

> 
> If SFINAE applies to the disjunctions of the atomic constraints, I can make a
> new patch to use TI_PARTIAL_INFO, as you suggested. Otherwise, in addition to
> these changes, we also need to modify constraint_satisfaction_value
> (constraints.cc) as well to raise an error for the static_assert and other
> usages of concept expressions as well.

A patch using TI_PARTIAL_INFO would be much appreciated :)

> 
> On Fri, 12 Jul 2024 at 21:31, Patrick Palka <ppa...@redhat.com> wrote:
> >
> > Interesting, thanks for the detailed write-up!
> >
> > On Tue, 9 Jul 2024, Seyed Sajad Kahani wrote:
> >
> > > Hi.
> > >
> > > While investigating a fix for C++/PR115030 (a bug in constrained auto
> > > deduction), I was wondering why we are not substituting constraint args 
> > > of an
> > > auto node in tsubst (pt.cc:16533). Instead, this substitution is delayed 
> > > until
> > > do_auto_deduction (pt.cc), where we attempt to find the substituted args 
> > > of the
> > > enclosing scope. At that point, it becomes difficult to find partially
> > > specialised args, as they are erased in the process.
> >
> > FWIW it should be much easier to obtain the partially specialised
> > tmpl/args of a class/variable specialization since GCC 14, by looking
> > at TI_PARTIAL_INFO of DECL_TEMPLATE_INFO (if it exists).
> >
> > Maybe passing the TI_PARTIAL_INFO args to do_auto_deduction where
> > appropriate might fix this PR too?
> >
> > > I propose modifying tsubst to eagerly substitute the constraint args of 
> > > an auto node.
> > >
> > > By making this change, we would not need to provide outer_targs for
> > > do_auto_deduction in cases where tsubst has been called for the type, 
> > > which
> > > covers most scenarios. However, we still need outer_targs for cases like:
> > >
> > > template <typename T, C<T> auto V>
> > > struct S { };
> > >
> > > Hence, outer_targs cannot be completely removed but will be set to 
> > > TREE_NULL in
> > > all calls except the one in convert_template_argument (pt.cc:8788).
> > > Additionally, the tmpl argument of do_auto_deduction, which helps to 
> > > provide
> > > outer args in the scope, will no longer be necessary and can be safely
> > > removed.
> > >
> > > Also, the trimming hack proposed in
> > > https://gcc.gnu.org/pipermail/gcc-patches/2024-June/654724.html to address
> > > C++/PR114915 is no longer needed. We will add an assertion to ensure that 
> > > the
> > > missing levels do not become negative.
> > >
> > > Substituting constraint arguments earlier will slightly alter error 
> > > messages
> > > (see testsuite/g++.dg/cpp2a/concepts-placeholder3.C as an example).
> > >
> > > To summarise, I have made the following changes:
> > > - Modified tsubst to substitute the constraint args.
> > > - Fixed shallow checks for using the cache from TEMPLATE_TYPE_DESCENDANTS
> > > (pt.cc:16513).
> > > - Substituted the constraint args and the auto node itself, while 
> > > retaining all
> > > other parameters as is (pt.cc:16533).
> > > - Removed now unnecessary code that attempted to find outer scope 
> > > template info
> > > and args in various locations.
> > > - Updated the missing levels hack (pt.cc:31320) to work with the 
> > > substituted
> > > constraints.
> > > - Used level instead of orig_level to find the missing levels.
> > > - Added an assertion for future safety.
> > >
> > > Details of these changes can be found in the patch "[PATCH v1] c++: 
> > > Eagerly
> > > substitute auto constraint args in tsubst [PR115030]" that will be 
> > > replied to
> > > this thread.
> > >
> > > This patch, in my opinion, improves code quality by removing an argument 
> > > from
> > > do_auto_deduction and eliminating the out-of-scope task of finding 
> > > outer_targs
> > > within do_auto_deduction. It simplifies the usage of do_auto_deduction and
> > > resolves C++/PR115030 without complex calculations for specialised args. 
> > > It
> > > also enhances the consistency of tsubst behaviour by not leaving 
> > > constraints
> > > un-substituted. However, these changes make args in 
> > > constraints_satisfied_p
> > > (being called from do_auto_deduction) a bit misleading, as it will not 
> > > carry
> > > the actual args of the constraint and can even be an empty vec.
> >
> > Your approach indeed seems like a nice simplification, and while it
> > won't change the behavior of the vast majority of code, eager
> > substitution into constraints versus waiting until satisfaction
> > can cause us to reject otherwise valid code in some edge cases.
> > Consider
> >
> >   template<class T, class U>
> >   concept C = __is_same(T, int) || __is_same(T, U);
> >
> >   template<class T>
> >   void f(T t) {
> >     C<typename T::type> auto x = t;
> >   }
> >
> >   int main() {
> >     f(0);
> >   }
> >
> > With the eager approach we'd substitute T=int into 'typename T::type' during
> > instantiation of f<int>(), which fails and causes the program to be 
> > ill-formed
> > since we're not in a SFINAE context.
> >
> > With the non-eager approach we pass the original constraint C<auto, 
> > typename T::type>
> > and the full set of arguments to satisfaction, which instead substitutes and
> > evaluates each atom of the constraint in a SFINAE manner.  And since the 
> > first
> > term of the disjunction is satisfied, the entire constraint is satisfied, 
> > and
> > there's no error.
> >
> > This is basically the motivation for deferring substitution into 
> > constraints,
> > most importantly the associated constraints of template declaration.
> > Admittedly it's probably less important to do this for constrained autos, 
> > but
> > IIUC we do so anyway for QoI/consistency.  Clang and MSVC reject the above
> > testcase FWIW, indicating they do substitute into constrained autos eagerly.
> > I'm not sure if the standard is completely clear about what to do here.
> >
> > Jason would have more context, but IIUC we deliberately don't eagerly
> > substitute into constrained autos and accept the implementation
> > complexities that this approach entails.
> >
> > >
> > > I have added a testsuite for C++/PR115030, and as far as I have tested 
> > > (only
> > > c++ dg.exp on x86_64-linux), there are no regressions.
> > >
> > > Extra:
> > > While doing this, I also realised that the hash function in ctp_hasher 
> > > (for
> > > canonical type of template parameters) slightly differs from its equality
> > > function. Specifically, the equality function uses comptypes (typeck.cc) 
> > > (with
> > > COMPARE_STRUCTURAL) and compares placeholder constraints (typeck.cc:1586),
> > > while the hash function ignores them (pt.cc:4528). As a result, we can 
> > > have two
> > > types with equal hashes that are unequal. For example, assuming:
> > >
> > > template <typename T, typename U>
> > > concept C1 = ...
> > >
> > > template <typename T, typename U>
> > > concept C2 = ...
> > >
> > >
> > > "C1<U> auto" and "C2<V> auto" have the same hash value but are unequal. 
> > > This
> > > issue does not cause any error (it is a hash collision and it is 
> > > handled), but
> > > it can be avoided by using hash_placeholder_constraint 
> > > (constraint.cc:1150).
> > >
> > > Therefore, I have made the following changes:
> > > - Fixed the hash function to calculate the hash of the constraint 
> > > (pt.cc:4528).
> > > - Slightly modified the existing hash_placeholder_constraint
> > > (constraint.cc:1150) to accept the initial hash value as an argument.
> > >
> > > Details of these changes can be found in the patch "[PATCH v1] c++: Hash
> > > placeholder constraint in ctp_hasher" that will be replied to this email.
> > >
> > > Thanks. I really appreciate your comments and feedback on these proposed
> > > changes.
> > >
> > >
> >
> 
> 

Reply via email to