On Mon, 28 Sep 2020, Jason Merrill wrote:

> On 9/25/20 4:44 PM, Patrick Palka wrote:
> > In the testcase concepts7.C below, we currently reject the call to f1
> > but we accept the call to f2, even though their associated constraints
> > are functionally equivalent.
> 
> Rather, because they are not functionally equivalent.
> 
> > The reason satisfaction differs for (!!C<typename T::type) and
> > C<typename T::type> is due to normalization: the former is already an
> > atom, and the latter is not.  Normalization of the former yields itself,
> > whereas normalization of the latter yields the atom 'true' with an empty
> > parameter mapping (since the atom uses no template parameters).
> 
> Yes.
> 
> > So when
> > building the latter atom we threw away the T::type term that would later
> > result in substitution failure during satisfaction.
> > 
> > However, [temp.constr.normal]/1 says:
> > 
> >    - ...
> >    - The normal form of a concept-id C<A1, A2, ..., An> is the normal
> >      form of the constraint-expression of C, after substituting A1, A2,
> >      ..., An for C's respective template parameters in the parameter
> >      mappings in each atomic constraint.
> >    - The normal form of any other expression E is the atomic constraint
> >      whose expression is E and whose parameter mapping is the identity
> >      mapping.
> > 
> > I believe these two bullet points imply that the atom 'true' in the
> > normal form of C<typename T::type> should have the mapping R |-> T::type
> > instead of the empty mapping that we give it, because according to the
> > last bullet point, each atom should start out with the identity mapping
> > that includes all template parameters.
> 
> But [temp.constr.atomic] says,
> 
> An atomic constraint is formed from an expression E and a mapping from the
> template parameters that appear within E...

I see..  Well, that certainly settles that :)  I definitely read that
passage more than once, but I must have glossed over this key wording
each time.  That, and my misinterpreting of "the identity mapping" as
"the identity mapping over all template parameters" ultimately led me
down the wrong path.

> 
> So the identity mapping only includes template parameters that appear within
> the expression, which in this case is none.
> 
> Also, the discussion of the example in [temp.constr.normal] says,
> 
> "Normalization of B’s constraint-expression is valid and results in T::value
> (with the mapping T → U*) V true (with an empty mapping)."
> 
> So I think the testcase is already handled as specified.

Got it.  Thanks for your clarification, and sorry for the noise.  I'll
make sure to read the standard ever more carefully next time.

