Hi Jason, Hope you are well. Thanks for the feedback.
> I like adding the configurability, but I think let's keep committing as > the default behavior. And adding the parameter to the rollback function > seems unnecessary. For the behavior argument, let's use an enum to be > clearer. > Sure, all done. The declaration of the enum could have gone in many places it seems but I put it in cp-tree.h. Hard to tell exactly where in the file it needed to go but I took an educated guess and put it in what I think is the parser.c bit. If the next token is >, we're already at the end of the template > argument list. If not, then something has gone wrong, so give an error > and skip ahead. > I guess it just seemed odd that with a name like cp_parser_skip_to_end_of_template_parameter_list, the calling function would expect that it should already be at the end of a template parameter list. Maybe if it was called cp_parser_check_reached_end_of_template_parameter_list... but like you suggested that's been refactored now anyways. Let's use cp_parser_nth_token_starts_template_argument_list_p. > Good point. Surprised that didn't trigger a failed test, but maybe there just isn't one. Without the <> they may be template names, but they aren't template-ids. > Very true, updated the comment to hopefully make more sense. I don't think << is ever treated as two <; that only applies to >>. > Good to know, I've taken that comment out. I think we don't want to return early here, we want to go through the > same <args>( check that we do for regular names. > I have changed it to do that, but could something like "operator- <" ever be intended as something other than the start of a template-id? The || case is already handled by the test at the top of the function, > right? > I thought this too originally but I don't think it is. The first check is just a performance booster, and only returns if it sees a name without a < after it. If it doesn't see a name, that's fine, because it might instead be a keyword like operator or template, so we continue. We then checked for the template and keyword operators and returned appropriately if we saw one (although, now, because of the change above, we continue if we see the operator keyword). But at this point we haven't checked what comes after the operator keyword. It could be gibberish. So at that point we ensure that we see a name and a < at that point. I don't think this check can go any earlier, since that's the earliest we can safely ensure we see both a name and a <, given the possibility of finding keywords initially. Although that whole part had to be refactored so it might be clearer now anyways. > + /* Consume the name and the "<" because > + cp_parser_skip_to_end_of_template_parameter_list requires it. */ > + cp_lexer_consume_token (parser->lexer); > + cp_lexer_consume_token (parser->lexer); > + > + /* Skip to the end. If it fails, it wasn't a template. */ > + if (!cp_parser_skip_to_end_of_template_parameter_list (parser, true)) > + return -1; How about factoring this call and the previous line into a cp_parser_skip_template_argument_list (parser) ? > - /* Are we ready, yet? If not, issue error message. */ > - if (cp_parser_require (parser, CPP_GREATER, RT_GREATER)) > - return; We could also factor this check into a separate function, leaving only the actual walking that's shared between the two callers. Yeah I think that would make things a lot clearer. I think I've done what you've suggested - I've removed the awkward call to cp_parser_require and put it (along with the call to cp_parser_skip_to_end_of_template_parameter_list) in its own new function called cp_parser_ensure_reached_end_of_template_parameter_list. I've then made another function called cp_parser_skip_entire_template_parameter_list that consumes the "<" and then skips to the end by calling the other function. I'm not sure how you'd see ';' after a class template-id, but you could > see it for a variable template. Along with many other possible tokens. > I guess any operator-or-punctuator after the > would suggest a > template-id. > At least one of us knows what they're talking about. Updated the comment. You're building a cp_expr that immediately converts back to a tree; just > pass next_token->u.value to constructor_name_p. > Oops... fixed. This should mention the possible false positive and how to silence it: > If you get actually meant to write two comparisons in t.m<N>(0), you can > make that clear by writing (t.m<N)>(0). > Done. Hmm, that's a problem. Can you avoid it by checking declarator_p? > We actually already check declarator_p in cp_parser_id_expression in that case. The reason it throws a warning is because typename14.C is intentionally malformed; in the eyes of the compiler it's written like an expression because it's missing the return type (although, even adding a return type would not make it valid). I'm not sure there's any worthwhile way around this really. You could tell DejaGnu to silence the warning, but I think it's probably more informative to just include the warning inline. Also, some more missing template keywords seemed to crop up in the regression tests, so I had to sprinkle some more template keywords in a few. I guess there was a hidden bug that was missing a few scenarios. Just out of curiosity, how pedantic would you say I should be when submitting patches to GCC etc? I regression test and bootstrap all my changes, but I'm always worrying about what might happen if I somehow forgot to stage a file in git, or attached the wrong patch file, or interpreted the .sum file wrong etc. Do patches go through another round of testing after submitting? Sometimes I wonder whether I should be applying the patch locally and then bootstrapping and regression testing it again, although that's hardly fun since that usually takes around 2-3 hours even with -j 12. Maybe I ought to think about getting a dedicated Linux PC. Patch should be attached. Kind regards, Anthony On Wed, 22 Sept 2021 at 21:04, Jason Merrill <ja...@redhat.com> wrote: > On 9/17/21 18:22, Anthony Sharp wrote: > > And also re-attaching the patch! > > > > On Fri, 17 Sept 2021 at 23:17, Anthony Sharp <anthonyshar...@gmail.com> > wrote: > >> > >> Re-adding gcc-patches@gcc.gnu.org. > >> > >> ---------- Forwarded message --------- > >> From: Anthony Sharp <anthonyshar...@gmail.com> > >> Date: Fri, 17 Sept 2021 at 23:11 > >> Subject: Re: [PATCH] c++: error message for dependent template members > [PR70417] > >> To: Jason Merrill <ja...@redhat.com> > >> > >> > >> Hi Jason! Apologies for the delay. > >> > >>> This is basically core issue 1835, http://wg21.link/cwg1835 > >> > >>> This was changed for C++23 by the paper "Declarations and where to find > >>> them", http://wg21.link/p1787 > >> > >> Interesting, I was not aware of that. I was very vaguely aware that a > >> template-id in a class member access expression could be found by > >> ordinary lookup (very bottom of here > >> https://en.cppreference.com/w/cpp/language/dependent_name), but it's > >> interesting to see it is deeper than I realised. > >> > >>> But in either case, whether create<U> is in a dependent scope depends > on > >>> how we resolve impl::, we don't need to remember further back in the > >>> expression. So your dependent_expression_p parameter seems like the > >>> wrong approach. Note that when we're looking up the name after ->, the > >>> type of the object expression is in parser->context->object_type. > >> > >> That's true. I think my thinking was that since it already got figured > >> out in cp_parser_postfix_dot_deref_expression, which is where . and -> > >> access expressions come from, I thought I might as well pass it > >> through, since it seemed to work. But looking again, you're right, > >> it's not really worth the hassle; might as well just call > >> dependent_scope_p again. > >> > >>> The cases you fixed in symbol-summary.h are indeed mistakes, but not > >>> ill-formed, so giving an error on them is wrong. For example, here is > a > >>> well-formed program that is rejected with your patch: > >> > >>> template <class T, int N> void f(T t) { t.m<N>(0); } > >>> struct A { int m; } a; > >>> int main() { f<A,42>(a); } > >> > >> I suppose there was always going to be edge-cases when doing it the > >> way I've done. But yes, it can be worked-around by making it a warning > >> instead. Interestingly Clang doesn't trip up on that example, so I > >> guess they must be examining it some other way (e.g. at instantiation > >> time) - but that approach perhaps misses out on the slight performance > >> improvement this seems to bring. > >> > >>> Now that we're writing C++, I'd prefer to avoid this kind of pattern in > >>> favor of RAII, such as saved_token_sentinel. If this is still relevant > >>> after addressing the above comments. > >> > >> Sorry, it's the junior developer in me showing! So this confused me at > >> first. After having mucked around a bit I tried using > >> saved_token_sentinel but didn't see any benefit since it doesn't > >> rollback on going out of scope, and I'll always want to rollback. I > >> can call rollback directly, but then I might as well save and restore > >> myself. So what I did was use it but also modify it slightly to > >> rollback by default on going out of scope (in my mind that makes more > >> sense, since if something goes wrong you wouldn't normally want to > >> commit anything that happened [edit 1: unless committing was part of > >> the whole sanity checking thing] [edit 2: well I guess you could also > >> argue that since this is a parser after all, we like to KEEP things > >> sometimes]). But anyways, I made this configurable; it now has three > >> modes - roll-back, commit or do nothing. Let me know if you think > >> that's not the way to go. > > I like adding the configurability, but I think let's keep committing as > the default behavior. And adding the parameter to the rollback function > seems unnecessary. For the behavior argument, let's use an enum to be > clearer. > > >>> This code doesn't handle skipping matched ()/{}/[] in the > >>> template-argument-list. You probably want to involve > >>> cp_parser_skip_to_end_of_template_parameter_list somehow. > >> > >> Good point. It required some refactoring, but I have used it. Also, > >> just putting it out there, this line from > >> cp_parser_skip_to_end_of_template_parameter_list makes zero sense to > >> me (why throw an error OR immediately return?), but I have worked > >> around it, since it seems to break without it: > >> > >>> /* Are we ready, yet? If not, issue error message. */ > >>> if (cp_parser_require (parser, CPP_GREATER, RT_GREATER)) > >>> return false; > > If the next token is >, we're already at the end of the template > argument list. If not, then something has gone wrong, so give an error > and skip ahead. > > >> Last thing - I initially made a mistake. I put something like: > >> > >> (next_token->type == CPP_NAME > >> && MAYBE_CLASS_TYPE_P (parser->scope) > >> && !constructor_name_p (cp_expr (next_token->u.value, > >> > next_token->location), > >> parser->scope)) > >> > >> Instead of: > >> > >> !(next_token->type == CPP_NAME > >> && MAYBE_CLASS_TYPE_P (parser->scope) > >> && constructor_name_p (cp_expr (next_token->u.value, > >> > next_token->location), > >> parser->scope)) > >> > >> This meant a lot of things were being excluded that weren't supposed > >> to be. Oops! Changing this opened up a whole new can of worms, so I > >> had to make some changes to the main logic, but it just a little bit > >> in the end. > >> > >> Regtested everything again and all seems fine. Bootstraps fine. Patch > >> attached. Let me know if it needs anything else. > >> > >> Thanks for the help, > >> Anthony > > > +/* Check if we are looking at what "looks" like a template-id. Of > course, > > + we can't technically know for sure whether it is a template-id > without > > + doing lookup (although, the reason we are here is because the context > > + might be dependent and so it might not be possible to do any lookup > > + yet). Return 1 if it does look like a template-id. Return -1 if > not. > > + > > + Note that this is not perfect - it will generate false positives for > > + things like a.m < n > (0), where m and n are integers, for example. > */ > > +static int > > +next_token_begins_template_id (cp_parser* parser) > > +{ > > + cp_token* next_token = cp_lexer_peek_token (parser->lexer); > > + > > + /* For performance's sake, quickly return from the most common case. > */ > > + if (next_token->type == CPP_NAME > > + && cp_lexer_peek_nth_token (parser->lexer, 2)->type != CPP_LESS) > > Let's use cp_parser_nth_token_starts_template_argument_list_p. > > > + return -1; > > + > > + /* For these purposes we must believe all "template" keywords; > identifiers > > + without <> at the end can still be template-ids, but they require > the > > + template keyword. The template keyword is the only way we can > tell those > > + kinds of ids are template-ids. */ > > Without the <> they may be template names, but they aren't template-ids. > > > + if (next_token->keyword == RID_TEMPLATE) > > + { > > + /* But at least make sure it's properly formed (e.g. see > PR19397). */ > > + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_NAME) > > + return 1; > > + > > + return -1; > > + } > > + > > + /* E.g. "::operator- <>". */ > > + if (next_token->keyword == RID_OPERATOR) > > + { > > + /* We could see "operator< <>" or "operator<< <>" ("<<" might > have been > > + treated as two "<"s). If we see "operator<<", and it > > + was treated as two "<"s, we will assume this is a template; > > + there is no (simple) way of knowing for sure. */ > > I don't think << is ever treated as two <; that only applies to >>. > > > + if (cp_lexer_peek_nth_token (parser->lexer, 3)->type == CPP_LESS) > > + return 1; > > I think we don't want to return early here, we want to go through the > same <args>( check that we do for regular names. > > > + else > > + return -1; > > + } > > + > > + /* Could be a ~ referencing the destructor of a class template. */ > > + if (next_token->type == CPP_COMPL) > > + { > > + /* It could only be a template. */ > > + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_NAME) > > + return 1; > > + > > + return -1; > > + } > > + > > + if (next_token->type == CPP_TEMPLATE_ID) > > + return 1; > > + > > + if (next_token->type != CPP_NAME > > + || cp_lexer_peek_nth_token (parser->lexer, 2)->type != CPP_LESS) > > + return -1; > > The || case is already handled by the test at the top of the function, > right? > > > + saved_token_sentinel saved_tokens (parser->lexer); > > > + /* Consume the name and the "<" because > > + cp_parser_skip_to_end_of_template_parameter_list requires it. */ > > + cp_lexer_consume_token (parser->lexer); > > + cp_lexer_consume_token (parser->lexer); > > + > > + /* Skip to the end. If it fails, it wasn't a template. */ > > + if (!cp_parser_skip_to_end_of_template_parameter_list (parser, true)) > > + return -1; > > How about factoring this call and the previous line into a > > cp_parser_skip_template_argument_list (parser) > > ? > > > - /* Are we ready, yet? If not, issue error message. */ > > - if (cp_parser_require (parser, CPP_GREATER, RT_GREATER)) > > - return; > > We could also factor this check into a separate function, leaving only > the actual walking that's shared between the two callers. > > > + cp_token* following_token = cp_lexer_peek_token (parser->lexer); > > + > > + /* If this is a function template then we would see a "(" after the > > + final ">". It could also be a class template constructor. */ > > + if (following_token->type == CPP_OPEN_PAREN > > + /* If this is a class template then we would expect to see ";" or > a > > + scope token after the final ">". */ > > I'm not sure how you'd see ';' after a class template-id, but you could > see it for a variable template. Along with many other possible tokens. > I guess any operator-or-punctuator after the > would suggest a > template-id. > > > + && constructor_name_p (cp_expr (next_token->u.value, > > + next_token->location), > > You're building a cp_expr that immediately converts back to a tree; just > pass next_token->u.value to constructor_name_p. > > > +@item -Wno-missing-template-keyword > > +@opindex Wmissing-template-keyword > > +@opindex Wno-missing-template-keyword > > + > > +The member access tokens ., -> and :: must be followed by the > @code{template} > > +keyword if the parent object is dependent and the member being named is > a > > +template. > > + > > +@smallexample > > +template <class X> > > +void DoStuff (X x) > > +@{ > > + x.template DoSomeOtherStuff<X>(); // Good. > > + x.DoMoreStuff<X>(); // Warning, x is dependent. > > +@} > > +@end smallexample > > + > > +This warning can be disabled with > @option{-Wno-missing-template-keyword}. > > This should mention the possible false positive and how to silence it: > If you get actually meant to write two comparisons in t.m<N>(0), you can > make that clear by writing (t.m<N)>(0). > > > +// { dg-warning "expected \"template\" keyword before dependent > template name" { target c++20 } .-1 } > > +// ^ bogus warning caused by the above being treated as an expression, > not a declaration. > > Hmm, that's a problem. Can you avoid it by checking declarator_p? > > Jason > >
From 5ee4c1cd7263639edab140a43f9d763030fa8f21 Mon Sep 17 00:00:00 2001 From: Anthony Sharp <anthonysharp15@gmail.com> Date: Sun, 3 Oct 2021 20:11:07 +0100 Subject: [PATCH] c++: warning for dependent template members [PR70417] Add a helpful warning message for when the user forgets to include the "template" keyword after ., -> or :: when accessing a member in a dependent context, where the member is a template. Correct some cases where the template keyword was missing when it shouldn't have been. gcc/c-family/ChangeLog: 2021-10-03 Anthony Sharp <anthonysharp15@gmail.com> * c.opt: Added -Wmissing-template-keyword. gcc/cp/ChangeLog: 2021-10-03 Anthony Sharp <anthonysharp15@gmail.com> * cp-tree.h (enum saved_token_sentinel_mode): New enum for saved_token_sentinel mode. * parser.c (struct saved_token_sentinel): Added modes to control what happens on destruction. (next_token_begins_template_id): New method to check whether we are looking at what syntactically looks like a template-id. (cp_parser_id_expression): Check whether the id looks like a template; only attempt to parse it as one if so. Give warning if missing "template" keyword. (cp_parser_unqualified_id): Pass looks_like_template through this function to cp_parser_template_id_expr to avoid repeating logic unnecessarily. (cp_parser_lambda_declarator_opt): cp_parser_skip_to_end_of_template_parameter_list becomes cp_parser_ensure_reached_end_of_template_parameter_list. (cp_parser_statement): Adjust for changes to saved_token_sentinel. (cp_parser_template_id): Pass looks_like_template to avoid repeating logic. (cp_parser_template_id_expr): As above. (cp_parser_explicit_template_declaration): cp_parser_skip_to_end_of_template_parameter_list becomes cp_parser_ensure_reached_end_of_template_parameter_list. (cp_parser_enclosed_template_argument_list): Same as above. (cp_parser_skip_entire_template_parameter_list): New function that skips an entire template parameter list. (cp_parser_ensure_reached_end_of_template_parameter_list): See below. Checks whether we have reached the end of a template parameter list. (cp_parser_skip_to_end_of_template_parameter_list): Refactored to be called from one of the above two functions. Returns true on success and false otherwise. gcc/ChangeLog: 2021-10-03 Anthony Sharp <anthonysharp15@gmail.com> * doc/invoke.texi: Documentation for Wmissing-template-keyword. gcc/testsuite/ChangeLog: 2021-10-03 Anthony Sharp <anthonysharp15@gmail.com> * g++.dg/cpp0x/decltype34.C: Add missing template keywords. * g++.dg/cpp0x/variadic-mem_fn2.C: Add warning about missing template keyword. * g++.dg/cpp2a/typename14.C: Catch new template keyword warnings. * g++.dg/parse/error11.C: Catch new (valid) errors in test case. * g++.dg/parse/template26.C: Add missing template keywords. * g++.old-deja/g++.pt/explicit81.C: Add missing template keywords. * g++.dg/template/dependent-name15.C: New test for missing template keywords. * g++.dg/cpp2a/explicit16.C: Add missing template keyword. * g++.dg/lookup/decl1.C: Add missing template keyword. --- gcc/c-family/c.opt | 4 + gcc/cp/cp-tree.h | 7 + gcc/cp/parser.c | 348 ++++++++++++++---- gcc/doc/invoke.texi | 33 ++ gcc/testsuite/g++.dg/cpp0x/decltype34.C | 4 +- gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C | 1 + gcc/testsuite/g++.dg/cpp2a/explicit16.C | 2 +- gcc/testsuite/g++.dg/cpp2a/typename14.C | 4 + gcc/testsuite/g++.dg/lookup/decl1.C | 2 +- gcc/testsuite/g++.dg/parse/error11.C | 3 +- gcc/testsuite/g++.dg/parse/template26.C | 6 +- .../g++.dg/template/dependent-name15.C | 51 +++ .../g++.old-deja/g++.pt/explicit81.C | 4 +- 13 files changed, 379 insertions(+), 90 deletions(-) create mode 100644 gcc/testsuite/g++.dg/template/dependent-name15.C diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 9c151d19870..8a2d9bf5f30 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -848,6 +848,10 @@ Wmissing-requires C++ ObjC++ Var(warn_missing_requires) Init(1) Warning Warn about likely missing requires keyword. +Wmissing-template-keyword +C++ ObjC++ Var(warn_missing_template_keyword) Init(1) Warning +Warn when the template keyword is missing after a member access token in a dependent member access expression if that member is a template. + Wmultistatement-macros C ObjC C++ ObjC++ Var(warn_multistatement_macros) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall) Warn about unsafe macros expanding to multiple statements used as a body of a clause such as if, else, while, switch, or for. diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 060d1a0a3db..21326534bc3 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -5535,6 +5535,13 @@ extern int comparing_dependent_aliases; /* In parser.c. */ +/* Determines what saved_token_sentinel does when going out of scope. */ +enum saved_token_sentinel_mode { + STS_COMMIT, + STS_ROLLBACK, + STS_DONOTHING +}; + /* Nonzero if we are parsing an unevaluated operand: an operand to sizeof, typeof, or alignof. This is a count since operands to sizeof can be nested. */ diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 7a0b6234350..524dc9effdb 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -1266,17 +1266,24 @@ cp_lexer_rollback_tokens (cp_lexer* lexer) lexer->next_token = lexer->saved_tokens.pop (); } -/* RAII wrapper around the above functions, with sanity checking. Creating - a variable saves tokens, which are committed when the variable is - destroyed unless they are explicitly rolled back by calling the rollback - member function. */ +/* RAII wrapper around the above functions, with sanity checking (the token + stream should be the same at the point of instantiation as it is at the + point of destruction). + + Creating a variable saves tokens. MODE determines what happens when the + object is destroyed. STS_COMMIT commits tokens (default), + STS_ROLLBACK rolls-back and STS_DONOTHING does nothing. Calling + rollback() will immediately roll-back tokens and set MODE to + STS_DONOTHING. */ struct saved_token_sentinel { cp_lexer *lexer; unsigned len; - bool commit; - saved_token_sentinel(cp_lexer *lexer): lexer(lexer), commit(true) + saved_token_sentinel_mode mode; + saved_token_sentinel (cp_lexer *_lexer, + saved_token_sentinel_mode _mode = STS_COMMIT) + : lexer (_lexer), mode (_mode) { len = lexer->saved_tokens.length (); cp_lexer_save_tokens (lexer); @@ -1284,12 +1291,15 @@ struct saved_token_sentinel void rollback () { cp_lexer_rollback_tokens (lexer); - commit = false; + mode = STS_DONOTHING; } - ~saved_token_sentinel() + ~saved_token_sentinel () { - if (commit) + if (mode == STS_COMMIT) cp_lexer_commit_tokens (lexer); + else if (mode == STS_ROLLBACK) + cp_lexer_rollback_tokens (lexer); + gcc_assert (lexer->saved_tokens.length () == len); } }; @@ -2128,7 +2138,7 @@ static cp_expr cp_parser_primary_expression static cp_expr cp_parser_id_expression (cp_parser *, bool, bool, bool *, bool, bool); static cp_expr cp_parser_unqualified_id - (cp_parser *, bool, bool, bool, bool); + (cp_parser *, bool, bool, bool, bool, int = 0); static tree cp_parser_nested_name_specifier_opt (cp_parser *, bool, bool, bool, bool, bool = false); static tree cp_parser_nested_name_specifier @@ -2456,9 +2466,9 @@ static tree cp_parser_template_parameter static tree cp_parser_type_parameter (cp_parser *, bool *); static tree cp_parser_template_id - (cp_parser *, bool, bool, enum tag_types, bool); + (cp_parser *, bool, bool, enum tag_types, bool, int = 0); static tree cp_parser_template_id_expr - (cp_parser *, bool, bool, bool); + (cp_parser *, bool, bool, bool, int = 0); static tree cp_parser_template_name (cp_parser *, bool, bool, bool, enum tag_types, bool *); static tree cp_parser_template_argument_list @@ -2761,7 +2771,11 @@ static void cp_parser_skip_to_end_of_block_or_statement (cp_parser *); static bool cp_parser_skip_to_closing_brace (cp_parser *); -static void cp_parser_skip_to_end_of_template_parameter_list +static bool cp_parser_skip_entire_template_parameter_list + (cp_parser *); +static void cp_parser_ensure_reached_end_of_template_parameter_list + (cp_parser *); +static bool cp_parser_skip_to_end_of_template_parameter_list (cp_parser *); static void cp_parser_skip_to_pragma_eol (cp_parser*, cp_token *); @@ -6053,6 +6067,96 @@ cp_parser_primary_expression (cp_parser *parser, /*decltype*/false, idk); } +/* Check if we are looking at what "looks" like a template-id. Of course, + we can't technically know for sure whether it is a template-id without + doing lookup (although, the reason we are here is because the context + might be dependent and so it might not be possible to do any lookup + yet). Return 1 if it does look like a template-id. Return -1 if not. + + Note that this is not perfect - it will generate false positives for + things like a.m < n > (0), where m and n are integers, for example. */ +static int +next_token_begins_template_id (cp_parser* parser) +{ + cp_token* next_token = cp_lexer_peek_token (parser->lexer); + + /* For performance's sake, quickly return from the most common case. */ + if (next_token->type == CPP_NAME + && !cp_parser_nth_token_starts_template_argument_list_p (parser, 2)) + return -1; + + /* For these purposes we must believe all "template" keywords; identifiers + without <> at the end can still be templates, but they require the + template keyword. The template keyword is the only way we can tell those + kinds of names are templates. */ + if (next_token->keyword == RID_TEMPLATE) + { + /* But at least make sure it's properly formed (e.g. see PR19397). */ + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_NAME) + return 1; + + return -1; + } + + /* Could be a ~ referencing the destructor of a class template. */ + if (next_token->type == CPP_COMPL) + { + /* It could only be a template. */ + if (cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_NAME) + return 1; + + return -1; + } + + saved_token_sentinel saved_tokens (parser->lexer, STS_ROLLBACK); + bool found_operator_keyword = false; + + /* E.g. "::operator- <>". */ + if (next_token->keyword == RID_OPERATOR) + { + /* Consume it so it doesn't get in our way. */ + cp_lexer_consume_token (parser->lexer); + next_token = cp_lexer_peek_token (parser->lexer); + found_operator_keyword = true; + } + + if (next_token->type == CPP_TEMPLATE_ID) + return 1; + + /* By now the next token should be a name/operator and the one after that + should be a "<". */ + if (!cp_parser_nth_token_starts_template_argument_list_p (parser, 2)) + return -1; + + if (!found_operator_keyword && next_token->type != CPP_NAME) + return -1; + + /* Consume the name/operator. */ + cp_lexer_consume_token (parser->lexer); + + /* Skip to the end. If it fails, it wasn't a template. */ + if (!cp_parser_skip_entire_template_parameter_list (parser)) + return -1; + + next_token = cp_lexer_peek_token (parser->lexer); + + /* If this is a function template then we would see a "(" after the + final ">". It could also be a class template constructor. */ + if (next_token->type == CPP_OPEN_PAREN + /* If this is a class template then we could see a scope token after + the final ">". */ + || next_token->type == CPP_SCOPE + /* We could see a ";" after a variable template. */ + || next_token->type == CPP_SEMICOLON + /* We could see something like + friend vect<t> (::operator- <>)( const vect<t>&, const vect<t>& ); + */ + || next_token->type == CPP_CLOSE_PAREN) + return 1; + + return -1; +} + /* Parse an id-expression. id-expression: @@ -6119,6 +6223,48 @@ cp_parser_id_expression (cp_parser *parser, template_keyword_p) != NULL_TREE); + /* At this point the next token might be the template keyword. */ + if (cp_lexer_next_token_is_keyword (parser->lexer, RID_TEMPLATE)) + template_keyword_p = true; + + /* If this doesn't look like a template-id, there is no point + trying to parse it as one. By checking first, we usually speed + compilation up, and it allows us to spot missing "template" + keywords. */ + int looks_like_template_id + = next_token_begins_template_id (parser); + + cp_token* next_token = cp_lexer_peek_token (parser->lexer); + + /* If this is a dependent member-access expression accessing a template + member without the "template" keyword, issue a helpful warning. */ + if (looks_like_template_id == 1 + && !template_keyword_p + /* . and -> access tokens. */ + && (dependent_scope_p (parser->context->object_type) + /* :: access expressions. */ + || (dependent_scope_p (parser->scope) + /* A template cannot name a constructor. But don't complain if + missing the template keyword in that case; continue on and + an error for the (ill-formed) named constructor will get + thrown later. */ + && !(next_token->type == CPP_NAME + && MAYBE_CLASS_TYPE_P (parser->scope) + && constructor_name_p (next_token->u.value, + parser->scope)) + && !declarator_p)) + /* ~ before a class template-id disallows the "template" keyword. + Probably because in the case of A::~A<>, it is certain that + A is a class template. Although a destructor cannot be + a template, you can call the destructor of a class template - + e.g. see "__node->~_Rb_tree_node<_Val>();" in stl_tree.h. */ + && next_token->type != CPP_COMPL) + { + warning_at (next_token->location, OPT_Wmissing_template_keyword, + "expected \"template\" keyword before dependent template " + "name"); + } + /* If there is a nested-name-specifier, then we are looking at the first qualified-id production. */ if (nested_name_specifier_p) @@ -6127,22 +6273,28 @@ cp_parser_id_expression (cp_parser *parser, tree saved_object_scope; tree saved_qualifying_scope; cp_expr unqualified_id; - bool is_template; - /* See if the next token is the `template' keyword. */ - if (!template_p) - template_p = &is_template; - *template_p = cp_parser_optional_template_keyword (parser); + /* We already know if it's there or not, we just need to + consume it. */ + if (template_keyword_p) + { + cp_parser_optional_template_keyword (parser); + + if (template_p) + *template_p = true; + } + /* Name lookup we do during the processing of the unqualified-id might obliterate SCOPE. */ saved_scope = parser->scope; saved_object_scope = parser->object_scope; saved_qualifying_scope = parser->qualifying_scope; /* Process the final unqualified-id. */ - unqualified_id = cp_parser_unqualified_id (parser, *template_p, + unqualified_id = cp_parser_unqualified_id (parser, template_keyword_p, check_dependency_p, declarator_p, - /*optional_p=*/false); + /*optional_p=*/false, + looks_like_template_id); /* Restore the SAVED_SCOPE for our caller. */ parser->scope = saved_scope; parser->object_scope = saved_object_scope; @@ -6154,33 +6306,23 @@ cp_parser_id_expression (cp_parser *parser, of the other qualified-id productions. */ else if (global_scope_p) { - cp_token *token; - tree id; - - /* Peek at the next token. */ - token = cp_lexer_peek_token (parser->lexer); - - /* If it's an identifier, and the next token is not a "<", then - we can avoid the template-id case. This is an optimization - for this common case. */ - if (token->type == CPP_NAME - && !cp_parser_nth_token_starts_template_argument_list_p - (parser, 2)) - return cp_parser_identifier (parser); - - cp_parser_parse_tentatively (parser); - /* Try a template-id. */ - id = cp_parser_template_id_expr (parser, - /*template_keyword_p=*/false, - /*check_dependency_p=*/true, - declarator_p); - /* If that worked, we're done. */ - if (cp_parser_parse_definitely (parser)) - return id; + if (looks_like_template_id == 1) + { + cp_parser_parse_tentatively (parser); + /* Try a template-id. */ + tree id = cp_parser_template_id_expr (parser, + /*template_keyword_p=*/false, + /*check_dependency_p=*/true, + declarator_p, + looks_like_template_id); + /* If that worked, we're done. */ + if (cp_parser_parse_definitely (parser)) + return id; + } /* Peek at the next token. (Changes in the token buffer may have invalidated the pointer obtained above.) */ - token = cp_lexer_peek_token (parser->lexer); + cp_token *token = cp_lexer_peek_token (parser->lexer); switch (token->type) { @@ -6201,7 +6343,8 @@ cp_parser_id_expression (cp_parser *parser, return cp_parser_unqualified_id (parser, template_keyword_p, /*check_dependency_p=*/true, declarator_p, - optional_p); + optional_p, + looks_like_template_id); } /* Parse an unqualified-id. @@ -6216,6 +6359,10 @@ cp_parser_id_expression (cp_parser *parser, If TEMPLATE_KEYWORD_P is TRUE, we have just seen the `template' keyword, in a construct like `A::template ...'. + If LOOKS_LIKE_TEMPLATE_ID is 1, we checked and saw that this looks + like a template-id. If it is -1, we checked and saw that it did + not look like a template-id. If 0, we have not checked. + Returns a representation of unqualified-id. For the `identifier' production, an IDENTIFIER_NODE is returned. For the `~ class-name' production a BIT_NOT_EXPR is returned; the operand of the @@ -6231,7 +6378,8 @@ cp_parser_unqualified_id (cp_parser* parser, bool template_keyword_p, bool check_dependency_p, bool declarator_p, - bool optional_p) + bool optional_p, + int looks_like_template_id) { cp_token *token; @@ -6242,18 +6390,21 @@ cp_parser_unqualified_id (cp_parser* parser, { case CPP_NAME: { - tree id; - - /* We don't know yet whether or not this will be a - template-id. */ - cp_parser_parse_tentatively (parser); - /* Try a template-id. */ - id = cp_parser_template_id_expr (parser, template_keyword_p, - check_dependency_p, - declarator_p); - /* If it worked, we're done. */ - if (cp_parser_parse_definitely (parser)) - return id; + if (looks_like_template_id != -1) + { + /* We don't know yet whether or not this will be a + template-id. */ + cp_parser_parse_tentatively (parser); + /* Try a template-id. */ + tree id = cp_parser_template_id_expr (parser, + template_keyword_p, + check_dependency_p, + declarator_p, + looks_like_template_id); + /* If it worked, we're done. */ + if (cp_parser_parse_definitely (parser)) + return id; + } /* Otherwise, it's an ordinary identifier. */ return cp_parser_identifier (parser); } @@ -6261,7 +6412,8 @@ cp_parser_unqualified_id (cp_parser* parser, case CPP_TEMPLATE_ID: return cp_parser_template_id_expr (parser, template_keyword_p, check_dependency_p, - declarator_p); + declarator_p, + looks_like_template_id); case CPP_COMPL: { @@ -11382,7 +11534,7 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr) cp_lexer_consume_token (parser->lexer); template_param_list = cp_parser_template_parameter_list (parser); - cp_parser_skip_to_end_of_template_parameter_list (parser); + cp_parser_ensure_reached_end_of_template_parameter_list (parser); /* We may have a constrained generic lambda; parse the requires-clause immediately after the template-parameter-list and combine with any @@ -12263,11 +12415,11 @@ cp_parser_statement (cp_parser* parser, tree in_statement_expr, { if (saved_tokens.lexer == lexer) { - if (saved_tokens.commit) + if (saved_tokens.mode == STS_COMMIT) cp_lexer_commit_tokens (lexer); gcc_assert (lexer->saved_tokens.length () == saved_tokens.len); saved_tokens.lexer = parser->lexer; - saved_tokens.commit = false; + saved_tokens.mode = STS_DONOTHING; saved_tokens.len = parser->lexer->saved_tokens.length (); } cp_lexer_destroy (lexer); @@ -17985,6 +18137,11 @@ cp_parser_type_parameter (cp_parser* parser, bool *is_parameter_pack) of functions, returns a TEMPLATE_ID_EXPR. If the template-name names a class, returns a TYPE_DECL for the specialization. + If LOOKS_LIKE_TEMPLATE is 1, then we have already discovered + that this looks like a template-id. If -1, we have checked and seen + that this does not look like a template-id. If it is 0, then we + did not check. + If CHECK_DEPENDENCY_P is FALSE, names are looked up in uninstantiated templates. */ @@ -17993,7 +18150,8 @@ cp_parser_template_id (cp_parser *parser, bool template_keyword_p, bool check_dependency_p, enum tag_types tag_type, - bool is_declaration) + bool is_declaration, + int looks_like_template_id) { tree templ; tree arguments; @@ -18014,10 +18172,11 @@ cp_parser_template_id (cp_parser *parser, /* Avoid performing name lookup if there is no possibility of finding a template-id. */ - if ((token->type != CPP_NAME && token->keyword != RID_OPERATOR) + if (looks_like_template_id == -1 + || (token->type != CPP_NAME && token->keyword != RID_OPERATOR) || (token->type == CPP_NAME && !cp_parser_nth_token_starts_template_argument_list_p - (parser, 2))) + (parser, 2))) { cp_parser_error (parser, "expected template-id"); return error_mark_node; @@ -18244,10 +18403,12 @@ static tree cp_parser_template_id_expr (cp_parser *parser, bool template_keyword_p, bool check_dependency_p, - bool is_declaration) + bool is_declaration, + int looks_like_template_id) { tree x = cp_parser_template_id (parser, template_keyword_p, check_dependency_p, - none_type, is_declaration); + none_type, is_declaration, + looks_like_template_id); if (TREE_CODE (x) == TEMPLATE_ID_EXPR && concept_check_p (x)) /* We didn't check the arguments in cp_parser_template_id; do that now. */ @@ -31338,7 +31499,7 @@ cp_parser_explicit_template_declaration (cp_parser* parser, bool member_p) } /* Look for the `>'. */ - cp_parser_skip_to_end_of_template_parameter_list (parser); + cp_parser_ensure_reached_end_of_template_parameter_list (parser); /* Manage template requirements */ if (flag_concepts) @@ -31857,7 +32018,7 @@ cp_parser_enclosed_template_argument_list (cp_parser* parser) } } else - cp_parser_skip_to_end_of_template_parameter_list (parser); + cp_parser_ensure_reached_end_of_template_parameter_list (parser); /* The `>' token might be a greater-than operator again now. */ parser->greater_than_is_operator_p = saved_greater_than_is_operator_p; @@ -32749,11 +32910,42 @@ cp_parser_require (cp_parser* parser, } } -/* An error message is produced if the next token is not '>'. - All further tokens are skipped until the desired token is - found or '{', '}', ';' or an unbalanced ')' or ']'. */ +/* Skip an entire parameter list from start to finish. The next token must + be the initial "<" of the parameter list. Returns true on success and + false otherwise. */ +static bool cp_parser_skip_entire_template_parameter_list (cp_parser* parser) +{ + /* Consume the "<" because cp_parser_skip_to_end_of_template_parameter_list + requires it. */ + cp_lexer_consume_token (parser->lexer); + return cp_parser_skip_to_end_of_template_parameter_list (parser); +} +/* Ensure we are at the end of a template parameter list. If we are, return. + If we are not, something has gone wrong, in which case issue an error and + skip to end of the parameter list. */ static void +cp_parser_ensure_reached_end_of_template_parameter_list (cp_parser* parser) +{ + /* Are we ready, yet? If not, issue error message. */ + if (cp_parser_require (parser, CPP_GREATER, RT_GREATER)) + return; + + cp_parser_skip_to_end_of_template_parameter_list (parser); +} + +/* You should only call this function from inside a template parameter list + (i.e. the current token should at least be the initial "<" of the + parameter list). If you are skipping the entire list, it may be better to + use cp_parser_skip_entire_template_parameter_list. + + Tokens are skipped until the final ">" is found, or if we see + '{', '}', ';', or if we find an unbalanced ')' or ']'. + + Returns true if we successfully reached the end, and false if + something unexpected happened (e.g. end of file). */ + +static bool cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser) { /* Current level of '< ... >'. */ @@ -32761,10 +32953,6 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser) /* Ignore '<' and '>' nested inside '( ... )' or '[ ... ]'. */ unsigned nesting_depth = 0; - /* Are we ready, yet? If not, issue error message. */ - if (cp_parser_require (parser, CPP_GREATER, RT_GREATER)) - return; - /* Skip tokens until the desired token is found. */ while (true) { @@ -32788,7 +32976,7 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser) spurious. Just consume the `>>' and stop; we've already produced at least one error. */ cp_lexer_consume_token (parser->lexer); - return; + return false; } /* Fall through for C++0x, so we handle the second `>' in the `>>'. */ @@ -32799,7 +32987,7 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser) { /* We've reached the token we want, consume it and stop. */ cp_lexer_consume_token (parser->lexer); - return; + return true; } break; @@ -32811,7 +32999,7 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser) case CPP_CLOSE_PAREN: case CPP_CLOSE_SQUARE: if (nesting_depth-- == 0) - return; + return false; break; case CPP_EOF: @@ -32820,7 +33008,7 @@ cp_parser_skip_to_end_of_template_parameter_list (cp_parser* parser) case CPP_OPEN_BRACE: case CPP_CLOSE_BRACE: /* The '>' was probably forgotten, don't look further. */ - return; + return false; default: break; diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 78cfc100ac2..6da066d7ff8 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -8776,6 +8776,39 @@ type @samp{T}. This warning can be disabled with @option{-Wno-missing-requires}. +@item -Wno-missing-template-keyword +@opindex Wmissing-template-keyword +@opindex Wno-missing-template-keyword + +The member access tokens ., -> and :: must be followed by the @code{template} +keyword if the parent object is dependent and the member being named is a +template. + +@smallexample +template <class X> +void DoStuff (X x) +@{ + x.template DoSomeOtherStuff<X>(); // Good. + x.DoMoreStuff<X>(); // Warning, x is dependent. +@} +@end smallexample + +In rare cases it is possible to get false positives. To silence this, wrap +the expression in parentheses. For example, the following is treated as a +template, even where m and N are integers: + +@smallexample +void NotATemplate (my_class t) +@{ + int N = 5; + + bool test = t.m < N > (0); // Treated as a template. + test = (t.m < N) > (0); // Same meaning, but not treated as a template. +@} +@end smallexample + +This warning can be disabled with @option{-Wno-missing-template-keyword}. + @item -Wno-multichar @opindex Wno-multichar @opindex Wmultichar diff --git a/gcc/testsuite/g++.dg/cpp0x/decltype34.C b/gcc/testsuite/g++.dg/cpp0x/decltype34.C index 028e50669f9..b5e7a5cba0c 100644 --- a/gcc/testsuite/g++.dg/cpp0x/decltype34.C +++ b/gcc/testsuite/g++.dg/cpp0x/decltype34.C @@ -7,13 +7,13 @@ struct impl }; template<class T, class U, - class = decltype(impl::create<T>()->impl::create<U>())> + class = decltype(impl::create<T>()->impl::template create<U>())> struct tester{}; tester<impl*, int> ti; template<class T, class U, - class = decltype(impl::create<T>()->impl::create<U>())> + class = decltype(impl::create<T>()->impl::template create<U>())> int test() { return 0; } int i = test<impl*, int>(); diff --git a/gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C b/gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C index 4a02ab22990..c343f32086d 100644 --- a/gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C +++ b/gcc/testsuite/g++.dg/cpp0x/variadic-mem_fn2.C @@ -5,5 +5,6 @@ template <class A0, class... As> struct tuple tuple<As...> tail; template <int Offset, class... More> int apply(const More&... more) { return tail.apply<1>(more...); // { dg-error "" } needs .template + // { dg-warning "keyword before dependent template name" "" { target *-*-* } .-1 } } }; diff --git a/gcc/testsuite/g++.dg/cpp2a/explicit16.C b/gcc/testsuite/g++.dg/cpp2a/explicit16.C index 9c20f6332e9..b7fd2e1ae82 100644 --- a/gcc/testsuite/g++.dg/cpp2a/explicit16.C +++ b/gcc/testsuite/g++.dg/cpp2a/explicit16.C @@ -9,7 +9,7 @@ struct Foo { template <typename T> template <typename U> -Foo<T>::operator Foo<U>() { +Foo<T>::template operator Foo<U>() { return {}; } diff --git a/gcc/testsuite/g++.dg/cpp2a/typename14.C b/gcc/testsuite/g++.dg/cpp2a/typename14.C index ba7dad8245f..8c74468bdd5 100644 --- a/gcc/testsuite/g++.dg/cpp2a/typename14.C +++ b/gcc/testsuite/g++.dg/cpp2a/typename14.C @@ -9,6 +9,8 @@ template<typename> struct A template<typename T> template<typename U> A<T>::A<U> () // { dg-error "" } +// { dg-warning "expected \"template\" keyword before dependent template name" "" { target { c++20 } } .-1 } +// ^ bogus warning caused by the above being treated as an expression, not a declaration. { } @@ -20,6 +22,8 @@ template<typename> struct B template<typename T> template<typename U> B<T>::foo<int>(int) // { dg-error "" } +// { dg-warning "expected \"template\" keyword before dependent template name" "" { target { c++20 } } .-1 } +// ^ bogus warning caused by the above being treated as an expression, not a declaration. { return 1; } diff --git a/gcc/testsuite/g++.dg/lookup/decl1.C b/gcc/testsuite/g++.dg/lookup/decl1.C index 205ffcff1d7..4c62dc58c9a 100644 --- a/gcc/testsuite/g++.dg/lookup/decl1.C +++ b/gcc/testsuite/g++.dg/lookup/decl1.C @@ -14,7 +14,7 @@ struct C2 { }; template<typename X> template<typename Y> -C2<X>::operator C1<Y>() +C2<X>::template operator C1<Y>() { return C1<Y>(); } diff --git a/gcc/testsuite/g++.dg/parse/error11.C b/gcc/testsuite/g++.dg/parse/error11.C index 4baf97e531d..6f643e5b5d8 100644 --- a/gcc/testsuite/g++.dg/parse/error11.C +++ b/gcc/testsuite/g++.dg/parse/error11.C @@ -20,9 +20,10 @@ struct Foo // { dg-message "17:'<:' is an alternate spelling" "17-alt" { target { ! c++11 } } .-1 } // { dg-error "39:'<::' cannot begin" "39-begin" { target { ! c++11 } } .-2 } // { dg-message "39:'<:' is an alternate spelling" "39-alt" { target { ! c++11 } } .-3 } - n.template Nested<B>::method(); + n.template Nested<B>::method(); // { dg-error "is not a template" "" { target { *-*-* } } } n.template Nested<::B>::method(); // { dg-error "22:'<::' cannot begin" "error" { target { ! c++11 } } } // { dg-message "22:'<:' is an alternate" "note" { target { ! c++11 } } .-1 } +// { dg-error "is not a template" "" { target { *-*-* } } .-2 } Nested<B>::method(); Nested<::B>::method(); // { dg-error "11:'<::' cannot begin" "error" { target { ! c++11 } } } // { dg-message "11:'<:' is an alternate" "note" { target { ! c++11 } } .-1 } diff --git a/gcc/testsuite/g++.dg/parse/template26.C b/gcc/testsuite/g++.dg/parse/template26.C index aab9763ccaf..add8c64eabb 100644 --- a/gcc/testsuite/g++.dg/parse/template26.C +++ b/gcc/testsuite/g++.dg/parse/template26.C @@ -6,13 +6,13 @@ namespace impl } template <class T, class U, __SIZE_TYPE__ - = sizeof(impl::create<T>()->*impl::create<U>())> + = sizeof(impl::create<T>()->*impl::template create<U>())> struct foo1; template <class T, class U, __SIZE_TYPE__ - = sizeof(impl::create<T>()->impl::create<U>())> // { dg-error "not a class member" } + = sizeof(impl::create<T>()->impl::template create<U>())> // { dg-error "not a class member" } struct foo2; template <class T, class U, __SIZE_TYPE__ - = sizeof(impl::create<T>().impl::create<U>())> // { dg-error "not a class member" } + = sizeof(impl::create<T>().impl::template create<U>())> // { dg-error "not a class member" } struct foo3; diff --git a/gcc/testsuite/g++.dg/template/dependent-name15.C b/gcc/testsuite/g++.dg/template/dependent-name15.C new file mode 100644 index 00000000000..3d16a49d940 --- /dev/null +++ b/gcc/testsuite/g++.dg/template/dependent-name15.C @@ -0,0 +1,51 @@ +// C++ PR 70317 +// { dg-do compile } + +template<class T> class mytemplateclass +{ + public: + template<class U> void class_func() {} + template<class U> static void class_func_static() {} +}; + +class myclass +{ + public: + int testint; + template<class U> void class_func() {} + template<class U> static void class_func_static() {} +}; + +template<class Y> void tests_func(mytemplateclass<Y> c, myclass c2) +{ + /* Dependent template accessors (ill-formed code). */ + c.class_func<Y>(); // { dg-warning "keyword before dependent template name" } + // { dg-error "expected primary-expression" "" { target { *-*-* } } .-1 } + (&c)->class_func<Y>(); // { dg-warning "keyword before dependent template name" } + // { dg-error "expected primary-expression" "" { target { *-*-* } } .-1 } + mytemplateclass<Y>::class_func_static<Y>(); // { dg-warning "keyword before dependent template name" } + // { dg-error "expected primary-expression" "" { target { *-*-* } } .-1 } + + /* Dependent template accessors (well-formed code). */ + c.template class_func<Y>(); + (&c)->template class_func<Y>(); + mytemplateclass<Y>::template class_func_static<Y>(); + + /* Non-dependent template accessors (well-formed code). */ + c2.class_func<myclass>(); + (&c2)->class_func<myclass>(); + myclass::class_func_static<myclass>(); +} + +int main() +{ + mytemplateclass<myclass> c; + myclass c2; + tests_func<myclass>(c, c2); + + c2.testint = 53; + /* Make sure this isn't treated as a template. */ + bool testbool = c2.testint < 999 > 7; + /* This probably will be treated as a template initially but it should still work. */ + testbool = c2.testint < 123 > (50); +} \ No newline at end of file diff --git a/gcc/testsuite/g++.old-deja/g++.pt/explicit81.C b/gcc/testsuite/g++.old-deja/g++.pt/explicit81.C index 576ba5439a6..5232d770720 100644 --- a/gcc/testsuite/g++.old-deja/g++.pt/explicit81.C +++ b/gcc/testsuite/g++.old-deja/g++.pt/explicit81.C @@ -26,10 +26,10 @@ void tf(C *ptr) { N::nf<N::e0>(); gf<N::e0>(); - ptr->X::xfn <N::e0> (); + ptr->X::template xfn <N::e0> (); ptr->C::template xfn <N::e0> (); ptr->template xfn <N::e0> (); - ptr->X::sfn <N::e0> (); + ptr->X::template sfn <N::e0> (); ptr->C::template sfn <N::e0> (); ptr->template sfn <N::e0> (); X::sfn <N::e0> (); -- 2.33.0