Bug 94799 - [8/9/10 Regression] Calling a member template function fails
Summary: [8/9/10 Regression] Calling a member template function fails
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 9.3.0
: P2 normal
Target Milestone: 8.5
Assignee: Marek Polacek
URL:
Keywords: patch, rejects-valid
: 97564 (view as bug list)
Depends on:
Blocks:
 
Reported: 2020-04-27 13:57 UTC by Owen Smith
Modified: 2021-12-08 17:27 UTC (History)
8 users (show)

See Also:
Host:
Target:
Build:
Known to work: 6.4.0
Known to fail:
Last reconfirmed: 2020-04-27 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Owen Smith 2020-04-27 13:57:51 UTC
From gcc version 7.2 and upwards this c++ code, using a member template function look-up, fails to compile:

template <typename T>
struct A {
    int a() {
        return 42;
    }
};

template <typename T>
struct B {
    int b(A<T> *p) {
        return p->template A<T>::a();
    }
};

int main() {
    A<int> a;
    B<int> b;
    return b.b(&a);
}

On gcc version 9.3.0 (Gentoo 9.3.0 p2), this output is generated:

main.cc: In member function 'int B<T>::b(A<T>*)':
main.cc:11:32: error: expected ';' before '::' token
   11 |         return p->template A<T>::a();
      |                                ^~
      |                                ;
main.cc:11:34: error: '::a' has not been declared
   11 |         return p->template A<T>::a();
      |                                  ^
main.cc: In instantiation of 'int B<T>::b(A<T>*) [with T = int]':
main.cc:18:18:   required from here
main.cc:11:28: error: 'A' is not a member template function
   11 |         return p->template A<T>::a();
      |                ~~~~~~~~~~~~^~~~

This code successfully compiles on new versions of clang, icc and msvc as well as gcc versions before 7.2.
Comment 1 Marek Polacek 2020-04-27 15:53:14 UTC
Started with

commit 1e5f79b61994f8ffccad62a58031f5937aa16ae3
Author: Jason Merrill <jason@redhat.com>
Date:   Wed Jun 28 15:41:36 2017 -0400

    PR c++/54769 - wrong lookup of dependent template-name.
    
            * parser.c (cp_parser_template_name): Handle dependent object type.
            (cp_parser_nested_name_specifier_opt): Make template_keyword_p a
            parameter.
            (cp_parser_id_expression): Pass it.
            (cp_parser_diagnose_invalid_type_name): Handle TEMPLATE_ID_EXPR.
    
    From-SVN: r249752
Comment 3 Marek Polacek 2020-04-27 19:24:18 UTC
Here we have "p->template A<T>::a()" but cp_parser_expression only parses the "p->template A<T>" part, so we complain that a ; isn't following.

It's because cp_parser_template_name now considers the object scope:

16957       if (template_keyword_p)
16958         {
16959           tree scope = (parser->scope ? parser->scope
16960                         : parser->context->object_type);
16961           if (scope && TYPE_P (scope)
16962               && (!CLASS_TYPE_P (scope) 
16963                   || (check_dependency_p && dependent_type_p (scope))))
16964             {
16965               /* We're optimizing away the call to cp_parser_lookup_name, but
16966                  we still need to do this.  */
16967               parser->context->object_type = NULL_TREE;
16968               return identifier;
16969             }
16970         }

which here is unknown_type_node, signalling a type-dependent object:

 7756   if (dependent_p)
 7757     /* Tell cp_parser_lookup_name that there was an object, even though it's
 7758        type-dependent.  */
 7759     parser->context->object_type = unknown_type_node;

so now cp_parser_template_name returns identifier 'A', cp_parser_class_name creates a TEMPLATE_ID_EXPR A<T>, but then

23735       decl = make_typename_type (scope, decl, tag_type, tf_error);

fails because scope is NULL -- we've used the object scope instead.  We probably have to look that up first.
Comment 4 Marek Polacek 2020-04-28 00:51:26 UTC
Another test, where the name after . isn't an injected-class-name:

template<typename T> struct B {
  void foo ();
  int i;
};

template<typename T>
struct D : public B<T> { };

template<typename T>
void fn (D<T> d)
{
  d.template B<T>::foo ();
  d.template B<T>::i = 42;
}
Comment 5 Marek Polacek 2020-04-28 23:43:10 UTC
Finally I have something that seems to work...
Comment 7 GCC Commits 2020-05-05 14:19:50 UTC
The master branch has been updated by Marek Polacek <mpolacek@gcc.gnu.org>:

https://gcc.gnu.org/g:ef3479afc5ab415f00a53fc6f6a990df7f6a0747

