This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[C++ PATCH] Fix bug 775


Hi,
this fixes bug 775. It is rather more parser oriented than I would have
liked, but that's the way it went. It is also a prerequisite to at least
one other upcoming patch (2023). I suspect some other bugs are fixed
by this too.

parse.y has one way of handling class-head with a qualified id, and
another for unqualified-ids. The unqualified id path DTRT for declarations
and definitions, because it tells them appart. The qualified-id path
does not distinguish, leading to problems with 'friend class qualified-id'.

This rearranges the grammar into more obvious (IMO) non-terminals, which
delay the processing in handle_class_head, until it is known whether a
declaration or definition is happening. Also both qualified and unqualified
ids go through handle_class_head.

No new shift/reduce or reduce/reduce conflicts.

built & tested on i686-pc-linux-gnu, ok?

nathan
-- 
Dr Nathan Sidwell   ::   http://www.codesourcery.com   ::   CodeSourcery LLC
         'But that's a lie.' - 'Yes it is. What's your point?'
nathan@codesourcery.com : http://www.cs.bris.ac.uk/~nathan/ : nathan@acm.org
2001-12-28  Nathan Sidwell  <nathan@codesourcery.com>

	PR c++/775
	* cp-tree.h (handle_class_head): Adjust prototype.
	* decl2.c (handle_class_head): Add DEFN_P and NEW_TYPE_P
	parameters. Use for all class heads.
	* parse.y (named_class_head_sans_basetype, named_class_head,
	named_complex_class_head_sans_basetype,
	named_class_head_sans_basetype_defn,
	unnamed_class_head): Remove.
	(class_head, class_head_apparent_template): Recognize class heads
	(class_head_decl, class_head_defn): New reductions. Process class
	heads.
	(structsp): Adjust class definition and class declaration
	reductions.
	(maybe_base_class_list): Give diagnostic on empty list.

Index: cp/cp-tree.h
===================================================================
RCS file: /cvs/gcc/gcc/gcc/cp/cp-tree.h,v
retrieving revision 1.667
diff -c -3 -p -r1.667 cp-tree.h
*** cp-tree.h	2001/12/18 03:35:25	1.667
--- cp-tree.h	2001/12/28 23:58:24
*************** extern tree do_class_using_decl			PARAMS
*** 3769,3775 ****
  extern void do_using_directive			PARAMS ((tree));
  extern void check_default_args			PARAMS ((tree));
  extern void mark_used				PARAMS ((tree));
! extern tree handle_class_head			PARAMS ((tree, tree, tree));
  extern tree lookup_arg_dependent                PARAMS ((tree, tree, tree));
  extern void finish_static_data_member_decl      PARAMS ((tree, tree, tree, int));
  extern tree build_artificial_parm               PARAMS ((tree, tree));
--- 3769,3775 ----
  extern void do_using_directive			PARAMS ((tree));
  extern void check_default_args			PARAMS ((tree));
  extern void mark_used				PARAMS ((tree));
! extern tree handle_class_head			PARAMS ((tree, tree, tree, int, int *));
  extern tree lookup_arg_dependent                PARAMS ((tree, tree, tree));
  extern void finish_static_data_member_decl      PARAMS ((tree, tree, tree, int));
  extern tree build_artificial_parm               PARAMS ((tree, tree));
Index: cp/decl2.c
===================================================================
RCS file: /cvs/gcc/gcc/gcc/cp/decl2.c,v
retrieving revision 1.507
diff -c -3 -p -r1.507 decl2.c
*** decl2.c	2001/12/23 16:07:09	1.507
--- decl2.c	2001/12/28 23:59:15
*************** mark_used (decl)
*** 5158,5227 ****
      instantiate_decl (decl, /*defer_ok=*/1);
  }
  
! /* Helper function for named_class_head_sans_basetype nonterminal.  We
!    have just seen something of the form `AGGR SCOPE::ID'.  Return a
!    TYPE_DECL for the type declared by ID in SCOPE.  */
  
  tree
