cp-tree.h (PROCESSING_REAL_TEMPLATE_DECL_P): New macro.
1998-07-31 Mark Mitchell <mark@markmitchell.com> * cp-tree.h (PROCESSING_REAL_TEMPLATE_DECL_P): New macro. (maybe_check_template_type): New function. * decl.c (maybe_process_template_type_declaration): New function, split out from pushtag Call maybe_check_template_type. (pushtag): Use it. Use PROCESSING_REAL_TEMPLATE_DECL_P. (xref_tag): Use PROCESSING_REAL_TEMPLATE_DECL_P. * friend.c (do_friend): Use PROCESSING_REAL_TEMPLATE_DECL_P. * pt.c (template_class_depth_real): Generalization of ... (template_class_depth): Use it. (register_specialization): Use duplicate_decls for duplicate declarations of specializations. (maybe_check_template_type): New function. (push_template_decl_real): Fix comment. (convert_nontype_argument): Likewise. (lookup_template_class): Likewise. Avoid an infinite loop on erroneous code. (tsubst_friend_function): Fix comment. (tsubst, case FUNCTION_DECL): Deal with a DECL_TI_TEMPLATE that is an IDENTIFIER_NODE. * semantics.c (begin_function_definition): Use reset_specialization to note that template headers don't apply directly to declarations after the opening curly for a function. From-SVN: r21505
This commit is contained in:
parent
5f97de0ac9
commit
39c01e4c53
@ -1,3 +1,28 @@
|
||||
1998-07-31 Mark Mitchell <mark@markmitchell.com>
|
||||
|
||||
* cp-tree.h (PROCESSING_REAL_TEMPLATE_DECL_P): New macro.
|
||||
(maybe_check_template_type): New function.
|
||||
* decl.c (maybe_process_template_type_declaration): New function,
|
||||
split out from pushtag Call maybe_check_template_type.
|
||||
(pushtag): Use it. Use PROCESSING_REAL_TEMPLATE_DECL_P.
|
||||
(xref_tag): Use PROCESSING_REAL_TEMPLATE_DECL_P.
|
||||
* friend.c (do_friend): Use PROCESSING_REAL_TEMPLATE_DECL_P.
|
||||
* pt.c (template_class_depth_real): Generalization of ...
|
||||
(template_class_depth): Use it.
|
||||
(register_specialization): Use duplicate_decls for duplicate
|
||||
declarations of specializations.
|
||||
(maybe_check_template_type): New function.
|
||||
(push_template_decl_real): Fix comment.
|
||||
(convert_nontype_argument): Likewise.
|
||||
(lookup_template_class): Likewise. Avoid an infinite loop on
|
||||
erroneous code.
|
||||
(tsubst_friend_function): Fix comment.
|
||||
(tsubst, case FUNCTION_DECL): Deal with a DECL_TI_TEMPLATE that is
|
||||
an IDENTIFIER_NODE.
|
||||
* semantics.c (begin_function_definition): Use
|
||||
reset_specialization to note that template headers don't apply
|
||||
directly to declarations after the opening curly for a function.
|
||||
|
||||
1998-07-29 Jason Merrill <jason@yorick.cygnus.com>
|
||||
|
||||
* decl.c (push_overloaded_decl): Use current_namespace instead of
|
||||
|
@ -1692,6 +1692,12 @@ extern int flag_new_for_scope;
|
||||
#define SET_CLASSTYPE_EXPLICIT_INSTANTIATION(NODE) \
|
||||
(CLASSTYPE_USE_TEMPLATE(NODE) = 3)
|
||||
|
||||
/* Non-zero iff we are currently processing a declaration for an
|
||||
entity with its own template parameter list, and which is not a
|
||||
full specialization. */
|
||||
#define PROCESSING_REAL_TEMPLATE_DECL_P() \
|
||||
(processing_template_decl > template_class_depth (current_class_type))
|
||||
|
||||
/* This function may be a guiding decl for a template. */
|
||||
#define DECL_MAYBE_TEMPLATE(NODE) DECL_LANG_FLAG_4 (NODE)
|
||||
/* We know what we're doing with this decl now. */
|
||||
@ -2794,6 +2800,7 @@ extern int template_class_depth PROTO((tree));
|
||||
extern int is_specialization_of PROTO((tree, tree));
|
||||
extern int comp_template_args PROTO((tree, tree));
|
||||
extern void maybe_process_partial_specialization PROTO((tree));
|
||||
extern void maybe_check_template_type PROTO((tree));
|
||||
|
||||
extern int processing_specialization;
|
||||
extern int processing_explicit_instantiation;
|
||||
|
145
gcc/cp/decl.c
145
gcc/cp/decl.c
@ -176,6 +176,7 @@ static int member_function_or_else PROTO((tree, tree, char *));
|
||||
static void bad_specifiers PROTO((tree, char *, int, int, int, int,
|
||||
int));
|
||||
static void lang_print_error_function PROTO((char *));
|
||||
static tree maybe_process_template_type_declaration PROTO((tree, int, struct binding_level*));
|
||||
|
||||
#if defined (DEBUG_CP_BINDING_LEVELS)
|
||||
static void indent PROTO((void));
|
||||
@ -2223,6 +2224,88 @@ pop_everything ()
|
||||
#endif
|
||||
}
|
||||
|
||||
/* The type TYPE is being declared. If it is a class template, or a
|
||||
specialization of a class template, do any processing required and
|
||||
perform error-checking. If IS_FRIEND is non-zero, this TYPE is
|
||||
being declared a friend. B is the binding level at which this TYPE
|
||||
should be bound.
|
||||
|
||||
Returns the TYPE_DECL for TYPE, which may have been altered by this
|
||||
processing. */
|
||||
|
||||
static tree
|
||||
maybe_process_template_type_declaration (type, globalize, b)
|
||||
tree type;
|
||||
int globalize;
|
||||
struct binding_level* b;
|
||||
{
|
||||
tree decl = TYPE_NAME (type);
|
||||
|
||||
if (processing_template_parmlist)
|
||||
/* You can't declare a new template type in a template parameter
|
||||
list. But, you can declare a non-template type:
|
||||
|
||||
template <class A*> struct S;
|
||||
|
||||
is a forward-declaration of `A'. */
|
||||
;
|
||||
else
|
||||
{
|
||||
maybe_check_template_type (type);
|
||||
|
||||
if (IS_AGGR_TYPE (type)
|
||||
&& (/* If !GLOBALIZE then we are looking at a definition.
|
||||
It may not be a primary template. (For example, in:
|
||||
|
||||
template <class T>
|
||||
struct S1 { class S2 {}; }
|
||||
|
||||
we have to push_template_decl for S2.) */
|
||||
(processing_template_decl && !globalize)
|
||||
/* If we are declaring a friend template class, we will
|
||||
have GLOBALIZE set, since something like:
|
||||
|
||||
template <class T>
|
||||
struct S1 {
|
||||
template <class U>
|
||||
friend class S2;
|
||||
};
|
||||
|
||||
declares S2 to be at global scope. */
|
||||
|| PROCESSING_REAL_TEMPLATE_DECL_P ()))
|
||||
{
|
||||
/* This may change after the call to
|
||||
push_template_decl_real, but we want the original value. */
|
||||
tree name = DECL_NAME (decl);
|
||||
|
||||
decl = push_template_decl_real (decl, globalize);
|
||||
/* If the current binding level is the binding level for the
|
||||
template parameters (see the comment in
|
||||
begin_template_parm_list) and the enclosing level is a class
|
||||
scope, and we're not looking at a friend, push the
|
||||
declaration of the member class into the class scope. In the
|
||||
friend case, push_template_decl will already have put the
|
||||
friend into global scope, if appropriate. */
|
||||
if (!globalize && b->pseudo_global
|
||||
&& b->level_chain->parm_flag == 2)
|
||||
{
|
||||
pushdecl_with_scope (CLASSTYPE_TI_TEMPLATE (type),
|
||||
b->level_chain);
|
||||
/* Put this tag on the list of tags for the class, since
|
||||
that won't happen below because B is not the class
|
||||
binding level, but is instead the pseudo-global level. */
|
||||
b->level_chain->tags =
|
||||
saveable_tree_cons (name, type, b->level_chain->tags);
|
||||
TREE_NONLOCAL_FLAG (type) = 1;
|
||||
if (TYPE_SIZE (current_class_type) == NULL_TREE)
|
||||
CLASSTYPE_TAGS (current_class_type) = b->level_chain->tags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decl;
|
||||
}
|
||||
|
||||
/* Push a tag name NAME for struct/class/union/enum type TYPE.
|
||||
Normally put it into the inner-most non-tag-transparent scope,
|
||||
but if GLOBALIZE is true, put it in the inner-most non-class scope.
|
||||
@ -2298,63 +2381,8 @@ pushtag (name, type, globalize)
|
||||
TYPE_NAME (type) = d;
|
||||
DECL_CONTEXT (d) = FROB_CONTEXT (context);
|
||||
|
||||
if (processing_template_parmlist)
|
||||
/* You can't declare a new template type in a template
|
||||
parameter list. But, you can declare a non-template
|
||||
type:
|
||||
|
||||
template <class A*> struct S;
|
||||
|
||||
is a forward-declaration of `A'. */
|
||||
;
|
||||
else if (IS_AGGR_TYPE (type)
|
||||
&& (/* If !GLOBALIZE then we are looking at a
|
||||
definition. It may not be a primary template.
|
||||
(For example, in:
|
||||
|
||||
template <class T>
|
||||
struct S1 { class S2 {}; }
|
||||
|
||||
we have to push_template_decl for S2.) */
|
||||
(processing_template_decl && !globalize)
|
||||
/* If we are declaring a friend template class, we
|
||||
will have GLOBALIZE set, since something like:
|
||||
|
||||
template <class T>
|
||||
struct S1 {
|
||||
template <class U>
|
||||
friend class S2;
|
||||
};
|
||||
|
||||
declares S2 to be at global scope. */
|
||||
|| (processing_template_decl >
|
||||
template_class_depth (current_class_type))))
|
||||
{
|
||||
d = push_template_decl_real (d, globalize);
|
||||
/* If the current binding level is the binding level for
|
||||
the template parameters (see the comment in
|
||||
begin_template_parm_list) and the enclosing level is
|
||||
a class scope, and we're not looking at a friend,
|
||||
push the declaration of the member class into the
|
||||
class scope. In the friend case, push_template_decl
|
||||
will already have put the friend into global scope,
|
||||
if appropriate. */
|
||||
if (!globalize && b->pseudo_global
|
||||
&& b->level_chain->parm_flag == 2)
|
||||
{
|
||||
pushdecl_with_scope (CLASSTYPE_TI_TEMPLATE (type),
|
||||
b->level_chain);
|
||||
/* Put this tag on the list of tags for the class,
|
||||
since that won't happen below because B is not
|
||||
the class binding level, but is instead the
|
||||
pseudo-global level. */
|
||||
b->level_chain->tags =
|
||||
saveable_tree_cons (name, type, b->level_chain->tags);
|
||||
TREE_NONLOCAL_FLAG (type) = 1;
|
||||
if (TYPE_SIZE (current_class_type) == NULL_TREE)
|
||||
CLASSTYPE_TAGS (current_class_type) = b->level_chain->tags;
|
||||
}
|
||||
}
|
||||
d = maybe_process_template_type_declaration (type,
|
||||
globalize, b);
|
||||
|
||||
if (b->parm_flag == 2)
|
||||
d = pushdecl_class_level (d);
|
||||
@ -11349,8 +11377,7 @@ xref_tag (code_type_node, name, binfo, globalize)
|
||||
{
|
||||
if (current_class_type
|
||||
&& template_class_depth (current_class_type)
|
||||
&& (processing_template_decl
|
||||
> template_class_depth (current_class_type)))
|
||||
&& PROCESSING_REAL_TEMPLATE_DECL_P ())
|
||||
/* Since GLOBALIZE is non-zero, we are not looking at a
|
||||
definition of this tag. Since, in addition, we are currently
|
||||
processing a (member) template declaration of a template
|
||||
|
@ -354,8 +354,7 @@ do_friend (ctype, declarator, decl, parmdecls, flags, quals, funcdef_flag)
|
||||
}
|
||||
|
||||
if (TREE_CODE (decl) == FUNCTION_DECL)
|
||||
is_friend_template = processing_template_decl >
|
||||
template_class_depth (current_class_type);
|
||||
is_friend_template = PROCESSING_REAL_TEMPLATE_DECL_P ();
|
||||
|
||||
if (ctype)
|
||||
{
|
||||
|
134
gcc/cp/pt.c
134
gcc/cp/pt.c
@ -121,6 +121,7 @@ static tree most_specialized PROTO((tree, tree, tree));
|
||||
static tree most_specialized_class PROTO((tree, tree));
|
||||
static tree most_general_template PROTO((tree));
|
||||
static void set_mangled_name_for_template_decl PROTO((tree));
|
||||
static int template_class_depth_real PROTO((tree, int));
|
||||
|
||||
/* We use TREE_VECs to hold template arguments. If there is only one
|
||||
level of template arguments, then the TREE_VEC contains the
|
||||
@ -234,12 +235,19 @@ finish_member_template_decl (template_parameters, decl)
|
||||
struct B {};
|
||||
};
|
||||
|
||||
A<T>::B<U> has depth two, while A<T> has depth one. Also,
|
||||
both A<T>::B<int> and A<int>::B<U> have depth one. */
|
||||
A<T>::B<U> has depth two, while A<T> has depth one.
|
||||
Both A<T>::B<int> and A<int>::B<U> have depth one, if
|
||||
COUNT_SPECIALIZATIONS is 0 or if they are instantiations, not
|
||||
specializations.
|
||||
|
||||
This function is guaranteed to return 0 if passed NULL_TREE so
|
||||
that, for example, `template_class_depth (current_class_type)' is
|
||||
always safe. */
|
||||
|
||||
int
|
||||
template_class_depth (type)
|
||||
template_class_depth_real (type, count_specializations)
|
||||
tree type;
|
||||
int count_specializations;
|
||||
{
|
||||
int depth;
|
||||
|
||||
@ -249,12 +257,25 @@ template_class_depth (type)
|
||||
type = TYPE_CONTEXT (type))
|
||||
if (CLASSTYPE_TEMPLATE_INFO (type)
|
||||
&& PRIMARY_TEMPLATE_P (CLASSTYPE_TI_TEMPLATE (type))
|
||||
&& uses_template_parms (CLASSTYPE_TI_ARGS (type)))
|
||||
&& ((count_specializations
|
||||
&& CLASSTYPE_TEMPLATE_SPECIALIZATION (type))
|
||||
|| uses_template_parms (CLASSTYPE_TI_ARGS (type))))
|
||||
++depth;
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
/* Returns the template nesting level of the indicated class TYPE.
|
||||
Like template_class_depth_real, but instantiations do not count in
|
||||
the depth. */
|
||||
|
||||
int
|
||||
template_class_depth (type)
|
||||
tree type;
|
||||
{
|
||||
return template_class_depth_real (type, /*count_specializations=*/0);
|
||||
}
|
||||
|
||||
/* Returns 1 if processing DECL as part of do_pending_inlines
|
||||
needs us to push template parms. */
|
||||
|
||||
@ -742,11 +763,8 @@ register_specialization (spec, tmpl, args)
|
||||
}
|
||||
else if (DECL_TEMPLATE_SPECIALIZATION (fn))
|
||||
{
|
||||
if (DECL_INITIAL (fn))
|
||||
cp_error ("duplicate specialization of %D", fn);
|
||||
|
||||
TREE_VALUE (s) = spec;
|
||||
return spec;
|
||||
duplicate_decls (spec, TREE_VALUE (s));
|
||||
return TREE_VALUE (s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1300,6 +1318,50 @@ check_explicit_specialization (declarator, decl, template_count, flags)
|
||||
return decl;
|
||||
}
|
||||
|
||||
/* TYPE is being declared. Verify that the use of template headers
|
||||
and such is reasonable. Issue error messages if not. */
|
||||
|
||||
void
|
||||
maybe_check_template_type (type)
|
||||
tree type;
|
||||
{
|
||||
if (template_header_count)
|
||||
{
|
||||
/* We are in the scope of some `template <...>' header. */
|
||||
|
||||
int context_depth
|
||||
= template_class_depth_real (TYPE_CONTEXT (type),
|
||||
/*count_specializations=*/1);
|
||||
|
||||
if (template_header_count <= context_depth)
|
||||
/* This is OK; the template headers are for the context. We
|
||||
are actually too lenient here; like
|
||||
check_explicit_specialization we should consider the number
|
||||
of template types included in the actual declaration. For
|
||||
example,
|
||||
|
||||
template <class T> struct S {
|
||||
template <class U> template <class V>
|
||||
struct I {};
|
||||
};
|
||||
|
||||
is illegal, but:
|
||||
|
||||
template <class T> struct S {
|
||||
template <class U> struct I;
|
||||
};
|
||||
|
||||
template <class T> template <class U.
|
||||
struct S<T>::I {};
|
||||
|
||||
is not. */
|
||||
;
|
||||
else if (template_header_count > context_depth + 1)
|
||||
/* There are two many template parameter lists. */
|
||||
cp_error ("too many template parameter lists in declaration of `%T'", type);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns 1 iff PARMS1 and PARMS2 are identical sets of template
|
||||
parameters. These are represented in the same format used for
|
||||
DECL_TEMPLATE_PARMS. */
|
||||
@ -1951,9 +2013,7 @@ push_template_decl_real (decl, is_friend)
|
||||
/* Push template declarations for global functions and types. Note
|
||||
that we do not try to push a global template friend declared in a
|
||||
template class; such a thing may well depend on the template
|
||||
parameters of the class. With guiding declarations, however, we
|
||||
push the template so that subsequent declarations of the template
|
||||
will match this one. */
|
||||
parameters of the class. */
|
||||
if (! ctx
|
||||
&& !(is_friend && template_class_depth (current_class_type) > 0))
|
||||
tmpl = pushdecl_namespace_level (tmpl);
|
||||
@ -2278,10 +2338,10 @@ convert_nontype_argument (type, expr)
|
||||
if (TREE_CODE (type_referred_to) == FUNCTION_TYPE)
|
||||
{
|
||||
/* For a non-type template-parameter of type reference to
|
||||
function, no conversions apply. If the
|
||||
template-argument represents a set of overloaded
|
||||
functions, the matching function is selected from the
|
||||
set (_over.over_). */
|
||||
function, no conversions apply. If the
|
||||
template-argument represents a set of overloaded
|
||||
functions, the matching function is selected from the
|
||||
set (_over.over_). */
|
||||
tree fns = expr;
|
||||
tree fn;
|
||||
|
||||
@ -3096,7 +3156,7 @@ lookup_template_class (d1, arglist, in_decl, context, entering_scope)
|
||||
|
||||
if (arg_depth == 1 && parm_depth > 1)
|
||||
{
|
||||
/* We've been with an incomplete set of template arguments.
|
||||
/* We've been given an incomplete set of template arguments.
|
||||
For example, given:
|
||||
|
||||
template <class T> struct S1 {
|
||||
@ -3109,8 +3169,28 @@ lookup_template_class (d1, arglist, in_decl, context, entering_scope)
|
||||
<class U> struct S1<T>::S2'. We must fill in the missing
|
||||
arguments. */
|
||||
my_friendly_assert (context != NULL_TREE, 0);
|
||||
while (!IS_AGGR_TYPE_CODE (TREE_CODE (context)))
|
||||
while (!IS_AGGR_TYPE_CODE (TREE_CODE (context))
|
||||
&& context != global_namespace)
|
||||
context = DECL_REAL_CONTEXT (context);
|
||||
|
||||
if (context == global_namespace)
|
||||
/* This is bad. We cannot get enough arguments, even from
|
||||
the surrounding context, to resolve this class. One
|
||||
case where this might happen is (illegal) code like:
|
||||
|
||||
template <class U>
|
||||
template <class T>
|
||||
struct S {
|
||||
A(const A<T>& a) {}
|
||||
};
|
||||
|
||||
We should catch this error sooner (at the opening curly
|
||||
for `S', but it is better to be safe than sorry here. */
|
||||
{
|
||||
cp_error ("invalid use of `%D'", template);
|
||||
return error_mark_node;
|
||||
}
|
||||
|
||||
arglist = add_to_template_args (CLASSTYPE_TI_ARGS (context),
|
||||
arglist);
|
||||
arg_depth = TMPL_ARGS_DEPTH (arglist);
|
||||
@ -3667,8 +3747,8 @@ tsubst_friend_function (decl, args)
|
||||
args, NULL_TREE),
|
||||
tsubst (DECL_TI_ARGS (decl),
|
||||
args, NULL_TREE));
|
||||
/* FIXME: The decl we create via the next tsubst be created on a
|
||||
temporary obstack. */
|
||||
/* FIXME: The decl we create via the next tsubst could be
|
||||
created on a temporary obstack. */
|
||||
new_friend = tsubst (decl, args, NULL_TREE);
|
||||
tmpl = determine_specialization (template_id, new_friend,
|
||||
&new_args,
|
||||
@ -4833,12 +4913,14 @@ tsubst (t, args, in_decl)
|
||||
};
|
||||
|
||||
Here, the DECL_TI_TEMPLATE for the friend declaration
|
||||
will be a LOOKUP_EXPR. We are being called from
|
||||
tsubst_friend_function, and we want only to create a
|
||||
new decl (R) with appropriate types so that we can call
|
||||
determine_specialization. */
|
||||
my_friendly_assert (TREE_CODE (DECL_TI_TEMPLATE (t))
|
||||
== LOOKUP_EXPR, 0);
|
||||
will be a LOOKUP_EXPR or an IDENTIFIER_NODE. We are
|
||||
being called from tsubst_friend_function, and we want
|
||||
only to create a new decl (R) with appropriate types so
|
||||
that we can call determine_specialization. */
|
||||
my_friendly_assert ((TREE_CODE (DECL_TI_TEMPLATE (t))
|
||||
== LOOKUP_EXPR)
|
||||
|| (TREE_CODE (DECL_TI_TEMPLATE (t))
|
||||
== IDENTIFIER_NODE), 0);
|
||||
gen_tmpl = NULL_TREE;
|
||||
}
|
||||
|
||||
|
@ -1098,6 +1098,10 @@ begin_function_definition (decl_specs, declarator)
|
||||
return 0;
|
||||
|
||||
reinit_parse_for_function ();
|
||||
/* The things we're about to see are not directly qualified by any
|
||||
template headers we've seen thus far. */
|
||||
reset_specialization ();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
10
gcc/testsuite/g++.old-deja/g++.pt/crash15.C
Normal file
10
gcc/testsuite/g++.old-deja/g++.pt/crash15.C
Normal file
@ -0,0 +1,10 @@
|
||||
// Build don't link:
|
||||
|
||||
template <class T>
|
||||
template <class U>
|
||||
struct A { // ERROR - too many template parameter lists
|
||||
public:
|
||||
A() {}
|
||||
|
||||
A(const A<T>& b) {} // ERROR - invalid use of template
|
||||
};
|
4
gcc/testsuite/g++.old-deja/g++.pt/enum5.C
Normal file
4
gcc/testsuite/g++.old-deja/g++.pt/enum5.C
Normal file
@ -0,0 +1,4 @@
|
||||
// Build don't link:
|
||||
|
||||
template <>
|
||||
enum E {e}; // ERROR - template declaration of enum
|
@ -4,7 +4,7 @@ template <class T>
|
||||
void foo(T t);
|
||||
|
||||
template <>
|
||||
void foo(int) {};
|
||||
void foo(int) {}; // ERROR - previously defined here.
|
||||
|
||||
template <>
|
||||
void foo<int>(int) {} // ERROR - duplicate specialization.
|
||||
|
22
gcc/testsuite/g++.old-deja/g++.pt/friend28.C
Normal file
22
gcc/testsuite/g++.old-deja/g++.pt/friend28.C
Normal file
@ -0,0 +1,22 @@
|
||||
// Build don't link:
|
||||
|
||||
class mystream;
|
||||
|
||||
template <class T> class a {
|
||||
public:
|
||||
friend mystream& operator>> <>( mystream&, a<T>& thea );
|
||||
private:
|
||||
T amember;
|
||||
};
|
||||
|
||||
template <class T> mystream& operator>>( mystream& s, a<T>& thea );
|
||||
|
||||
template<> mystream& operator>> <int>( mystream& s, a<int>& thea );
|
||||
|
||||
template class a<int>;
|
||||
|
||||
template<> mystream& operator>> <int>( mystream& s, a<int>& thea )
|
||||
{
|
||||
thea.amember = 0;
|
||||
return s;
|
||||
}
|
16
gcc/testsuite/g++.old-deja/g++.pt/friend29.C
Normal file
16
gcc/testsuite/g++.old-deja/g++.pt/friend29.C
Normal file
@ -0,0 +1,16 @@
|
||||
// Build don't link:
|
||||
|
||||
template <class T> class a {
|
||||
public:
|
||||
friend void foo<>( a<T>& thea );
|
||||
private:
|
||||
T amember;
|
||||
};
|
||||
|
||||
template <class T> void foo( a<T>& thea )
|
||||
{
|
||||
thea.amember = 0;
|
||||
}
|
||||
|
||||
template class a<int>;
|
||||
|
Loading…
Reference in New Issue
Block a user