Re: [PATCH] Fix PR c++/60573
On 2014-03-27 21:16, Adam Butcher wrote: On 2014-03-27 20:45, Adam Butcher wrote: PR c++/60573 * name-lookup.h (cp_binding_level): New field scope_defines_class_p. * semantics.c (begin_class_definition): Set scope_defines_class_p. * pt.c (instantiate_class_template_1): Likewise. * parser.c (synthesize_implicit_template_parm): Use cp_binding_level:: scope_defines_class_p rather than TYPE_BEING_DEFINED as the predicate for unwinding to class-defining scope to handle the erroneous definition of a generic function of an arbitrarily nested class within an enclosing class. Still got issues with this. It fails on out-of-line defs. I'll have another look. Turns out the solution was OK but I didn't account for the class-defining scope being reused for subsequent out-of-line declarations. I've made 'scope_defines_class_p' in to the now transient 'defining_class_p' predicate which is reset on leaving scope. I've ditched the 'scope_' prefix and also ditched the modifications to 'instantiate_class_template_1'. The patch delta is included below (but will probably be munged by my webmail client). I'll reply to this with the full patch. There is also the fix for PR c++/60626 (http://gcc.gnu.org/ml/gcc-patches/2014-03/msg01294.html) that deals with another form of erroneous generic function declarations with nested class scope. Cheers, Adam diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c index 53f14f3..0137c3f 100644 --- a/gcc/cp/name-lookup.c +++ b/gcc/cp/name-lookup.c @@ -1630,10 +1630,14 @@ leave_scope (void) free_binding_level = scope; } - /* Find the innermost enclosing class scope, and reset - CLASS_BINDING_LEVEL appropriately. */ if (scope-kind == sk_class) { + /* Reset DEFINING_CLASS_P to allow for reuse of a +class-defining scope in a non-defining context. */ + scope-defining_class_p = 0; + + /* Find the innermost enclosing class scope, and reset +CLASS_BINDING_LEVEL appropriately. */ class_binding_level = NULL; for (scope = current_binding_level; scope; scope = scope-level_chain) if (scope-kind == sk_class) diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h index 9e5d812..40e0338 100644 --- a/gcc/cp/name-lookup.h +++ b/gcc/cp/name-lookup.h @@ -255,9 +255,12 @@ struct GTY(()) cp_binding_level { unsigned more_cleanups_ok : 1; unsigned have_cleanups : 1; - /* Set if this scope is of sk_class kind and is the defining - scope for this_entity. */ - unsigned scope_defines_class_p : 1; + /* Transient state set if this scope is of sk_class kind + and is in the process of defining 'this_entity'. Reset + on leaving the class definition to allow for the scope + to be subsequently re-used as a non-defining scope for + 'this_entity'. */ + unsigned defining_class_p : 1; /* 23 bits left to fill a 32-bit word. */ }; diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 4919a67..0945bfd 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -32027,7 +32027,7 @@ synthesize_implicit_template_parm (cp_parser *parser) declarator should be injected into the scope of 'A' as if the ill-formed template was specified explicitly. */ - while (scope-kind == sk_class !scope-scope_defines_class_p) + while (scope-kind == sk_class !scope-defining_class_p) { parent_scope = scope; scope = scope-level_chain; diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index 90faeec..c791d03 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -8905,12 +8905,9 @@ instantiate_class_template_1 (tree type) return type; /* Now we're really doing the instantiation. Mark the type as in - the process of being defined... */ + the process of being defined. */ TYPE_BEING_DEFINED (type) = 1; - /* ... and the scope defining it. */ - class_binding_level-scope_defines_class_p = 1; - /* We may be in the middle of deferred access check. Disable it now. */ push_deferring_access_checks (dk_no_deferred); diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c index deba2ab..207a42d 100644 --- a/gcc/cp/semantics.c +++ b/gcc/cp/semantics.c @@ -2777,7 +2777,7 @@ begin_class_definition (tree t) maybe_process_partial_specialization (t); pushclass (t); TYPE_BEING_DEFINED (t) = 1; - class_binding_level-scope_defines_class_p = 1; + class_binding_level-defining_class_p = 1; if (flag_pack_struct) {
[PATCH] Fix PR c++/60573
PR c++/60573 * name-lookup.h (cp_binding_level): New transient field defining_class_p to indicate whether a scope is in the process of defining a class. * semantics.c (begin_class_definition): Set defining_class_p. * name-lookup.c (leave_scope): Reset defining_class_p. * parser.c (synthesize_implicit_template_parm): Use cp_binding_level:: defining_class_p rather than TYPE_BEING_DEFINED as the predicate for unwinding to class-defining scope to handle the erroneous definition of a generic function of an arbitrarily nested class within an enclosing class. PR c++/60573 * g++.dg/cpp1y/pr60573.C: New testcase. --- gcc/cp/name-lookup.c | 8 ++-- gcc/cp/name-lookup.h | 9 - gcc/cp/parser.c | 23 +-- gcc/cp/semantics.c | 1 + gcc/testsuite/g++.dg/cpp1y/pr60573.C | 28 5 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr60573.C diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c index 53f14f3..0137c3f 100644 --- a/gcc/cp/name-lookup.c +++ b/gcc/cp/name-lookup.c @@ -1630,10 +1630,14 @@ leave_scope (void) free_binding_level = scope; } - /* Find the innermost enclosing class scope, and reset - CLASS_BINDING_LEVEL appropriately. */ if (scope-kind == sk_class) { + /* Reset DEFINING_CLASS_P to allow for reuse of a +class-defining scope in a non-defining context. */ + scope-defining_class_p = 0; + + /* Find the innermost enclosing class scope, and reset +CLASS_BINDING_LEVEL appropriately. */ class_binding_level = NULL; for (scope = current_binding_level; scope; scope = scope-level_chain) if (scope-kind == sk_class) diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h index a63442f..40e0338 100644 --- a/gcc/cp/name-lookup.h +++ b/gcc/cp/name-lookup.h @@ -255,7 +255,14 @@ struct GTY(()) cp_binding_level { unsigned more_cleanups_ok : 1; unsigned have_cleanups : 1; - /* 24 bits left to fill a 32-bit word. */ + /* Transient state set if this scope is of sk_class kind + and is in the process of defining 'this_entity'. Reset + on leaving the class definition to allow for the scope + to be subsequently re-used as a non-defining scope for + 'this_entity'. */ + unsigned defining_class_p : 1; + + /* 23 bits left to fill a 32-bit word. */ }; /* The binding level currently in effect. */ diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index e729d65..0945bfd 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -32000,7 +32000,7 @@ synthesize_implicit_template_parm (cp_parser *parser) { /* If not defining a class, then any class scope is a scope level in an out-of-line member definition. In this case simply wind back -beyond the first such scope to inject the template argument list. +beyond the first such scope to inject the template parameter list. Otherwise wind back to the class being defined. The latter can occur in class member friend declarations such as: @@ -32011,12 +32011,23 @@ synthesize_implicit_template_parm (cp_parser *parser) friend void A::foo (auto); }; - The template argument list synthesized for the friend declaration - must be injected in the scope of 'B', just beyond the scope of 'A' - introduced by 'A::'. */ + The template parameter list synthesized for the friend declaration + must be injected in the scope of 'B'. This can also occur in + erroneous cases such as: - while (scope-kind == sk_class - !TYPE_BEING_DEFINED (scope-this_entity)) + struct A { +struct B { + void foo (auto); +}; +void B::foo (auto) {} + }; + + Here the attempted definition of 'B::foo' within 'A' is ill-formed + but, nevertheless, the template parameter list synthesized for the + declarator should be injected into the scope of 'A' as if the + ill-formed template was specified explicitly. */ + + while (scope-kind == sk_class !scope-defining_class_p) { parent_scope = scope; scope = scope-level_chain; diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c index 886fbb8..207a42d 100644 --- a/gcc/cp/semantics.c +++ b/gcc/cp/semantics.c @@ -2777,6 +2777,7 @@ begin_class_definition (tree t) maybe_process_partial_specialization (t); pushclass (t); TYPE_BEING_DEFINED (t) = 1; + class_binding_level-defining_class_p = 1; if (flag_pack_struct) { diff --git a/gcc/testsuite/g++.dg/cpp1y/pr60573.C b/gcc/testsuite/g++.dg/cpp1y/pr60573.C new file mode 100644
Re: [PATCH] Fix PR c++/60573
OK. Jason
Re: [PATCH] Fix PR c++/60573
On 03/26/2014 09:12 PM, Adam Butcher wrote: +Note: cp_binding_level::class_shadowed is used as a predicate to +indicate whether a class scope is a class-defining scope. We stop +at the first such scope as this will be the currently open class +definition into which the function being declared will be appended; +and therefore the scope into which the synthesized template +parameter list for the declarator should be injected. */ + + while (scope-kind == sk_class !scope-class_shadowed) That doesn't seem reliable either, unfortunately; class_shadowed is populated when names are looked up, so a declarator that refers to a type member of B will cause scope-class_shadowed to be non-null. Jason
[PATCH] Fix PR c++/60573
PR c++/60573 * name-lookup.h (cp_binding_level): New field scope_defines_class_p. * semantics.c (begin_class_definition): Set scope_defines_class_p. * pt.c (instantiate_class_template_1): Likewise. * parser.c (synthesize_implicit_template_parm): Use cp_binding_level:: scope_defines_class_p rather than TYPE_BEING_DEFINED as the predicate for unwinding to class-defining scope to handle the erroneous definition of a generic function of an arbitrarily nested class within an enclosing class. PR c++/60573 * g++.dg/cpp1y/pr60573.C: New testcase. --- gcc/cp/name-lookup.h | 6 +- gcc/cp/parser.c | 23 +-- gcc/cp/pt.c | 5 - gcc/cp/semantics.c | 1 + gcc/testsuite/g++.dg/cpp1y/pr60573.C | 28 5 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr60573.C diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h index a63442f..9e5d812 100644 --- a/gcc/cp/name-lookup.h +++ b/gcc/cp/name-lookup.h @@ -255,7 +255,11 @@ struct GTY(()) cp_binding_level { unsigned more_cleanups_ok : 1; unsigned have_cleanups : 1; - /* 24 bits left to fill a 32-bit word. */ + /* Set if this scope is of sk_class kind and is the defining + scope for this_entity. */ + unsigned scope_defines_class_p : 1; + + /* 23 bits left to fill a 32-bit word. */ }; /* The binding level currently in effect. */ diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index e729d65..4919a67 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -32000,7 +32000,7 @@ synthesize_implicit_template_parm (cp_parser *parser) { /* If not defining a class, then any class scope is a scope level in an out-of-line member definition. In this case simply wind back -beyond the first such scope to inject the template argument list. +beyond the first such scope to inject the template parameter list. Otherwise wind back to the class being defined. The latter can occur in class member friend declarations such as: @@ -32011,12 +32011,23 @@ synthesize_implicit_template_parm (cp_parser *parser) friend void A::foo (auto); }; - The template argument list synthesized for the friend declaration - must be injected in the scope of 'B', just beyond the scope of 'A' - introduced by 'A::'. */ + The template parameter list synthesized for the friend declaration + must be injected in the scope of 'B'. This can also occur in + erroneous cases such as: - while (scope-kind == sk_class - !TYPE_BEING_DEFINED (scope-this_entity)) + struct A { +struct B { + void foo (auto); +}; +void B::foo (auto) {} + }; + + Here the attempted definition of 'B::foo' within 'A' is ill-formed + but, nevertheless, the template parameter list synthesized for the + declarator should be injected into the scope of 'A' as if the + ill-formed template was specified explicitly. */ + + while (scope-kind == sk_class !scope-scope_defines_class_p) { parent_scope = scope; scope = scope-level_chain; diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c index c791d03..90faeec 100644 --- a/gcc/cp/pt.c +++ b/gcc/cp/pt.c @@ -8905,9 +8905,12 @@ instantiate_class_template_1 (tree type) return type; /* Now we're really doing the instantiation. Mark the type as in - the process of being defined. */ + the process of being defined... */ TYPE_BEING_DEFINED (type) = 1; + /* ... and the scope defining it. */ + class_binding_level-scope_defines_class_p = 1; + /* We may be in the middle of deferred access check. Disable it now. */ push_deferring_access_checks (dk_no_deferred); diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c index 886fbb8..deba2ab 100644 --- a/gcc/cp/semantics.c +++ b/gcc/cp/semantics.c @@ -2777,6 +2777,7 @@ begin_class_definition (tree t) maybe_process_partial_specialization (t); pushclass (t); TYPE_BEING_DEFINED (t) = 1; + class_binding_level-scope_defines_class_p = 1; if (flag_pack_struct) { diff --git a/gcc/testsuite/g++.dg/cpp1y/pr60573.C b/gcc/testsuite/g++.dg/cpp1y/pr60573.C new file mode 100644 index 000..2f60707 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/pr60573.C @@ -0,0 +1,28 @@ +// PR c++/60573 +// { dg-do compile { target c++1y } } +// { dg-options } + +struct A +{ + struct B + { +void foo(auto); + }; + + void B::foo(auto) {} // { dg-error cannot define } + + struct X + { +struct Y +{ + struct Z + { +void foo(auto); + }; +}; + +void
Re: [PATCH] Fix PR c++/60573
On 2014-03-27 20:45, Adam Butcher wrote: @@ -8905,9 +8905,12 @@ instantiate_class_template_1 (tree type) return type; /* Now we're really doing the instantiation. Mark the type as in - the process of being defined. */ + the process of being defined... */ TYPE_BEING_DEFINED (type) = 1; + /* ... and the scope defining it. */ + class_binding_level-scope_defines_class_p = 1; I meant current_binding_level here; but I'm not sure it's necessary here at all.
Re: [PATCH] Fix PR c++/60573
On 2014-03-27 20:45, Adam Butcher wrote: PR c++/60573 * name-lookup.h (cp_binding_level): New field scope_defines_class_p. * semantics.c (begin_class_definition): Set scope_defines_class_p. * pt.c (instantiate_class_template_1): Likewise. * parser.c (synthesize_implicit_template_parm): Use cp_binding_level:: scope_defines_class_p rather than TYPE_BEING_DEFINED as the predicate for unwinding to class-defining scope to handle the erroneous definition of a generic function of an arbitrarily nested class within an enclosing class. Still got issues with this. It fails on out-of-line defs. I'll have another look.
Re: [PATCH] Fix PR c++/60573
On 03/25/2014 03:48 PM, Adam Butcher wrote: I don't follow. Are you suggesting a case like the following? struct A { struct X { struct B { void foo(auto); }; }; void X::B::foo(auto) {} // { dg-error cannot define } }; I meant struct A { struct X { struct B { void foo(auto); }; void B::foo(auto) {} // { dg-error cannot define } }; }; Here we push both A and X for the declarator. When we get to the pushed X, we see that the enclosing scope is A, so we break out of the loop and don't pop either of the pushed scopes. Jason
Re: [PATCH] Fix PR c++/60573
On 2014-03-26 15:17, Jason Merrill wrote: I meant struct A { struct X { struct B { void foo(auto); }; void B::foo(auto) {} // { dg-error cannot define } }; }; Here we push both A and X for the declarator. When we get to the pushed X, we see that the enclosing scope is A, so we break out of the loop and don't pop either of the pushed scopes. Thought I was probably being dense! :) Yes, that will be broken with the current patch. Continuing the loop based on TYPE_BEING_DEFINED might do the trick. I'll try to have a look later. Cheers, Adam
Re: [PATCH] Fix PR c++/60573
On 2014-03-25 15:48, Jason Merrill wrote: I think we need some way to designate a scope that actually corresponds to a class-specifier. Agreed. I'll look into it. Adam
[PATCH] Fix PR c++/60573
PR c++/60573 * parser.c (synthesize_implicit_template_parm): Use cp_binding_level:: class_shadowed rather than TYPE_BEING_DEFINED as the predicate for unwinding to class-defining scope to handle the erroneous definition of a generic function of an arbitrarily nested class within an enclosing class. PR c++/60573 * g++.dg/cpp1y/pr60573.C: New testcase. --- gcc/cp/parser.c | 30 -- gcc/testsuite/g++.dg/cpp1y/pr60573.C | 28 2 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr60573.C diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index e729d65..2130bcd 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -32000,7 +32000,7 @@ synthesize_implicit_template_parm (cp_parser *parser) { /* If not defining a class, then any class scope is a scope level in an out-of-line member definition. In this case simply wind back -beyond the first such scope to inject the template argument list. +beyond the first such scope to inject the template parameter list. Otherwise wind back to the class being defined. The latter can occur in class member friend declarations such as: @@ -32011,12 +32011,30 @@ synthesize_implicit_template_parm (cp_parser *parser) friend void A::foo (auto); }; - The template argument list synthesized for the friend declaration - must be injected in the scope of 'B', just beyond the scope of 'A' - introduced by 'A::'. */ + The template parameter list synthesized for the friend declaration + must be injected in the scope of 'B'. This can also occur in + erroneous cases such as: - while (scope-kind == sk_class - !TYPE_BEING_DEFINED (scope-this_entity)) + struct A { +struct B { + void foo (auto); +}; +void B::foo (auto) {} + }; + +Here the attempted definition of 'B::foo' within 'A' is ill-formed +but, nevertheless, the template parameter list synthesized for the +declarator should be injected into the scope of 'A' as if the +ill-formed template was specified explicitly. + +Note: cp_binding_level::class_shadowed is used as a predicate to +indicate whether a class scope is a class-defining scope. We stop +at the first such scope as this will be the currently open class +definition into which the function being declared will be appended; +and therefore the scope into which the synthesized template +parameter list for the declarator should be injected. */ + + while (scope-kind == sk_class !scope-class_shadowed) { parent_scope = scope; scope = scope-level_chain; diff --git a/gcc/testsuite/g++.dg/cpp1y/pr60573.C b/gcc/testsuite/g++.dg/cpp1y/pr60573.C new file mode 100644 index 000..2f60707 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/pr60573.C @@ -0,0 +1,28 @@ +// PR c++/60573 +// { dg-do compile { target c++1y } } +// { dg-options } + +struct A +{ + struct B + { +void foo(auto); + }; + + void B::foo(auto) {} // { dg-error cannot define } + + struct X + { +struct Y +{ + struct Z + { +void foo(auto); + }; +}; + +void Y::Z::foo(auto) {} // { dg-error cannot define } + }; + + void X::Y::Z::foo(auto) {} // { dg-error cannot define } +}; -- 1.9.0
Re: [PATCH] Fix PR c++/60573
On 2014-03-25 15:48, Jason Merrill wrote: On 03/18/2014 10:46 PM, Adam Butcher wrote: + if (TYPE_BEING_DEFINED (scope-this_entity)) + if (scope-level_chain == 0 + || scope-this_entity != scope-level_chain-this_entity) + break; I don't think this is an adequate test; if you have another class wrapping B, you'll have two levels of context pushed for the declarator, so the this_entities will compare unequal. I think we need some way to designate a scope that actually corresponds to a class-specifier. I don't follow. Are you suggesting a case like the following? struct A { struct X { struct B { void foo(auto); }; }; void X::B::foo(auto) {} // { dg-error cannot define } }; If so, it is handled. The code you are referring to is within a level_chain traversal loop so handles arbitrary levels of pushed context. The end result is the same though; we arrive at an 'A::' scope that is not the defining class scope but has the same 'this_entity' as it (so TYPE_BEING_DEFINED returns true). The scope chain is as if the following was used to declare it: struct A { ... void A::X::B::foo(auto); }; It is the two levels of 'A' entity scopes that caused the ICE due the previous version of the loop stopping to inject the template parameter list between the 'A::' and the 'X::', rather than unwinding back to the scope defining 'A'. Apologies if I've completely misunderstood your point here. I've got a feeling that I may have. Adam
Re: [PATCH] Fix PR c++/60573
On 03/18/2014 10:46 PM, Adam Butcher wrote: + if (TYPE_BEING_DEFINED (scope-this_entity)) + if (scope-level_chain == 0 + || scope-this_entity != scope-level_chain-this_entity) + break; I don't think this is an adequate test; if you have another class wrapping B, you'll have two levels of context pushed for the declarator, so the this_entities will compare unequal. I think we need some way to designate a scope that actually corresponds to a class-specifier. Jason
Re: [PATCH] Fix PR c++/60573
On 03/18/2014 10:46 PM, Adam Butcher wrote: - while (scope-kind == sk_class - !TYPE_BEING_DEFINED (scope-this_entity)) Does it work to just change TYPE_BEING_DEFINED to currently_open_class? Jason
Re: [PATCH] Fix PR c++/60573
On 2014-03-24 17:23, Jason Merrill wrote: On 03/18/2014 10:46 PM, Adam Butcher wrote: - while (scope-kind == sk_class - !TYPE_BEING_DEFINED (scope-this_entity)) Does it work to just change TYPE_BEING_DEFINED to currently_open_class? No. The object referred to by 'scope-this_entity' is the same for each of the two levels, so unless there's something else I can pass to 'currently_open_class', the result will be equivalent; the issue is that the class being referred to *is* being defined, but I need to skip over the second class-scope for that type to arrive at the defining scope level. Adam
[PATCH] Fix PR c++/60573
PR c++/60573 * parser.c (synthesize_implicit_template_parm): Handle the fact that nested class member declarations erroneously appearing in an enclosing class contain an addition scope level for the class being defined. PR c++/60573 * g++.dg/cpp1y/pr60573.C: New testcase. --- gcc/cp/parser.c | 18 -- gcc/testsuite/g++.dg/cpp1y/pr60573.C | 13 + 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1y/pr60573.C diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c index 46e2453..36872c9 100644 --- a/gcc/cp/parser.c +++ b/gcc/cp/parser.c @@ -32006,9 +32006,23 @@ synthesize_implicit_template_parm (cp_parser *parser) must be injected in the scope of 'B', just beyond the scope of 'A' introduced by 'A::'. */ - while (scope-kind == sk_class - !TYPE_BEING_DEFINED (scope-this_entity)) + while (scope-kind == sk_class) { + /* In valid cases where the class being defined is reached, we're +at the point where the template argument list should be +injected for a generic member function. In the erroneous case +of generic member function of a nested class being declared in +the enclosing class, an additional class scope for the +enclosing class has been pushed by push_nested_class via +push_scope in cp_parser_direct_declarator. This additional +scope needs to be skipped to reach the class definition scope +where the template argument list should be injected. */ + + if (TYPE_BEING_DEFINED (scope-this_entity)) + if (scope-level_chain == 0 + || scope-this_entity != scope-level_chain-this_entity) + break; + parent_scope = scope; scope = scope-level_chain; } diff --git a/gcc/testsuite/g++.dg/cpp1y/pr60573.C b/gcc/testsuite/g++.dg/cpp1y/pr60573.C new file mode 100644 index 000..7f56ff4 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/pr60573.C @@ -0,0 +1,13 @@ +// PR c++/60573 +// { dg-do compile { target c++1y } } +// { dg-options } + +struct A +{ + struct B + { +void foo(auto); + }; + + void B::foo(auto) {} // { dg-error cannot define } +}; -- 1.9.0