! handle_class_head (aggr, scope, id)
       tree aggr, scope, id;
  {
    tree decl = NULL_TREE;
! 
!   if (TREE_CODE (id) == TYPE_DECL)
!     /* We must bash typedefs back to the main decl of the type. Otherwise
!        we become confused about scopes.  */
!     decl = TYPE_MAIN_DECL (TREE_TYPE (id));
!   else if (DECL_CLASS_TEMPLATE_P (id))
!     decl = DECL_TEMPLATE_RESULT (id);
!   else 
!     {
!       tree current = current_scope ();
    
!       if (current == NULL_TREE)
!         current = current_namespace;
!       if (scope == NULL_TREE)
!         scope = global_namespace;
  
!       if (TYPE_P (scope))
  	{
! 	  /* According to the suggested resolution of core issue 180,
! 	     'typename' is assumed after a class-key.  */
! 	  decl = make_typename_type (scope, id, 1);
! 	  if (decl != error_mark_node)
! 	    decl = TYPE_MAIN_DECL (decl);
  	  else
! 	    decl = NULL_TREE;
  	}
-       else if (scope == current)
-         {
-           /* We've been given AGGR SCOPE::ID, when we're already inside SCOPE.
-              Be nice about it.  */
-           if (pedantic)
-             pedwarn ("extra qualification `%T::' on member `%D' ignored",
-                         FROB_CONTEXT (scope), id);
-         }
-       else if (scope != global_namespace)
- 	error ("`%T' does not have a nested type named `%D'", scope, id);
-       else
- 	error ("no file-scope type named `%D'", id);
-       
-       /* Inject it at the current scope.  */
-       if (! decl)
- 	decl = TYPE_MAIN_DECL (xref_tag (aggr, id, 1));
      }
!  
!   /* Enter the SCOPE.  If this turns out not to be a definition, the
!      parser must leave the scope.  */
!   push_scope (CP_DECL_CONTEXT (decl));
  
!   /* If we see something like:
  
!        template <typename T> struct S::I ....
!        
!      we must create a TEMPLATE_DECL for the nested type.  */
!   if (PROCESSING_REAL_TEMPLATE_DECL_P ())
!     decl = push_template_decl (decl);
  
    return decl;
  }
--- 5158,5247 ----
      instantiate_decl (decl, /*defer_ok=*/1);
  }
  
! /* Helper function for class_head_decl and class_head_defn
!    nonterminals. AGGR is the class, union or struct tag. SCOPE is the
!    explicit scope used (NULL for no scope resolution). ID is the
!    name. DEFN_P is true, if this is a definition of the class and
!    NEW_TYPE_P is set to non-zero, if we push into the scope containing
!    the to be defined aggregate.
!    
!    Return a TYPE_DECL for the type declared by ID in SCOPE.  */
  
  tree
! handle_class_head (aggr, scope, id, defn_p, new_type_p)
       tree aggr, scope, id;
+      int defn_p;
+      int *new_type_p;
  {
    tree decl = NULL_TREE;
!   tree current = current_scope ();
!   bool xrefd_p = false;
    
!   if (current == NULL_TREE)
!     current = current_namespace;
  
!   *new_type_p = 0;
!   
!   if (scope)
!     {
!       if (TREE_CODE (id) == TYPE_DECL)
! 	/* We must bash typedefs back to the main decl of the
!        	   type. Otherwise we become confused about scopes.  */
! 	decl = TYPE_MAIN_DECL (TREE_TYPE (id));
!       else if (DECL_CLASS_TEMPLATE_P (id))
! 	decl = DECL_TEMPLATE_RESULT (id);
!       else
  	{
! 	  if (TYPE_P (scope))
! 	    {
! 	      /* According to the suggested resolution of core issue
! 	     	 180, 'typename' is assumed after a class-key.  */
! 	      decl = make_typename_type (scope, id, 1);
! 	      if (decl != error_mark_node)
! 		decl = TYPE_MAIN_DECL (decl);
! 	      else
! 		decl = NULL_TREE;
! 	    }
! 	  else if (scope == current)
! 	    {
! 	      /* We've been given AGGR SCOPE::ID, when we're already
!              	 inside SCOPE.  Be nice about it.  */
! 	      if (pedantic)
! 		pedwarn ("extra qualification `%T::' on member `%D' ignored",
! 			 scope, id);
! 	    }
  	  else
! 	    error ("`%T' does not have a class or union named `%D'",
! 		   scope, id);
  	}
      }
!   
!   if (!decl)
!     {
!       decl = TYPE_MAIN_DECL (xref_tag (aggr, id, !defn_p));
!       xrefd_p = true;
!     }
  
!   if (!TYPE_BINFO (TREE_TYPE (decl)))
!     {
!       error ("`%T' is not a class or union type", decl);
!       return error_mark_node;
!     }
!   
!   if (defn_p)
!     {
!       /* For a definition, we want to enter the containing scope
! 	 before looking up any base classes etc. Only do so, if this
! 	 is different to the current scope.  */
!       tree context = CP_DECL_CONTEXT (decl);
  
!       *new_type_p = current != context;
!       if (*new_type_p)
! 	push_scope (context);
!   
!       if (!xrefd_p && PROCESSING_REAL_TEMPLATE_DECL_P ())
! 	decl = push_template_decl (decl);
!     }
  
    return decl;
  }