commit r11-86-gef3479afc5ab415f00a53fc6f6a990df7f6a0747
Author: Marek Polacek <polacek@redhat.com>
Date:   Tue Apr 28 22:30:44 2020 -0400

    c++: Member template function lookup failure [PR94799]
    
    Whew, this took a while.  We fail to parse "p->template A<T>::a()"
    (where p is of type A<T> *) because since r249752 we treat the RHS of the ->
    as dependent and avoid a lookup in the enclosing context: since that rev
    cp_parser_template_name checks parser->context->object_type too, which
    here is unknown_type_node, signalling a type-dependent object:
    
     7756   if (dependent_p)
     7757     /* Tell cp_parser_lookup_name that there was an object, even though it's
     7758        type-dependent.  */
     7759     parser->context->object_type = unknown_type_node;
    
    with which cp_parser_template_name returns identifier 'A', cp_parser_class_name
    then creates a TEMPLATE_ID_EXPR A<T>, but then
    
    23735       decl = make_typename_type (scope, decl, tag_type, tf_error);
    
    in cp_parser_class_name fails because scope is NULL.  Then we return
    error_mark_node and parse errors ensue.
    
    I've tried various approaches, e.g. keeping TEMPLATE_ID_EXPR around
    instead of calling make_typename_type, which didn't work, whereupon I
    realized that since we don't want to perform name lookup if we've seen
    the template keyword and the scope is dependent, we can adjust
    parser->context->object_type and use the type of the object expression
    as the scope, even if it's type-dependent.  This should be in line with
    [basic.lookup.classref]p4.  If the postfix expression doesn't have a type,
    use typeof to carry its type.  This typeof will be processed in
    tsubst/TYPENAME_TYPE.
    
            PR c++/94799
            * parser.c (cp_parser_postfix_dot_deref_expression): If we have
            a type-dependent object of class type, stash it to
            parser->context->object_type.  If the postfix expression doesn't have
            a type, use typeof.
            (cp_parser_class_name): Consider object scope too.
            (cp_parser_lookup_name): Remove code dealing with the case when
            object_type is unknown_type_node.
    
            * g++.dg/lookup/this1.C: Adjust dg-error.
            * g++.dg/template/lookup12.C: New test.
            * g++.dg/template/lookup13.C: New test.
            * g++.dg/template/lookup14.C: New test.
            * g++.dg/template/lookup15.C: New test.
Comment 8 Marek Polacek 2020-05-05 14:21:37 UTC
Fixed on trunk so far.  I plan to backport it to 10.2, maybe 9 too.
Comment 9 Volker Reichelt 2020-05-15 09:52:30 UTC
Hi Marek,

your fix unfortunately breaks the following valid code (reduced from pybind11):

=======================================================
template <typename> struct A
{
  typedef int type;
  operator int();
};

template <typename T> using B = A<T>;

template <typename T> typename B<T>::type foo(B<T> b)
{
  return b.operator typename B<T>::type();
}

void bar()
{
  foo(A<int>());
}
=======================================================

bug.cc: In instantiation of 'typename B<T>::type foo(B<T>) [with T = int; typename B<T>::type = int; B<T> = A<int>; B<T> = A<int>]':
bug.cc:16:15:   required from here
bug.cc:11:36: error: no type named 'B' in 'struct A<int>'
   11 |   return b.operator typename B<T>::type();
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
Comment 10 Marek Polacek 2020-05-15 12:43:57 UTC
I see :(.  I'll take a look, thanks for noticing.
Comment 11 Volker Reichelt 2020-08-17 10:10:27 UTC
Hi Marek, any news on this one? It's three months now...
Or should I file a new bug for the regression on trunk?
Comment 12 Marek Polacek 2020-08-17 17:32:25 UTC
(In reply to Volker Reichelt from comment #11)
> Hi Marek, any news on this one? It's three months now...
> Or should I file a new bug for the regression on trunk?

No news yet.  I've been largely away from upstream GCC for the past two months, but I should have more time now.  I hope this will be fixed within the next couple of weeks.  Sorry it's taking so long.
Comment 13 Marek Polacek 2020-10-19 21:03:07 UTC
The fix may be as easy as this:

--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -23812,8 +23812,8 @@ cp_parser_class_name (cp_parser *parser,
 
   /* Any name names a type if we're following the `typename' keyword
      in a qualified name where the enclosing scope is type-dependent.  */
-  typename_p = (typename_keyword_p && scope && TYPE_P (scope)
-       && dependent_type_p (scope));
+  typename_p = (typename_keyword_p && parser->scope && TYPE_P (parser->scope)
+       && dependent_type_p (parser->scope));
   /* Handle the common case (an identifier, but not a template-id)
      efficiently.  */
   if (token->type == CPP_NAME
Comment 14 Marek Polacek 2020-10-20 00:53:42 UTC
Patch posted: https://gcc.gnu.org/pipermail/gcc-patches/2020-October/556517.html
Comment 15 Marek Polacek 2020-10-24 18:25:50 UTC
*** Bug 97564 has been marked as a duplicate of this bug. ***
Comment 16 GCC Commits 2020-10-28 19:26:43 UTC
The master branch has been updated by Marek Polacek <mpolacek@gcc.gnu.org>:

https://gcc.gnu.org/g:323dd4255203479d8c456b85513db4f8e0041d04

commit r11-4499-g323dd4255203479d8c456b85513db4f8e0041d04
Author: Marek Polacek <polacek@redhat.com>
Date:   Mon Oct 19 18:13:42 2020 -0400

    c++: Member template function lookup failure [PR94799]
    
    My earlier patch for this PR, r11-86, broke pybind11.  That patch
    changed cp_parser_class_name to also consider the object expression
    scope (parser->context->object_type) to fix parsing of
    
      p->template A<T>::foo(); // consider p's scope too
    
    Here we reject
    
      b.operator typename B<T>::type();
    
    because 'typename_p' in cp_parser_class_name uses 'scope', which means
    that 'typename_p' will be true for the example above.  Then we create
    a TYPENAME_TYPE via make_typename_type, which fails when tsubsting it;
    the code basically created 'typename B::B' and then we complain that there
    is no member named 'B' in 'A<int>'.  So, when deciding if we should
    create a TYPENAME_TYPE, don't consider the object_type scope, like we
    did pre-r11-86.
    
    gcc/cp/ChangeLog:
    
            PR c++/94799
            * parser.c (cp_parser_class_name): Use parser->scope when
            setting typename_p.
    
    gcc/testsuite/ChangeLog:
    
            PR c++/94799
            * g++.dg/template/lookup16.C: New test.
Comment 17 Marek Polacek 2020-10-28 19:27:42 UTC
Should be fixed now.  Sorry it took so long.