On Fri, 22 Jan 2021, Jason Merrill wrote:

> On 1/22/21 1:58 PM, Patrick Palka wrote:
> > On Fri, 22 Jan 2021, Jason Merrill wrote:
> > 
> > > On 1/22/21 12:59 PM, Patrick Palka wrote:
> > > > On Fri, 22 Jan 2021, Patrick Palka wrote:
> > > > 
> > > > > On Fri, 22 Jan 2021, Patrick Palka wrote:
> > > > > 
> > > > > > On Thu, 21 Jan 2021, Jason Merrill wrote:
> > > > > > 
> > > > > > > On 1/21/21 11:22 AM, Patrick Palka wrote:
> > > > > > > > Here at parse time finish_qualified_id_expr adds an implicit
> > > > > > > > 'this->' to
> > > > > > > > the expression tmp::integral<T> (because it's type-dependent,
> > > > > > > > and
> > > > > > > > also
> > > > > > > > current_class_ptr is set) within the trailing return type, and
> > > > > > > > later
> > > > > > > > during substitution we can't resolve the 'this' since
> > > > > > > > tsubst_function_type does inject_this_parm only for non-static
> > > > > > > > member
> > > > > > > > functions which tmp::func is not.
> > > > > > > > 
> > > > > > > > It seems the root of the problem is that we set
> > > > > > > > current_class_ptr
> > > > > > > > when
> > > > > > > > parsing the signature of a static member function.  Since
> > > > > > > > explicit
> > > > > > > > uses
> > > > > > > > of 'this' are already not allowed in this context, we probably
> > > > > > > > shouldn't
> > > > > > > > be doing inject_this_parm either.
> > > > > > > 
> > > > > > > Hmm, 'this' is defined in a static member function, it's just
> > > > > > > ill-formed to
> > > > > > > use it:
> > > > > > > 
> > > > > > > 7.5.2/2: "... [this] shall not appear within the declaration of a
> > > > > > > static
> > > > > > > member function (although its type and value category are defined
> > > > > > > within a
> > > > > > > static member function as they are within a non-static member
> > > > > > > function).
> > > > > > > [Note: This is because declaration matching does not occur until
> > > > > > > the
> > > > > > > complete
> > > > > > > declarator is known. — end note]"
> > > > > > 
> > > > > > Ah, I see.
> > > > > > 
> > > > > > > 
> > > > > > > Perhaps maybe_dummy_object needs to be smarter about recognizing
> > > > > > > static
> > > > > > > context.  Or perhaps there's no way to tell the difference between
> > > > > > > the
> > > > > > > specified behavior above and the behavior with your patch, but:
> > > > > > > 
> > > > > > > The note suggests that we need to test the case of an out-of-class
> > > > > > > definition
> > > > > > > of a static member function (template); we must inject_this_parm
> > > > > > > when
> > > > > > > parsing
> > > > > > > such a declaration, since we don't know it's static until we match
> > > > > > > it
> > > > > > > to the
> > > > > > > declaration in the class.  I'm guessing that this would lead to
> > > > > > > the
> > > > > > > same
> > > > > > > problem.
> > > > > > 
> > > > > > Good point, we do get a signature mismatch error in this case, due
> > > > > > to
> > > > > > 'this->' appearing in the trailing return type out-of-class
> > > > > > declaration
> > > > > > but not in that of the in-class declaration, so the trailing return
> > > > > > types don't match up.  In light of this case, it doesn't seem
> > > > > > possible
> > > > > > for maybe_dummy_object to distinguish between a static and
> > > > > > non-static
> > > > > > context when parsing the trailing return type of the declaration.
> > > > > > 
> > > > > > So perhaps we should mirror at substitution time what we do at parse
> > > > > > time, and inject 'this' also when substituting into the function
> > > > > > type of
> > > > > > a static member function?  That way, we'll resolve the use of
> > > > > > 'this->'
> > > > > > and then discard it the RHS of -> is a static member function, or
> > > > > > complain that 'this' is not a constant expression if the RHS is a
> > > > > > non-static member function.
> > > > > > 
> > > > > > The following patch performs that, and seems to pass light testing.
> > > > > > Full testing in progress.  Revised commit message is still a WIP.
> > > > > > How does this approach look?
> > > > 
> > > > Sorry for the spam, but I'd also like to propose a more conservative and
> > > > targeted approach that basically undoes part of the r9-5972 patch which
> > > > caused the regression.
> > > > 
> > > > According to the email thread at
> > > > https://gcc.gnu.org/pipermail/gcc-patches/2019-February/516421.html
> > > > the hunk from r9-5972
> > > > 
> > > > --- a/gcc/cp/semantics.c
> > > > +++ b/gcc/cp/semantics.c
> > > > @@ -2096,7 +2096,8 @@ finish_qualified_id_expr (tree qualifying_class,
> > > >        {
> > > >          /* See if any of the functions are non-static members.  */
> > > >          /* If so, the expression may be relative to 'this'.  */
> > > > -      if (!shared_member_p (expr)
> > > > +      if ((type_dependent_expression_p (expr)
> > > > +          || !shared_member_p (expr))
> > > >             && current_class_ptr
> > > >             && DERIVED_FROM_P (qualifying_class,
> > > >                                current_nonlambda_class_type ()))
> > > > 
> > > > was added to avoid calling shared_member_p here when the overload set
> > > > contains a dependent USING_DECL.  But according to
> > > > https://gcc.gnu.org/pipermail/gcc-patches/2018-December/513237.html if
> > > > an overload set contains a dependent USING_DECL, then it'll be the first
> > > > in the overload set.  So we could partially revert the r9-5972 patch and
> > > > do:
> > > > 
> > > > diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
> > > > index c6b4c70dc0f..6b0d68ae4bf 100644
> > > > --- a/gcc/cp/semantics.c
> > > > +++ b/gcc/cp/semantics.c
> > > > @@ -2214,7 +2214,7 @@ finish_qualified_id_expr (tree qualifying_class,
> > > >        {
> > > >          /* See if any of the functions are non-static members.  */
> > > >          /* If so, the expression may be relative to 'this'.  */
> > > > -      if ((type_dependent_expression_p (expr)
> > > > +      if ((TREE_CODE (get_first_fn (expr)) == USING_DECL
> > > >              || !shared_member_p (expr))
> > > >             && current_class_ptr
> > > >             && DERIVED_FROM_P (qualifying_class,
> > > > 
> > > > The above pases 'make check-c++', and allows us to compile pr97399a.C
> > > > successfully, but we'd still ICE on the invalid pr97399b.C.
> > > 
> > > What if we flipped the sense of the type_dependent_expression_p check, so
> > > we
> > > never add this-> if the function operand is dependent?
> > 
> > Looks like this doesn't survive 'make check-c++', we crash on the
> > testcase g++.dg/cpp1y/lambda-generic-this1a.C:
> > 
> > gcc/testsuite/g++.dg/cpp1y/lambda-generic-this1a.C: In instantiation of
> > ‘MyType::crash()::<lambda(auto:1)> [with auto:1 = A]’:
> > gcc/testsuite/g++.dg/cpp1y/lambda-generic-this1a.C:12:10:   required from
> > here
> > gcc/testsuite/g++.dg/cpp1y/lambda-generic-this1a.C:9:28: internal compiler
> > error: trying to capture ‘this’ in instantiation of generic lambda
> >      9 |       MyType::make_crash<i>(); // Line (1)
> >        |       ~~~~~~~~~~~~~~~~~~~~~^~
> > 0x9bab23 add_capture(tree_node*, tree_node*, tree_node*, bool, bool)
> >          /home/patrick/gcc-master/gcc/cp/lambda.c:633
> > 0x9bac75 add_default_capture(tree_node*, tree_node*, tree_node*)
> >          /home/patrick/gcc-master/gcc/cp/lambda.c:693
> > 0x9bb3a1 lambda_expr_this_capture(tree_node*, int)
> >          /home/patrick/gcc-master/gcc/cp/lambda.c:811
> > 0x9bb4a0 maybe_resolve_dummy(tree_node*, bool)
> >          /home/patrick/gcc-master/gcc/cp/lambda.c:894
> > 0x8de510 build_new_method_call_1
> >          /home/patrick/gcc-master/gcc/cp/call.c:10503
> > 0x8df04f build_new_method_call(tree_node*, tree_node*, vec<tree_node*,
> > va_gc, vl_embed>**, tree_node*, int, tree_node**, int)
> > 
> > Presumably because we no longer add 'this->' to the dependent call
> > MyType::make_crash<i> inside the generic lambda, so we don't capture it
> > when we should have?
> 
> Ah, yes, we still need to add this-> in a generic lambda, or at least capture
> 'this'.
> 
> I think I was wrong in my assertion around Alex's patch that shared_member_p
> should abort on a dependent USING_DECL; it now seems appropriate for it to
> return false if we don't know, we just need to adjust the comment to say that.
> 
> And then drop the type-dependent check.

Sounds good, thanks a lot.  Here's what I'm testing.

Note that with this approach we still ICE on the invalid testcase
pr97399b.C (starting with r8-2724, it seems).  I'll create a separate PR
to track this issue.

-- >8 --

Subject: [PATCH] c++: 'this' injection and static member functions [PR97399]

In the testcase pr97399.C below, finish_qualified_id_expr at parse time
adds an implicit 'this->' to the expression tmp::integral<T> (because
it's type-dependent, and also current_class_ptr is set at this point)
within the trailing return type.  Later when substituting into the
trailing return type we crash due to the unresolved 'this', since
tsubst_function_type does inject_this_parm only for non-static member
functions, which tmp::func is not.

This patch fixes this issue by removing the type-dependence check
in finish_qualified_id_expr added by r9-5972, and in its place relaxing
shared_member_p to handle dependent USING_DECLs:

> I think I was wrong in my assertion around Alex's patch that
> shared_member_p should abort on a dependent USING_DECL; it now seems
> appropriate for it to return false if we don't know, we just need to
> adjust the comment to say that.

Moreover, for friend functions, we shouldn't be setting
current_class_ptr at all when parsing its signature, so this patch
suppresses inject_this_parm for friends.

Finally, the change to cp_parser_init_declarator below is needed so that
we properly communicate static-ness to cp_parser_direct_declarator when
parsing a member function template.  This lets us reject the explicit
use of 'this' in the testcase this2.C below.

Bootstrap and regtest re-running on x86_64-pc-linux-gnu, does this look
OK for trunk if successful?

gcc/cp/ChangeLog:

        PR c++/97399
        * cp-tree.h (shared_member_p): Adjust declaration.
        * parser.c (cp_parser_init_declarator): If the storage class
        specifier is sc_static, pass true for static_p to
        cp_parser_declarator.
        (cp_parser_direct_declarator): Don't do inject_this_parm when
        the function is specified as a friend.
        * search.c (shared_member_p): Change return type to bool and
        adjust return statements accordingly.  Return false for a
        dependent USING_DECL.
        * pt.c (tsubst_function_type): Also inject 'this' when
        substituting into the function type of a static member
        function.

gcc/testsuite/ChangeLog:

        PR c++/88548
        PR c++/97399
        * g++.dg/cpp0x/this2.C: New test.
        * g++.dg/template/pr97399.C: New test.
---
 gcc/cp/cp-tree.h                        |  2 +-
 gcc/cp/parser.c                         |  5 +++--
 gcc/cp/search.c                         | 20 +++++++++++---------
 gcc/cp/semantics.c                      |  3 +--
 gcc/testsuite/g++.dg/cpp0x/this2.C      |  8 ++++++++
 gcc/testsuite/g++.dg/template/pr97399.C | 23 +++++++++++++++++++++++
 6 files changed, 47 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp0x/this2.C
 create mode 100644 gcc/testsuite/g++.dg/template/pr97399.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index eb20de433b7..65b4cc7f5c8 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7312,7 +7312,7 @@ extern tree adjust_result_of_qualified_name_lookup
                                                (tree, tree, tree);
 extern tree copied_binfo                       (tree, tree);
 extern tree original_binfo                     (tree, tree);
-extern int shared_member_p                     (tree);
+extern bool shared_member_p                    (tree);
 extern bool any_dependent_bases_p (tree = current_nonlambda_class_type ());
 extern bool maybe_check_overriding_exception_spec (tree, tree);
 
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 48437f23175..3a78ea49634 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -21413,6 +21413,7 @@ cp_parser_init_declarator (cp_parser* parser,
   bool is_non_constant_init;
   int ctor_dtor_or_conv_p;
   bool friend_p = cp_parser_friend_p (decl_specifiers);
+  bool static_p = decl_specifiers->storage_class == sc_static;
   tree pushed_scope = NULL_TREE;
   bool range_for_decl_p = false;
   bool saved_default_arg_ok_p = parser->default_arg_ok_p;
@@ -21446,7 +21447,7 @@ cp_parser_init_declarator (cp_parser* parser,
     = cp_parser_declarator (parser, CP_PARSER_DECLARATOR_NAMED,
                            flags, &ctor_dtor_or_conv_p,
                            /*parenthesized_p=*/NULL,
-                           member_p, friend_p, /*static_p=*/false);
+                           member_p, friend_p, static_p);
   /* Gather up the deferred checks.  */
   stop_deferring_access_checks ();
 
@@ -22122,7 +22123,7 @@ cp_parser_direct_declarator (cp_parser* parser,
 
                  tree save_ccp = current_class_ptr;
                  tree save_ccr = current_class_ref;
-                 if (memfn)
+                 if (memfn && !friend_p)
                    /* DR 1207: 'this' is in scope after the cv-quals.  */
                    inject_this_parameter (current_class_type, cv_quals);
 
diff --git a/gcc/cp/search.c b/gcc/cp/search.c
index dd3773da4f7..9c60c80da44 100644
--- a/gcc/cp/search.c
+++ b/gcc/cp/search.c
@@ -910,7 +910,7 @@ struct lookup_field_info {
   const char *errstr;
 };
 
-/* Nonzero for a class member means that it is shared between all objects
+/* True for a class member means that it is shared between all objects
    of that class.
 
    [class.member.lookup]:If the resulting set of declarations are not all
@@ -920,25 +920,27 @@ struct lookup_field_info {
 
    This function checks that T contains no non-static members.  */
 
-int
+bool
 shared_member_p (tree t)
 {
-  if (VAR_P (t) || TREE_CODE (t) == TYPE_DECL \
+  if (VAR_P (t) || TREE_CODE (t) == TYPE_DECL
       || TREE_CODE (t) == CONST_DECL)
-    return 1;
+    return true;
   if (is_overloaded_fn (t))
     {
       for (ovl_iterator iter (get_fns (t)); iter; ++iter)
        {
          tree decl = strip_using_decl (*iter);
-         /* We don't expect or support dependent decls.  */
-         gcc_assert (TREE_CODE (decl) != USING_DECL);
+         if (TREE_CODE (decl) == USING_DECL)
+           /* We don't know what this dependent using declaration
+              will resolve to; conservatively return false.  */
+           return false;
          if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
-           return 0;
+           return false;
        }
-      return 1;
+      return true;
     }
-  return 0;
+  return false;
 }
 
 /* Routine to see if the sub-object denoted by the binfo PARENT can be
diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index 067095276af..51841dcf24a 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -2214,8 +2214,7 @@ finish_qualified_id_expr (tree qualifying_class,
     {
       /* See if any of the functions are non-static members.  */
       /* If so, the expression may be relative to 'this'.  */
-      if ((type_dependent_expression_p (expr)
-          || !shared_member_p (expr))
+      if (!shared_member_p (expr)
          && current_class_ptr
          && DERIVED_FROM_P (qualifying_class,
                             current_nonlambda_class_type ()))
diff --git a/gcc/testsuite/g++.dg/cpp0x/this2.C 
b/gcc/testsuite/g++.dg/cpp0x/this2.C
new file mode 100644
index 00000000000..3781bc5efec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/this2.C
@@ -0,0 +1,8 @@
+// PR c++/88548
+// { dg-do compile { target c++11 } }
+
+struct S {
+  int a;
+  template <class> static auto m1 ()
+    -> decltype(this->a) { return 0; }; // { dg-error "'this'" }
+};
diff --git a/gcc/testsuite/g++.dg/template/pr97399.C 
b/gcc/testsuite/g++.dg/template/pr97399.C
new file mode 100644
index 00000000000..4bb818908fd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/pr97399.C
@@ -0,0 +1,23 @@
+// PR c++/97399
+// { dg-do compile { target c++11 } }
+
+template <bool> struct enable_if_t {};
+
+struct tmp {
+  template <class> static constexpr bool is_integral();
+  template <class T> static auto f()
+    -> enable_if_t<tmp::is_integral<T>()>;
+  template <class T> friend auto g(tmp, T)
+    -> enable_if_t<!tmp::is_integral<T>()>;
+};
+
+template <class> constexpr bool tmp::is_integral() { return true; }
+
+template <class T> auto tmp::f()
+  -> enable_if_t<tmp::is_integral<T>()> { return {}; }
+
+int main()
+{
+  tmp::f<int>();
+  g(tmp{}, 0);
+}
-- 
2.30.0.155.g66e871b664


> 
> > To be clear, this is with
> > 
> > --- a/gcc/cp/semantics.c
> > +++ b/gcc/cp/semantics.c
> > @@ -2214,8 +2214,8 @@ finish_qualified_id_expr (tree qualifying_class,
> >       {
> >         /* See if any of the functions are non-static members.  */
> >         /* If so, the expression may be relative to 'this'.  */
> > -      if ((type_dependent_expression_p (expr)
> > -          || !shared_member_p (expr))
> > +      if (!type_dependent_expression_p (expr)
> > +         && !shared_member_p (expr)
> >            && current_class_ptr
> >            && DERIVED_FROM_P (qualifying_class,
> >                               current_nonlambda_class_type ()))
> > 
> > > 
> > > > > > -- >8 --
> > > > > > 
> > > > > > Subject: [PATCH] c++: 'this' injection and static member functions
> > > > > > [PR97399]
> > > > > > 
> > > > > > gcc/cp/ChangeLog:
> > > > > > 
> > > > > >     PR c++/97399
> > > > > >     * parser.c (cp_parser_init_declarator): If the storage class
> > > > > >     specifier is sc_static, pass true for static_p to
> > > > > >     cp_parser_declarator.
> > > 
> > > I don't see this change in the patch; it seems like a good cleanup even if
> > > it
> > > isn't needed for this bug.
> > 
> > Sounds good.  I'll add it back in (and the testcase cpp0x/this2.C) in the
> > next
> > iteration of the patch.
> > 
> > > 
> > > > > >     (cp_parser_direct_declarator): Don't do inject_this_parm when
> > > > > >     the function is specified as a friend.
> > > > > >     * pt.c (tsubst_function_type): Also inject 'this' when
> > > > > >     substituting into the function type of a static member
> > > > > >     function.
> > > > > > 
> > > > > > gcc/testsuite/ChangeLog:
> > > > > > 
> > > > > >     PR c++/88548
> > > > > >     PR c++/97399
> > > > > >     * g++.dg/cpp0x/this2.C: New test.
> > > > > >     * g++.dg/gomp/this-1.C: Adjust expected error for use of
> > > > > > 'this'
> > > > > >     in signature of static member function.
> > > > > >     * g++.dg/template/pr97399a.C: New test.
> > > > > >     * g++.dg/template/pr97399b.C: New test.
> > > > > > ---
> > > > > >    gcc/cp/parser.c                          |  2 +-
> > > > > >    gcc/cp/pt.c                              | 13 +++++++++++--
> > > > > >    gcc/testsuite/g++.dg/template/pr97399a.C | 23
> > > > > > +++++++++++++++++++++++
> > > > > >    gcc/testsuite/g++.dg/template/pr97399b.C | 23
> > > > > > +++++++++++++++++++++++
> > > > > >    4 files changed, 58 insertions(+), 3 deletions(-)
> > > > > >    create mode 100644 gcc/testsuite/g++.dg/template/pr97399a.C
> > > > > >    create mode 100644 gcc/testsuite/g++.dg/template/pr97399b.C
> > > > > > 
> > > > > > diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
> > > > > > index 48437f23175..a0efa55d21e 100644
> > > > > > --- a/gcc/cp/parser.c
> > > > > > +++ b/gcc/cp/parser.c
> > > > > > @@ -22122,7 +22122,7 @@ cp_parser_direct_declarator (cp_parser*
> > > > > > parser,
> > > > > >                       tree save_ccp = current_class_ptr;
> > > > > >               tree save_ccr = current_class_ref;
> > > > > > -             if (memfn)
> > > > > > +             if (memfn && !friend_p)
> > > > > >                 /* DR 1207: 'this' is in scope after the cv-quals.
> > > > > > */
> > > > > >                 inject_this_parameter (current_class_type,
> > > > > > cv_quals);
> > > > > >    diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
> > > > > > index ad855dbde72..d4763325e25 100644
> > > > > > --- a/gcc/cp/pt.c
> > > > > > +++ b/gcc/cp/pt.c
> > > > > > @@ -15072,8 +15072,17 @@ tsubst_function_type (tree t,
> > > > > >            tree save_ccp = current_class_ptr;
> > > > > >          tree save_ccr = current_class_ref;
> > > > > > -      tree this_type = (TREE_CODE (t) == METHOD_TYPE
> > > > > > -                   ? TREE_TYPE (TREE_VALUE (arg_types)) :
> > > > > > NULL_TREE);
> > > > > > +      tree this_type = NULL_TREE;
> > > > > > +      if (TREE_CODE (t) == METHOD_TYPE)
> > > > > > +   this_type = TREE_TYPE (TREE_VALUE (arg_types));
> > > > > > +      else if (in_decl
> > > > > > +          && DECL_FUNCTION_MEMBER_P (in_decl)
> > > > > 
> > > > > Oops, this line should instead be checking DECL_STATIC_FUNCTION_P.
> > > > > Consider it changed.
> > > > > 
> > > > > > +          && t == TREE_TYPE (in_decl))
> > > > > > +   /* Also inject 'this' when substituting into the function type
> > > > > > +      of a static member function, as we may have conservatively
> > > > > > +      added 'this->' to a dependent member function call in its
> > > > > > +      trailing return type which we might need to resolve.  */
> > > > > > +   this_type = DECL_CONTEXT (in_decl);
> > > > > >          bool do_inject = this_type && CLASS_TYPE_P (this_type);
> > > > > >          if (do_inject)
> > > > > >     {
> > > > > > diff --git a/gcc/testsuite/g++.dg/template/pr97399a.C
> > > > > > b/gcc/testsuite/g++.dg/template/pr97399a.C
> > > > > > new file mode 100644
> > > > > > index 00000000000..4bb818908fd
> > > > > > --- /dev/null
> > > > > > +++ b/gcc/testsuite/g++.dg/template/pr97399a.C
> > > > > > @@ -0,0 +1,23 @@
> > > > > > +// PR c++/97399
> > > > > > +// { dg-do compile { target c++11 } }
> > > > > > +
> > > > > > +template <bool> struct enable_if_t {};
> > > > > > +
> > > > > > +struct tmp {
> > > > > > +  template <class> static constexpr bool is_integral();
> > > > > > +  template <class T> static auto f()
> > > > > > +    -> enable_if_t<tmp::is_integral<T>()>;
> > > > > > +  template <class T> friend auto g(tmp, T)
> > > > > > +    -> enable_if_t<!tmp::is_integral<T>()>;
> > > > > > +};
> > > > > > +
> > > > > > +template <class> constexpr bool tmp::is_integral() { return true; }
> > > > > > +
> > > > > > +template <class T> auto tmp::f()
> > > > > > +  -> enable_if_t<tmp::is_integral<T>()> { return {}; }
> > > > > > +
> > > > > > +int main()
> > > > > > +{
> > > > > > +  tmp::f<int>();
> > > > > > +  g(tmp{}, 0);
> > > > > > +}
> > > > > > diff --git a/gcc/testsuite/g++.dg/template/pr97399b.C
> > > > > > b/gcc/testsuite/g++.dg/template/pr97399b.C
> > > > > > new file mode 100644
> > > > > > index 00000000000..673ba6624e3
> > > > > > --- /dev/null
> > > > > > +++ b/gcc/testsuite/g++.dg/template/pr97399b.C
> > > > > > @@ -0,0 +1,23 @@
> > > > > > +// PR c++/97399
> > > > > > +// { dg-do compile { target c++11 } }
> > > > > > +
> > > > > > +template <bool> struct enable_if_t {};
> > > > > > +
> > > > > > +struct tmp {
> > > > > > +  template <class> constexpr bool is_integral(); // non-static
> > > > > > +  template <class T> static auto f()
> > > > > > +    -> enable_if_t<tmp::is_integral<T>()>; // { dg-message "in
> > > > > > template
> > > > > > argument" }
> > > > > > +  template <class T> friend auto g(tmp, T)
> > > > > > +    -> enable_if_t<!tmp::is_integral<T>()>; // { dg-error "without
> > > > > > object" }
> > > > > > +};
> > > > > > +
> > > > > > +template <class> constexpr bool tmp::is_integral() { return true; }
> > > > > > +
> > > > > > +template <class T> auto tmp::f() // { dg-error "'this' is not a
> > > > > > constant" }
> > > > > > +  -> enable_if_t<tmp::is_integral<T>()> { return {}; }
> > > > > > +
> > > > > > +int main()
> > > > > > +{
> > > > > > +  tmp::f<int>(); // { dg-error "no match" }
> > > > > > +  g(tmp{}, 0); // { dg-error "no match" }
> > > > > > +}
> > > > > > -- 
> > > > > > 2.30.0.155.g66e871b664
> > > 
> 
> 

Reply via email to