Index: cp/parse.y
===================================================================
RCS file: /cvs/gcc/gcc/gcc/cp/parse.y,v
retrieving revision 1.239
diff -c -3 -p -r1.239 parse.y
*** parse.y	2001/12/26 20:33:36	1.239
--- parse.y	2001/12/28 23:59:49
*************** cp_parse_init ()
*** 386,395 ****
  %type <ttype> component_constructor_declarator
  %type <ttype> fn.def2 return_id constructor_declarator
  %type <ttype> .begin_function_body
! %type <ttype> named_class_head_sans_basetype
! %type <ftype> class_head named_class_head 
! %type <ftype> named_complex_class_head_sans_basetype 
! %type <ttype> unnamed_class_head
  %type <ttype> base_class_list
  %type <ttype> base_class_access_list
  %type <ttype> base_class maybe_base_class_list base_class.1
--- 386,393 ----
  %type <ttype> component_constructor_declarator
  %type <ttype> fn.def2 return_id constructor_declarator
  %type <ttype> .begin_function_body
! %type <ttype> class_head class_head_apparent_template
! %type <ftype> class_head_decl class_head_defn
  %type <ttype> base_class_list
  %type <ttype> base_class_access_list
  %type <ttype> base_class maybe_base_class_list base_class.1
*************** cp_parse_init ()
*** 418,424 ****
  %type <ttype> explicit_template_type
  /* in order to recognize aggr tags as defining and thus shadowing.  */
  %token TYPENAME_DEFN IDENTIFIER_DEFN PTYPENAME_DEFN
- %type <ttype> named_class_head_sans_basetype_defn
  %type <ttype> identifier_defn IDENTIFIER_DEFN TYPENAME_DEFN PTYPENAME_DEFN
  %type <ttype> handler_args
  %type <ttype> self_template_type .finish_template_type
--- 416,421 ----
*************** structsp:
*** 2277,2284 ****
  		  if (!processing_template_decl)
  		    pedwarn ("using `typename' outside of template"); }
  	/* C++ extensions, merged with C to avoid shift/reduce conflicts */
! 	| class_head '{'
!                 { $1.t = begin_class_definition ($1.t); 
                    current_aggr = NULL_TREE; }
            opt.component_decl_list '}' maybe_attribute
  		{ 
--- 2274,2294 ----
  		  if (!processing_template_decl)
  		    pedwarn ("using `typename' outside of template"); }
  	/* C++ extensions, merged with C to avoid shift/reduce conflicts */
