Hi Jason,
I did intend to break this up but as I've completed the first pass at
refactoring implicit function templates and along the way fixed the 5
bugs submitted by Volker I thought I'd post the rolled up patch for you
to peruse.
It (should) support arbitrarily complex use of 'auto' in a parameter
list to introduce template parameters. Implicit pack expansion
parameters containing a single 'auto' are handled by tentatively
assuming a single 'auto' might be a pack then reverting the flag if
found not to be. This now fully supports the current C++14 generic
lambda examples.
No new regressions on trunk as of this morning.
Any feedback appreciated. Diff attached with -w -b.
Cheers,
Adam
TODO: Changelog post review.
PR c++/58534
PR c++/58536
PR c++/58548
PR c++/58549
PR c++/58637
gcc/cp/decl.c | 30 +---
gcc/cp/parser.c | 312
++++++++++++++++++++++++++---------
gcc/cp/parser.h | 15 ++
gcc/testsuite/g++.dg/cpp1y/pr58534.C | 9 +
gcc/testsuite/g++.dg/cpp1y/pr58536.C | 12 ++
gcc/testsuite/g++.dg/cpp1y/pr58548.C | 10 ++
gcc/testsuite/g++.dg/cpp1y/pr58549.C | 10 ++
gcc/testsuite/g++.dg/cpp1y/pr58637.C | 7 +
8 files changed, 299 insertions(+), 106 deletions(-)
commit 90c77cdd87eb63617719a9ad129803a2048761ff
Author: Adam Butcher <a...@jessamine.co.uk>
Date: Wed Sep 18 17:39:40 2013 +0100
Refactor implicit function template implementation and fix 58534, 58536, 58548, 58549 and 58637.
TODO: Changelog post review.
PR c++/58534
PR c++/58536
PR c++/58548
PR c++/58549
PR c++/58637
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 81ed409..8095eca 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -10323,34 +10323,12 @@ grokdeclarator (const cp_declarator *declarator,
if (type_uses_auto (type))
{
- if (template_parm_flag)
- {
- error ("template parameter declared %<auto%>");
- type = error_mark_node;
- }
- else if (decl_context == CATCHPARM)
- {
- error ("catch parameter declared %<auto%>");
+ if (cxx_dialect >= cxx1y)
+ error ("%<auto%> parameter not permitted in this context");
+ else
+ error ("parameter declared %<auto%>");
type = error_mark_node;
}
- else if (current_class_type && LAMBDA_TYPE_P (current_class_type))
- {
- if (cxx_dialect < cxx1y)
- pedwarn (location_of (type), 0,
- "use of %<auto%> in lambda parameter declaration "
- "only available with "
- "-std=c++1y or -std=gnu++1y");
- }
- else if (cxx_dialect < cxx1y)
- pedwarn (location_of (type), 0,
- "use of %<auto%> in parameter declaration "
- "only available with "
- "-std=c++1y or -std=gnu++1y");
- else
- pedwarn (location_of (type), OPT_Wpedantic,
- "ISO C++ forbids use of %<auto%> in parameter "
- "declaration");
- }
/* A parameter declared as an array of T is really a pointer to T.
One declared as a function is really a pointer to a function.
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index 90c1775..8c8be4c 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -245,6 +245,19 @@ static FILE *cp_lexer_debug_stream;
sizeof, typeof, or alignof. */
int cp_unevaluated_operand;
+/* Nonzero if parsing a context where 'auto' in a parameter list should not
+ trigger an implicit template parameter. Specifically, 'auto' should not
+ introduce a new template type parameter in explicit specializations, trailing
+ return types or exception specifiers. */
+int cp_disable_auto_as_implicit_function_template_parm;
+
+/* Track the number of implicit template parameters introduced by the
+ current function parameter and, for handling implicit parameter packs, track
+ the most recently synthesized type. These are reset prior to parsing in
+ cp_parameter_declarator and updated in synthesize_implicit_template_parm. */
+int cp_num_implicit_template_type_parms;
+tree cp_last_implicit_template_type_parm;
+
/* Dump up to NUM tokens in BUFFER to FILE starting with token
START_TOKEN. If START_TOKEN is NULL, the dump starts with the
first token in BUFFER. If NUM is 0, dump all the tokens. If
@@ -2064,8 +2077,8 @@ static vec<constructor_elt, va_gc> *cp_parser_initializer_list
static bool cp_parser_ctor_initializer_opt_and_function_body
(cp_parser *, bool);
-static tree add_implicit_template_parms
- (cp_parser *, size_t, tree);
+static tree synthesize_implicit_template_parm
+ (cp_parser *parser);
static tree finish_fully_implicit_template
(cp_parser *, tree);
@@ -3393,6 +3406,8 @@ cp_parser_new (void)
/* Not declaring an implicit function template. */
parser->fully_implicit_function_template_p = false;
+ parser->implicit_template_parms = 0;
+ parser->implicit_template_scope = 0;
return parser;
}
@@ -8559,11 +8574,15 @@ cp_parser_lambda_expression (cp_parser* parser)
unsigned char in_statement = parser->in_statement;
bool in_switch_statement_p = parser->in_switch_statement_p;
bool fully_implicit_function_template_p = parser->fully_implicit_function_template_p;
+ tree implicit_template_parms = parser->implicit_template_parms;
+ cp_binding_level* implicit_template_scope = parser->implicit_template_scope;
parser->num_template_parameter_lists = 0;
parser->in_statement = 0;
parser->in_switch_statement_p = false;
parser->fully_implicit_function_template_p = false;
+ parser->implicit_template_parms = 0;
+ parser->implicit_template_scope = 0;
/* By virtue of defining a local class, a lambda expression has access to
the private variables of enclosing classes. */
@@ -8588,6 +8607,8 @@ cp_parser_lambda_expression (cp_parser* parser)
parser->in_statement = in_statement;
parser->in_switch_statement_p = in_switch_statement_p;
parser->fully_implicit_function_template_p = fully_implicit_function_template_p;
+ parser->implicit_template_parms = implicit_template_parms;
+ parser->implicit_template_scope = implicit_template_scope;
}
pop_deferring_access_checks ();
@@ -13978,12 +13999,18 @@ cp_parser_explicit_specialization (cp_parser* parser)
cp_parser_explicit_specialization (parser);
}
else
+ {
+ ++cp_disable_auto_as_implicit_function_template_parm;
+
/* Parse the dependent declaration. */
cp_parser_single_declaration (parser,
/*checks=*/NULL,
/*member_p=*/false,
/*explicit_specialization_p=*/true,
/*friend_p=*/NULL);
+
+ --cp_disable_auto_as_implicit_function_template_parm;
+ }
/* We're done with the specialization. */
end_specialization ();
/* For the erroneous case of a template with C linkage, we pushed an
@@ -14287,6 +14314,30 @@ cp_parser_simple_type_specifier (cp_parser* parser,
case RID_AUTO:
maybe_warn_cpp0x (CPP0X_AUTO);
+ if (!cp_disable_auto_as_implicit_function_template_parm &&
+ current_binding_level->kind == sk_function_parms)
+ {
+ type = synthesize_implicit_template_parm (parser);
+
+ if (current_class_type && LAMBDA_TYPE_P (current_class_type))
+ {
+ if (cxx_dialect < cxx1y)
+ pedwarn (location_of (type), 0,
+ "use of %<auto%> in lambda parameter declaration "
+ "only available with "
+ "-std=c++1y or -std=gnu++1y");
+ }
+ else if (cxx_dialect < cxx1y)
+ pedwarn (location_of (type), 0,
+ "use of %<auto%> in parameter declaration "
+ "only available with "
+ "-std=c++1y or -std=gnu++1y");
+ else
+ pedwarn (location_of (type), OPT_Wpedantic,
+ "ISO C++ forbids use of %<auto%> in parameter "
+ "declaration");
+ }
+ else
type = make_auto ();
break;
@@ -17725,7 +17776,10 @@ static tree cp_parser_template_type_arg (cp_parser *parser)
static tree cp_parser_trailing_type_id (cp_parser *parser)
{
- return cp_parser_type_id_1 (parser, false, true);
+ ++cp_disable_auto_as_implicit_function_template_parm;
+ tree t = cp_parser_type_id_1 (parser, false, true);
+ --cp_disable_auto_as_implicit_function_template_parm;
+ return t;
}
/* Parse a type-specifier-seq.
@@ -17934,7 +17988,6 @@ cp_parser_parameter_declaration_list (cp_parser* parser, bool *is_error)
tree *tail = ¶meters;
bool saved_in_unbraced_linkage_specification_p;
int index = 0;
- int implicit_template_parms = 0;
/* Assume all will go well. */
*is_error = false;
@@ -17951,6 +18004,8 @@ cp_parser_parameter_declaration_list (cp_parser* parser, bool *is_error)
cp_parameter_declarator *parameter;
tree decl = error_mark_node;
bool parenthesized_p = false;
+ cp_last_implicit_template_type_parm = 0;
+ cp_num_implicit_template_type_parms = 0;
/* Parse the parameter. */
parameter
= cp_parser_parameter_declaration (parser,
@@ -17963,16 +18018,43 @@ cp_parser_parameter_declaration_list (cp_parser* parser, bool *is_error)
if (parameter)
{
+ /* If there is only one generic type in the parameter, tentatively
+ assume that that it is a parameter pack. If it turns out, after
+ grokdeclarator, that the parameter does not contain a pack
+ expansion, then reset it be a non-pack type. */
+ if (cp_num_implicit_template_type_parms == 1)
+ TEMPLATE_PARM_PARAMETER_PACK
+ (TEMPLATE_TYPE_PARM_INDEX
+ (cp_last_implicit_template_type_parm)) = true;
+
decl = grokdeclarator (parameter->declarator,
¶meter->decl_specifiers,
PARM,
parameter->default_argument != NULL_TREE,
¶meter->decl_specifiers.attributes);
+ if (cp_last_implicit_template_type_parm)
+ {
+ if (parameter->declarator
+ && parameter->declarator->parameter_pack_p)
+ {
+ /* Forbid ambiguous implicit pack expansions by only allowing
+ a single generic type in such a parameter.
- if (TREE_TYPE (decl) != error_mark_node
- && parameter->decl_specifiers.type
- && is_auto_or_concept (parameter->decl_specifiers.type))
- ++implicit_template_parms;
+ XXX: Maybe allow if explicitly specified template
+ XXX: 'typename...' account for all expansions? Though this
+ XXX: could be tricky or slow.
+ */
+ if (cp_num_implicit_template_type_parms > 1)
+ cp_parser_error (parser,
+ "cannot expand parameter pack "
+ "with more than one implicit "
+ "template parameter");
+ }
+ else if (cp_num_implicit_template_type_parms == 1)
+ TEMPLATE_PARM_PARAMETER_PACK
+ (TEMPLATE_TYPE_PARM_INDEX
+ (cp_last_implicit_template_type_parm)) = false;
+ }
}
deprecated_state = DEPRECATED_NORMAL;
@@ -18061,10 +18143,13 @@ cp_parser_parameter_declaration_list (cp_parser* parser, bool *is_error)
parser->in_unbraced_linkage_specification_p
= saved_in_unbraced_linkage_specification_p;
- if (parameters != error_mark_node && implicit_template_parms)
- parameters = add_implicit_template_parms (parser,
- implicit_template_parms,
- parameters);
+ if (cp_binding_level *its = parser->implicit_template_scope)
+ if (current_binding_level == its
+ || (current_class_name && current_binding_level->level_chain == its))
+ {
+ parser->implicit_template_parms = 0;
+ parser->implicit_template_scope = 0;
+ }
return parameters;
}
@@ -20575,6 +20660,8 @@ cp_parser_exception_specification_opt (cp_parser* parser)
/* Consume the `throw'. */
cp_lexer_consume_token (parser->lexer);
+ ++cp_disable_auto_as_implicit_function_template_parm;
+
/* Look for the `('. */
cp_parser_require (parser, CPP_OPEN_PAREN, RT_OPEN_PAREN);
@@ -20598,6 +20685,8 @@ cp_parser_exception_specification_opt (cp_parser* parser)
/* Look for the `)'. */
cp_parser_require (parser, CPP_CLOSE_PAREN, RT_CLOSE_PAREN);
+ --cp_disable_auto_as_implicit_function_template_parm;
+
return type_id_list;
}
@@ -22292,6 +22381,15 @@ cp_parser_function_definition_after_declarator (cp_parser* parser,
bool saved_in_function_body;
unsigned saved_num_template_parameter_lists;
cp_token *token;
+ bool fully_implicit_function_template_p
+ = parser->fully_implicit_function_template_p;
+ parser->fully_implicit_function_template_p = false;
+ tree implicit_template_parms
+ = parser->implicit_template_parms;
+ parser->implicit_template_parms = 0;
+ cp_binding_level* implicit_template_scope
+ = parser->implicit_template_scope;
+ parser->implicit_template_scope = 0;
saved_in_function_body = parser->in_function_body;
parser->in_function_body = true;
@@ -22364,6 +22462,13 @@ cp_parser_function_definition_after_declarator (cp_parser* parser,
= saved_num_template_parameter_lists;
parser->in_function_body = saved_in_function_body;
+ parser->fully_implicit_function_template_p
+ = fully_implicit_function_template_p;
+ parser->implicit_template_parms
+ = implicit_template_parms;
+ parser->implicit_template_scope
+ = implicit_template_scope;
+
if (parser->fully_implicit_function_template_p)
finish_fully_implicit_template (parser, /*member_decl_opt=*/0);
@@ -28920,7 +29025,7 @@ static tree
make_generic_type_name ()
{
char buf[32];
- sprintf (buf, "<auto%d>", ++generic_parm_count);
+ sprintf (buf, "auto:%d", ++generic_parm_count);
return get_identifier (buf);
}
@@ -28934,36 +29039,79 @@ tree_type_is_auto_or_concept (const_tree t)
return TREE_TYPE (t) && is_auto_or_concept (TREE_TYPE (t));
}
-/* Add EXPECT_COUNT implicit template parameters gleaned from the generic
- type parameters in PARAMETERS to the CURRENT_TEMPLATE_PARMS (creating a new
- template parameter list if necessary). Returns PARAMETERS suitably rewritten
- to reference the newly created types or ERROR_MARK_NODE on failure. */
+/* Add an implicit template type parameter to the CURRENT_TEMPLATE_PARMS
+ (creating a new template parameter list if necessary). Returns the newly
+ created template type parm. */
tree
-add_implicit_template_parms (cp_parser *parser, size_t expect_count,
- tree parameters)
+synthesize_implicit_template_parm (cp_parser *parser)
{
gcc_assert (current_binding_level->kind == sk_function_parms);
cp_binding_level *fn_parms_scope = current_binding_level;
- bool become_template =
- fn_parms_scope->level_chain->kind != sk_template_parms;
+ /* CLASS_SCOPE is set for out-of-line member definitions. */
+ cp_binding_level *class_scope = 0;
+ if (current_class_type && !LAMBDA_TYPE_P (current_class_type)
+ && parser->num_classes_being_defined == 0)
+ {
+ class_scope = current_binding_level->level_chain;
+ gcc_assert (class_scope->kind == sk_class);
+ }
- size_t synth_count = 0;
+ /* Roll back to the existing impl tmpl scope level or the previous scope and
+ either introduce a new template parameter list or update an existing one.
+ The function scope is added back after template parameter synthesis
+ below. */
- /* Roll back a scope level and either introduce a new template parameter list
- or update an existing one. The function scope is added back after template
- parameter synthesis below. */
- current_binding_level = fn_parms_scope->level_chain;
+ bool become_template = false;
- /* TPARMS tracks the function's template parameter list. This is either a new
- chain in the case of a fully implicit function template or an extension of
- the function's explicitly specified template parameter list. */
- tree tparms = NULL_TREE;
+ /* We are either continuing a function template that already contains implicit
+ template parameters, creating a new fully-implicit function template, or
+ extending an existing function template with implicit template parameters.
+ */
- if (become_template)
+ /* XXX: Performance note: I tried to reduce the decomposition and reassembling
+ of current_template_parms and implicit_template_parms but failed; mostly
+ due to the expectations of surrounding code and process_template_parm and
+ end_template_parm_list. Though the code below is functionally sound, it
+ seems overly complex as it recreates the template parameter list for each
+ new generic type parsed. I'm sure this can be remedied by a future
+ optimization. */
+
+ if (parser->implicit_template_scope)
{
+ gcc_assert (parser->implicit_template_parms);
+
+ current_binding_level = parser->implicit_template_scope->level_chain;
+ tree inner_vec = INNERMOST_TEMPLATE_PARMS (current_template_parms);
+ current_template_parms = TREE_CHAIN (current_template_parms);
+
+ size_t inner_vec_len = TREE_VEC_LENGTH (inner_vec);
+ gcc_assert (inner_vec_len != 0);
+
+ tree t
+ = parser->implicit_template_parms
+ = TREE_VEC_ELT (inner_vec, 0);
+
+ for (size_t n = 1; n < inner_vec_len; ++n)
+ t = TREE_CHAIN (t) = TREE_VEC_ELT (inner_vec, n);
+
+ ++processing_template_parmlist;
+ }
+ else if (fn_parms_scope->level_chain->kind != sk_template_parms)
+ {
+ gcc_assert (!parser->implicit_template_parms);
+ gcc_assert (!parser->implicit_template_scope);
+
+ become_template = true;
+
+ if (class_scope)
+ parser->implicit_template_scope = class_scope;
+ else
+ parser->implicit_template_scope = fn_parms_scope;
+ current_binding_level = parser->implicit_template_scope->level_chain;
+
push_deferring_access_checks (dk_deferred);
begin_template_parm_list ();
@@ -28977,61 +29125,65 @@ add_implicit_template_parms (cp_parser *parser, size_t expect_count,
gcc_assert (current_template_parms);
- /* Pop the innermost template parms into TPARMS. */
+ if (class_scope)
+ parser->implicit_template_scope = class_scope;
+ else
+ parser->implicit_template_scope = fn_parms_scope;
+ current_binding_level = parser->implicit_template_scope->level_chain;
+
+ /* Pop the innermost template parms into IMPLICIT_TEMPLATE_PARMS. */
+
tree inner_vec = INNERMOST_TEMPLATE_PARMS (current_template_parms);
current_template_parms = TREE_CHAIN (current_template_parms);
size_t inner_vec_len = TREE_VEC_LENGTH (inner_vec);
- if (inner_vec_len != 0)
- {
- tree t = tparms = TREE_VEC_ELT (inner_vec, 0);
+ gcc_assert (inner_vec_len != 0);
+
+ tree t
+ = parser->implicit_template_parms
+ = TREE_VEC_ELT (inner_vec, 0);
+
for (size_t n = 1; n < inner_vec_len; ++n)
t = TREE_CHAIN (t) = TREE_VEC_ELT (inner_vec, n);
- }
++processing_template_parmlist;
}
- for (tree p = parameters; p && synth_count < expect_count; p = TREE_CHAIN (p))
- {
- tree generic_type_ptr
- = find_type_usage (TREE_VALUE (p), tree_type_is_auto_or_concept);
-
- if (!generic_type_ptr)
- continue;
-
- ++synth_count;
-
tree synth_id = make_generic_type_name ();
tree synth_tmpl_parm = finish_template_type_parm (class_type_node,
synth_id);
- tparms = process_template_parm (tparms, DECL_SOURCE_LOCATION (TREE_VALUE
- (p)),
- build_tree_list (NULL_TREE,
- synth_tmpl_parm),
+ parser->implicit_template_parms
+ = process_template_parm (parser->implicit_template_parms,
+ input_location,
+ build_tree_list (NULL_TREE, synth_tmpl_parm),
/*non_type=*/false,
/*param_pack=*/false);
- /* Rewrite the type of P to be the template_parm added above (getdecls is
- used to retrieve it since it is the most recent declaration in this
- scope). Qualifiers need to be preserved also. */
-
- tree& cur_type = TREE_TYPE (generic_type_ptr);
tree new_type = TREE_TYPE (getdecls ());
- if (TYPE_QUALS (cur_type))
- cur_type = cp_build_qualified_type (new_type, TYPE_QUALS (cur_type));
- else
- cur_type = new_type;
+ /* If introducing a new sk_template_parms scope, then push the existing
+ scopes, otherwise simply return to the calling scope. */
+ if (become_template)
+ {
+ if (class_scope)
+ push_binding_level (class_scope);
+ push_binding_level (fn_parms_scope);
}
+ else
+ current_binding_level = fn_parms_scope;
- gcc_assert (synth_count == expect_count);
+ /* XXX: Performance note: Feels like we can save on work here by doing this
+ incrementally rather than building a tree vec and populating with same
+ content each time. See XXX above. */
- push_binding_level (fn_parms_scope);
+ end_template_parm_list (parser->implicit_template_parms);
- end_template_parm_list (tparms);
+ /* Track progress to allow for support and diagnostics relating to implicit
+ pack expansions. */
+ cp_last_implicit_template_type_parm = new_type;
+ ++cp_num_implicit_template_type_parms;
- return parameters;
+ return new_type;
}
/* Finish the declaration of a fully implicit function template. Such a
diff --git a/gcc/cp/parser.h b/gcc/cp/parser.h
index ffdddaf..c02d889 100644
--- a/gcc/cp/parser.h
+++ b/gcc/cp/parser.h
@@ -347,6 +347,21 @@ typedef struct GTY(()) cp_parser {
identifiers) rather than an explicit template parameter list. */
bool fully_implicit_function_template_p;
+ /* Tracks the function's template parameter list when declaring a function
+ using generic type parameters. This is either a new chain in the case of a
+ fully implicit function template or an extension of the function's existing
+ template parameter list. This is tracked to optimize calls subsequent
+ calls to synthesize_implicit_template_parm during
+ cp_parser_parameter_declaration. */
+ tree implicit_template_parms;
+
+ /* The scope into which an implicit template parameter list has been
+ introduced or an existing template parameter list is being extended with
+ implicit template paramaters. In most cases this is the sk_function_parms
+ scope containing the use of a generic type. In the case of an out-of-line
+ member definition using a generic type, it is the sk_class scope. */
+ cp_binding_level* implicit_template_scope;
+
} cp_parser;
/* In parser.c */
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr58534.C b/gcc/testsuite/g++.dg/cpp1y/pr58534.C
new file mode 100644
index 0000000..4aa4f43
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/pr58534.C
@@ -0,0 +1,9 @@
+// { dg-do compile }
+// { dg-options "-std=gnu++1y" }
+
+// PR c++/58534
+
+template<typename> void foo(const auto&) {}
+
+template<typename, typename...T> void foo(const auto&, T...) {}
+
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr58536.C b/gcc/testsuite/g++.dg/cpp1y/pr58536.C
new file mode 100644
index 0000000..8050c19
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/pr58536.C
@@ -0,0 +1,12 @@
+// { dg-do compile }
+// { dg-options "-std=gnu++1y" }
+
+// PR c++/58536
+
+struct A
+{
+ A(auto);
+};
+
+A::A(auto) {}
+
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr58548.C b/gcc/testsuite/g++.dg/cpp1y/pr58548.C
new file mode 100644
index 0000000..0ac2e1c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/pr58548.C
@@ -0,0 +1,10 @@
+// { dg-do compile }
+// { dg-options "-std=gnu++1y" }
+
+// PR c++/58548
+
+void foo(auto)
+{
+ struct A { int i; };
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr58549.C b/gcc/testsuite/g++.dg/cpp1y/pr58549.C
new file mode 100644
index 0000000..b71bac9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/pr58549.C
@@ -0,0 +1,10 @@
+// { dg-do compile }
+// { dg-options "-std=gnu++1y" }
+
+// PR c++/58549
+
+void foo(auto)
+{
+ void bar();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr58637.C b/gcc/testsuite/g++.dg/cpp1y/pr58637.C
new file mode 100644
index 0000000..46200ff
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/pr58637.C
@@ -0,0 +1,7 @@
+// { dg-do compile }
+// { dg-options "-std=gnu++1y" }
+
+// PR c++/58637
+
+template<> void foo(auto); // { dg-error "auto|not a template" }
+