> 
> > This patch fixes this issue by always giving the first atom in the
> > normal form of each concept a 'complete' parameter mapping, i.e. one
> > that includes all template parameters.  I think it suffices to do this
> > only for the first atom so that we catch substitution failures like in
> > concepts7.C at the right time.  For the other atoms, their mappings can
> > continue to include only template parameters used in the atom.
> > 
> > I noticed that PR92268 alludes to this issue, so this patch refers to
> > that PR and adds the PR's first testcase which we now accept.
> > 
> > Is the above interpretation of the standard correct here?  If so, does
> > this seem like a good approach?
> > 
> > gcc/cp/ChangeLog:
> > 
> >     PR c++/92268
> >     * constraint.cc (build_parameter_mapping): Add a bool parameter
> >     'complete'.  When 'complete' is true, then include all in-scope
> >     template parameters in the mapping.
> >     (norm_info::update_context): Pass false as the 'complete'
> >     argument to build_parameter_mapping.
> >     (norm_info::normalized_first_atom_p): New bool data member.
> >     (normalize_logical_operation): Set info.normalized_first_atom_p
> >     after normalizing the left operand.
> >     (normalize_concept_check): Reset info.normalized_first_atom_p
> >     before normalizing this concept.
> >     (normalize_atom): Always give the first atom of a concept
> >     definition a complete parameter mapping.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> >     PR c++/92268
> >     * g++.dg/cpp2a/concepts-pr92268.C: New test.
> >     * g++.dg/cpp2a/concepts-return-req1.C: Don't expect an error,
> >     as the call is no longer ambiguous.
> >     * g++.dg/cpp2a/concepts7.C: New test.
> >     * g++.dg/cpp2a/concepts8.C: New test.
> > ---
> >   gcc/cp/constraint.cc                          | 44 ++++++++++++++++---
> >   gcc/testsuite/g++.dg/cpp2a/concepts-pr92268.C | 42 ++++++++++++++++++
> >   .../g++.dg/cpp2a/concepts-return-req1.C       |  2 +-
> >   gcc/testsuite/g++.dg/cpp2a/concepts7.C        |  9 ++++
> >   gcc/testsuite/g++.dg/cpp2a/concepts8.C        | 25 +++++++++++
> >   5 files changed, 116 insertions(+), 6 deletions(-)
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-pr92268.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts7.C
> >   create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts8.C
> > 
> > diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
> > index d49957a6c4a..729d02b73d7 100644
> > --- a/gcc/cp/constraint.cc
> > +++ b/gcc/cp/constraint.cc
> > @@ -559,10 +559,14 @@ map_arguments (tree parms, tree args)
> >     return parms;
> >   }
> >   -/* Build the parameter mapping for EXPR using ARGS.  */
> > +/* Build the parameter mapping for EXPR using ARGS.
> > +
> > +   If COMPLETE then return the complete parameter mapping that includes
> > +   all in-scope template parameters.  Otherwise include only the
> > +   parameters used by EXPR.  */
> >     static tree
> > -build_parameter_mapping (tree expr, tree args, tree decl)
> > +build_parameter_mapping (tree expr, tree args, tree decl, bool complete)
> >   {
> >     tree ctx_parms = NULL_TREE;
> >     if (decl)
> > @@ -579,6 +583,24 @@ build_parameter_mapping (tree expr, tree args, tree
> > decl)
> >         ctx_parms = current_template_parms;
> >       }
> >   +  if (!ctx_parms)
> > +    return NULL_TREE;
> > +
> > +  if (complete)
> > +    {
> > +      if (!processing_template_parmlist)
> > +   /* Search through ctx_parms to build a complete mapping.  */
> > +   expr = template_parms_to_args (ctx_parms);
> > +      else
> > +   /* The expression might use parameters introduced in the currently
> > +      open template parameter list, which ctx_parms doesn't yet have.
> > +      So we need to search through the expression in addition to
> > +      ctx_parms.  */
> > +   expr = tree_cons (NULL_TREE, expr,
> > +                     build_tree_list (NULL_TREE,
> > +                                      template_parms_to_args
> > (ctx_parms)));
> > +    }
> > +
> >     tree parms = find_template_parameters (expr, ctx_parms);
> >     tree map = map_arguments (parms, args);
> >     return map;
> > @@ -635,7 +657,8 @@ struct norm_info : subst_info
> >     {
> >       if (generate_diagnostics ())
> >         {
> > -   tree map = build_parameter_mapping (expr, args, in_decl);
> > +   tree map = build_parameter_mapping (expr, args, in_decl,
> > +                                       /*complete=*/false);
> >     context = tree_cons (map, expr, context);
> >         }
> >       in_decl = get_concept_check_template (expr);
> > @@ -647,6 +670,9 @@ struct norm_info : subst_info
> >        for that check.  */
> >       tree context;
> > +
> > +  /* True if we've normalized the first atom of this concept.  */
> > +  bool normalized_first_atom_p = false;
> >   };
> >     static tree normalize_expression (tree, tree, norm_info);
> > @@ -658,6 +684,7 @@ static tree
> >   normalize_logical_operation (tree t, tree args, tree_code c, norm_info
> > info)
> >   {
> >     tree t0 = normalize_expression (TREE_OPERAND (t, 0), args, info);
> > +  info.normalized_first_atom_p = true;
> >     tree t1 = normalize_expression (TREE_OPERAND (t, 1), args, info);
> >       /* Build a new info object for the constraint.  */
> > @@ -706,6 +733,7 @@ normalize_concept_check (tree check, tree args,
> > norm_info info)
> >       return error_mark_node;
> >       info.update_context (check, args);
> > +  info.normalized_first_atom_p = false;
> >     return normalize_expression (def, subst, info);
> >   }
> >   @@ -722,8 +750,14 @@ normalize_atom (tree t, tree args, norm_info info)
> >     if (concept_check_p (t))
> >       return normalize_concept_check (t, args, info);
> >   -  /* Build the parameter mapping for the atom.  */
> > -  tree map = build_parameter_mapping (t, args, info.in_decl);
> > +  /* Build the parameter mapping for the atom.  If T is the first
> > +     atom of a concept definition then we want this mapping to be
> > +     complete, so that during satisfaction we check all substituted
> > +     template parameters first and foremost.  */
> > +  bool complete = (!info.normalized_first_atom_p
> > +              && info.in_decl != NULL_TREE
> > +              && concept_definition_p (info.in_decl));
> > +  tree map = build_parameter_mapping (t, args, info.in_decl, complete);
> >       /* Build a new info object for the atom.  */
> >     tree ci = build_tree_list (t, info.context);
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-pr92268.C
> > b/gcc/testsuite/g++.dg/cpp2a/concepts-pr92268.C
> > new file mode 100644
> > index 00000000000..817cd3187bd
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-pr92268.C
> > @@ -0,0 +1,42 @@
> > +// PR c++/92268
> > +// { dg-do compile { target c++20 } }
> > +
> > +template<typename T, typename U>
> > +  struct common_reference { };
> > +
> > +template<typename T, typename U>
> > +  using common_reference_t = typename common_reference<T, U>::type;
> > +
> > +template<typename T, typename U> concept foo = true;
> > +template<typename T, typename U> concept bar = true;
> > +template<typename T, typename U> concept baz = true;
> > +
> > +template<typename T, typename U>
> > +  concept common_reference_with
> > +    =  foo<common_reference_t<T, U>, common_reference_t<U, T>>
> > +    && bar<common_reference_t<T, U>, common_reference_t<U, T>>
> > +    && baz<common_reference_t<T, U>, common_reference_t<U, T>>;
> > +
> > +template<typename T>
> > +  using iter_reference_t = decltype(((T*)0)->f());
> > +
> > +template<typename I>
> > +  concept forward_iterator
> > +    = common_reference_with<iter_reference_t<I>&&, typename
> > I::value_type&>;
> > +
> > +struct test_range
> > +{
> > +  struct iterator
> > +  {
> > +    using value_type = int;
> > +
> > +    char f() const;
> > +  };
> > +
> > +  iterator begin();
> > +};
> > +
> > +template<typename T>
> > +concept F = requires (T& t) { { t.begin() } -> forward_iterator; };
> > +
> > +static_assert( !F<test_range> );
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req1.C
> > b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req1.C
> > index d21a49be14e..84c9ae9d6da 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/concepts-return-req1.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts-return-req1.C
> > @@ -15,5 +15,5 @@ int f(...);
> >     int main()
> >   {
> > -  f<int>();                        // { dg-error "ambiguous" }
> > +  f<int>();
> >   }
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts7.C
> > b/gcc/testsuite/g++.dg/cpp2a/concepts7.C
> > new file mode 100644
> > index 00000000000..780cfc859ce
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts7.C
> > @@ -0,0 +1,9 @@
> > +// { dg-do compile { target c++20 } }
> > +
> > +template<class R> concept C = true;
> > +
> > +template<class T> int f1(T) requires (!!C<typename T::type>); // { dg-error
> > "'int'" }
> > +template<class T> int f2(T) requires C<typename T::type>;
> > +
> > +int i1 = f1(0); // { dg-error "no match" }
> > +int i2 = f2(0); // { dg-error "no match|'int'" }
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts8.C
> > b/gcc/testsuite/g++.dg/cpp2a/concepts8.C
> > new file mode 100644
> > index 00000000000..29d3e24e323
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp2a/concepts8.C
> > @@ -0,0 +1,25 @@
> > +// { dg-do compile { target c++20 } }
> > +
> > +template<class... T> concept C = true;
> > +
> > +template<class T> struct W { using type = T::type; };
> > +
> > +struct S { using type = int; };
> > +
> > +template<class T, class U=T> int f1() requires (C<typename T::type>
> > +                                           && C<typename W<U>::type>);
> > +
> > +int i1 = f1<int>(); // { dg-error "no match|'int'" }
> > +int i2 = f1<S>();
> > +
> > +template<class T, class U=T> int f2() requires (C<typename U::type>
> > +                                           && C<typename W<T>::type>);
> > +
> > +int i3 = f2<int>(); // { dg-error "no match|'int'" }
> > +int i4 = f2<S>();
> > +int i5 = f2<S, int>(); // { dg-error "no match|'int" }
> > +
> > +template<class T> int f3() requires C<typename T::type, typename
> > W<T>::type>;
> > +
> > +int i6 = f3<int>(); // { dg-error "no match|'int'" }
> > +int i7 = f3<S>();
> > 
> 
> 

Reply via email to