! 	| class_head_defn maybe_base_class_list '{'
! 		{
! 		  if ($2 && $1.t != error_mark_node)
! 		    {
! 		      tree type = TREE_TYPE ($1.t);
! 		  
! 		      if (TREE_CODE (type) == TYPENAME_TYPE)
! 			/* In a definition of a member class template,
!                            we will get here with an implicit typename,
!                            a TYPENAME_TYPE with a type. */
! 			type = TREE_TYPE (type);
! 		      maybe_process_partial_specialization (type);
! 		      xref_basetypes (current_aggr, $1.t, type, $2);
! 		    }
! 		  $1.t = begin_class_definition (TREE_TYPE ($1.t)); 
                    current_aggr = NULL_TREE; }
            opt.component_decl_list '}' maybe_attribute
  		{ 
*************** structsp:
*** 2289,2296 ****
  		    yychar = YYLEX;
  		  semi = yychar == ';';
  
! 		  t = finish_class_definition ($1.t, $6, semi,
! 					       $1.new_type_flag); 
  		  $<ttype>$ = t;
  
  		  /* restore current_aggr */
--- 2299,2305 ----
  		    yychar = YYLEX;
  		  semi = yychar == ';';
  
! 		  t = finish_class_definition ($1.t, $7, semi, $1.new_type_flag);
  		  $<ttype>$ = t;
  
  		  /* restore current_aggr */
*************** structsp:
*** 2307,2338 ****
  	  pending_inlines
                  {
  		  finish_inline_definitions ();
! 		  $$.t = $<ttype>7;
  		  $$.new_type_flag = 1; 
  		}
! 	| class_head  %prec EMPTY
  		{
! 		  if ($1.new_type_flag && $1.t != error_mark_node)
! 		    pop_scope (CP_DECL_CONTEXT (TYPE_MAIN_DECL ($1.t)));
! 		  $$.new_type_flag = 0;
! 		  if ($1.t == error_mark_node)
! 		    $$.t = $1.t;
! 		  else if (TYPE_BINFO ($1.t) == NULL_TREE)
! 		    {
! 		      error ("%T is not a class type", $1.t);
! 		      $$.t = error_mark_node;
! 		    } 
! 		  else
! 		    {
! 		      $$.t = $1.t;
! 		      /* struct B: public A; is not accepted by the standard grammar.  */
! 		      if (CLASS_TYPE_P ($$.t)
! 			  && TYPE_BINFO_BASETYPES ($$.t) 
! 			  && !COMPLETE_TYPE_P ($$.t)
! 			  && ! TYPE_BEING_DEFINED ($$.t))
! 			error ("base clause without member specification for `%#T'",
! 				  $$.t);
! 		    }
  		}
  	;
  
--- 2316,2328 ----
  	  pending_inlines
                  {
  		  finish_inline_definitions ();
! 		  $$.t = $<ttype>8;
  		  $$.new_type_flag = 1; 
  		}
! 	| class_head_decl
  		{
! 		  $$.t = TREE_TYPE ($1.t);
! 		  $$.new_type_flag = $1.new_type_flag;
  		}
  	;
  
*************** aggr:
*** 2362,2501 ****
  		{ $$ = build_tree_list ($2, $1); }
  	;
  
! named_class_head_sans_basetype:
  	  aggr identifier
! 		{ 
! 		  current_aggr = $1; 
! 		  $$ = $2; 
  		}
! 	;
! 
! named_class_head_sans_basetype_defn:
! 	  aggr identifier_defn  %prec EMPTY
! 		{ current_aggr = $$; $$ = $2; }
! 	| named_class_head_sans_basetype '{'
! 		{ yyungetc ('{', 1); }
! 	| named_class_head_sans_basetype ':'
! 		{ yyungetc (':', 1); }
! 	;
! 
! named_complex_class_head_sans_basetype:
! 	  aggr nested_name_specifier identifier
  		{
  		  current_aggr = $1;
! 		  $$.t = handle_class_head ($1, $2, $3);
! 		  $$.new_type_flag = 1;
  		}
  	| aggr global_scope nested_name_specifier identifier
  		{
  		  current_aggr = $1;
! 		  $$.t = handle_class_head ($1, $3, $4);
! 		  $$.new_type_flag = 1;
  		}
  	| aggr global_scope identifier
  		{
  		  current_aggr = $1;
! 		  $$.t = handle_class_head ($1, NULL_TREE, $3);
! 		  $$.new_type_flag = 1;
  		}
! 	| aggr apparent_template_type
  		{ 
  		  current_aggr = $1; 
! 		  $$.t = $2;
! 		  $$.new_type_flag = 0;
  		}
  	| aggr nested_name_specifier apparent_template_type
  		{ 
  		  current_aggr = $1; 
! 		  $$.t = $3;
! 		  push_scope (CP_DECL_CONTEXT ($$.t));
! 		  $$.new_type_flag = 1;
  		}
  	| aggr global_scope nested_name_specifier apparent_template_type
  		{ 
  		  current_aggr = $1; 
! 		  $$.t = $4;
! 		  push_scope (CP_DECL_CONTEXT ($$.t));
! 		  $$.new_type_flag = 1;
  		}
  	;
  
! named_class_head:
! 	  named_class_head_sans_basetype  %prec EMPTY
! 		{ 
! 		  $$.t = xref_tag (current_aggr, $1, 1); 
! 		  $$.new_type_flag = 0;
  		}
! 	| named_class_head_sans_basetype_defn 
!                 { $<ttype>$ = xref_tag (current_aggr, $1, 0); }
!           /* Class name is unqualified, so we look for base classes
!              in the current scope.  */
!           maybe_base_class_list  %prec EMPTY
! 		{ 
! 		  $$.t = $<ttype>2;
! 		  $$.new_type_flag = 0;
! 		  if ($3)
!                     xref_basetypes (current_aggr, $1, $<ttype>2, $3); 
  		}
! 	| named_complex_class_head_sans_basetype 
! 	  maybe_base_class_list
! 		{ 
! 		  if ($1.t != error_mark_node)
! 		    {
! 		      tree type = TREE_TYPE ($1.t);
! 
! 		      $$.t = type;
! 		      $$.new_type_flag = $1.new_type_flag;
! 		      if ((current_aggr == union_type_node)
! 			  != (TREE_CODE (type) == UNION_TYPE))
! 			pedwarn (current_aggr == union_type_node
! 	                            ? "`union' tag used in declaring `%#T'"
! 	                            : "non-`union' tag used in declaring `%#T'", 
! 				    type);
! 		      else if (TREE_CODE (type) == RECORD_TYPE)
! 			/* We might be specializing a template with a different
! 			   class-key; deal.  */
! 			CLASSTYPE_DECLARED_CLASS (type) 
! 			  = (current_aggr == class_type_node);
! 		      if ($2)
! 			{
!                           if (TREE_CODE (type) == TYPENAME_TYPE)
!                             /* In a definition of a member class template, we
!                                will get here with an implicit typename, a
!                                TYPENAME_TYPE with a type. */
!                             type = TREE_TYPE (type);
! 			  maybe_process_partial_specialization (type);
! 			  xref_basetypes (current_aggr, $1.t, type, $2); 
! 			}
! 		    }
  		}
  	;
- 
- unnamed_class_head:
- 	  aggr '{'
- 		{ $$ = xref_tag ($$, make_anon_name (), 0);
- 		  yyungetc ('{', 1); }
- 	;
  
! /* The tree output of this nonterminal a declarationf or the type
!    named.  If NEW_TYPE_FLAG is set, then the name used in this
!    class-head was explicitly qualified, e.g.:  `struct X::Y'.  We have
!    already called push_scope for X.  */
! class_head:
! 	  unnamed_class_head
!                 {
  		  $$.t = $1;
  		  $$.new_type_flag = 0;
  		}
- 	| named_class_head
  	;
  
  maybe_base_class_list:
! 	  /* empty */  %prec EMPTY
  		{ $$ = NULL_TREE; }
! 	| ':' see_typename  %prec EMPTY
! 		{ yyungetc(':', 1); $$ = NULL_TREE; }
! 	| ':' see_typename base_class_list  %prec EMPTY
  		{ $$ = $3; }
  	;
  
--- 2352,2477 ----
  		{ $$ = build_tree_list ($2, $1); }
  	;
  
! class_head:
  	  aggr identifier
! 		{
! 		  current_aggr = $1;
! 		  $$ = build_tree_list (NULL_TREE, $2);
  		}
! 	| aggr nested_name_specifier identifier
  		{
  		  current_aggr = $1;
! 		  $$ = build_tree_list ($2, $3);
  		}
  	| aggr global_scope nested_name_specifier identifier
  		{
  		  current_aggr = $1;
! 		  $$ = build_tree_list ($3, $4);
  		}
  	| aggr global_scope identifier
  		{
  		  current_aggr = $1;
! 		  $$ = build_tree_list (global_namespace, $3);
  		}
! 	;
! 
! class_head_apparent_template:
! 	  aggr apparent_template_type
  		{ 
  		  current_aggr = $1; 
! 		  $$ = $2;
  		}
  	| aggr nested_name_specifier apparent_template_type
  		{ 
  		  current_aggr = $1; 
! 		  $$ = $3;
  		}
  	| aggr global_scope nested_name_specifier apparent_template_type
  		{ 
  		  current_aggr = $1; 
! 		  $$ = $4;
  		}
  	;
  
! class_head_decl:
! 	  class_head %prec EMPTY
! 		{
! 		  $$.t = handle_class_head (current_aggr,
! 					    TREE_PURPOSE ($1), TREE_VALUE ($1),
! 					    0, &$$.new_type_flag);
  		}
! 	| aggr identifier_defn %prec EMPTY
! 		{
! 		  current_aggr = $1;
! 		  $$.t = TYPE_MAIN_DECL (xref_tag (current_aggr, $2, 0));
! 		  $$.new_type_flag = 1;
  		}
! 	| class_head_apparent_template %prec EMPTY
! 		{
! 		  $$.t = $1;
! 		  $$.new_type_flag = 0;
  		}
  	;
  
! class_head_defn:
! 	  class_head '{'
! 		{
! 		  yyungetc ('{', 1);
! 		  $$.t = handle_class_head (current_aggr,
! 					    TREE_PURPOSE ($1), TREE_VALUE ($1),
! 					    1, &$$.new_type_flag);
! 		}
! 	| class_head ':'
! 		{
! 		  yyungetc (':', 1);
! 		  $$.t = handle_class_head (current_aggr,
! 					    TREE_PURPOSE ($1), TREE_VALUE ($1),
! 					    1, &$$.new_type_flag);
! 		}
! 	| class_head_apparent_template '{'
! 		{
! 		  yyungetc ('{', 1);
! 		  $$.t = $1;
! 		  $$.new_type_flag = 0;
! 		}
! 	| class_head_apparent_template ':'
! 		{
! 		  yyungetc (':', 1);
  		  $$.t = $1;
  		  $$.new_type_flag = 0;
+ 		}
+ 	| aggr identifier_defn '{'
+ 		{
+ 		  yyungetc ('{', 1);
+ 		  current_aggr = $1;
+ 		  $$.t = handle_class_head (current_aggr,
+ 					    NULL_TREE, $2,
+ 					    1, &$$.new_type_flag);
+ 		}
+ 	| aggr identifier_defn ':'
+ 		{
+ 		  yyungetc (':', 1);
+ 		  current_aggr = $1;
+ 		  $$.t = handle_class_head (current_aggr,
+ 					    NULL_TREE, $2,
+ 					    1, &$$.new_type_flag);
+ 		}
+         | aggr '{'
+ 		{
+ 		  current_aggr = $1;
+ 		  $$.t = TYPE_MAIN_DECL (xref_tag ($1, make_anon_name (), 0));
+ 		  $$.new_type_flag = 0;
+ 		  yyungetc ('{', 1);
  		}
  	;
  
  maybe_base_class_list:
! 	  /* empty */
  		{ $$ = NULL_TREE; }
! 	| ':' see_typename
! 		{ error ("no bases given following `:'");
! 		  $$ = NULL_TREE; }
! 	| ':' see_typename base_class_list
  		{ $$ = $3; }
  	;
  
Index: testsuite/g++.old-deja/g++.brendan/crash8.C
===================================================================
RCS file: /cvs/gcc/gcc/gcc/testsuite/g++.old-deja/g++.brendan/crash8.C,v
retrieving revision 1.5
diff -c -3 -p -r1.5 crash8.C
*** crash8.C	2000/09/07 00:37:14	1.5
--- crash8.C	2001/12/28 23:59:49
***************
*** 1,8 ****
  // Build don't link: 
  // GROUPS passed old-abort
  template<int a, int b>
! class Elvis // ERROR - in template.*
! {
  } ;
  
  template<int a>
--- 1,8 ----
  // Build don't link: 
  // GROUPS passed old-abort
  template<int a, int b>
! class Elvis
! { // ERROR - in template.*
  } ;
  
  template<int a>
// { dg-do compile }

// Copyright (C) 2001 Free Software Foundation, Inc.
// Contributed by Nathan Sidwell 28 Dec 2001 <nathan@nathan@codesourcery.com>

// PR 775 friend classes with qualified names inside template classes.

struct A
{
  struct B {
    B () { }
  };
};

template <class T>
struct C: A {
  friend A::B::B (); // 2.95.2 ICE
  friend struct A;
  friend struct A::B; // 2.97 error
};

template class C<char>;

template <typename T> class TPL
{
  class nested;
};

template <typename T> class TPL<T>::nested 
{
};

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]