cp-tree.h (TI_PENDING_SPECIALIZATION_FLAG): Remove.

* cp-tree.h (TI_PENDING_SPECIALIZATION_FLAG): Remove.
	* class.c (finish_struct): Remove hackery to deal with explicit
	specializations in class scope.
	* decl.c (grokfndecl): Improve error-recovery.
	* decl2.c (grokfield): Likewise.
	* pt.c (check_specialization_scope): New function.
	(begin_specialization): Call it.
	(process_partial_specialization): New function, split out from
	push_template_decl.  Check partial specializations more
	stringently.
	(push_template_decl): Call it.
	(check_explicit_specialization): Don't attempt to handle explicit
	specializations in class scope.
	(template_parm_data): Document.  Add current_arg and
	arg_uses_template_parms.
	(mark_template_parm): Set it.
	(tsubst_arg_types): Remove unused variable.
	* semantics.c (begin_class_definition): Tweak.

From-SVN: r22271
This commit is contained in:
Mark Mitchell 1998-09-05 20:14:21 +00:00 committed by Mark Mitchell
parent 00dd3ccd9b
commit 6c30752f09
23 changed files with 373 additions and 193 deletions

View File

@ -1,3 +1,24 @@
1998-09-05 Mark Mitchell <mark@markmitchell.com>
* cp-tree.h (TI_PENDING_SPECIALIZATION_FLAG): Remove.
* class.c (finish_struct): Remove hackery to deal with explicit
specializations in class scope.
* decl.c (grokfndecl): Improve error-recovery.
* decl2.c (grokfield): Likewise.
* pt.c (check_specialization_scope): New function.
(begin_specialization): Call it.
(process_partial_specialization): New function, split out from
push_template_decl. Check partial specializations more
stringently.
(push_template_decl): Call it.
(check_explicit_specialization): Don't attempt to handle explicit
specializations in class scope.
(template_parm_data): Document. Add current_arg and
arg_uses_template_parms.
(mark_template_parm): Set it.
(tsubst_arg_types): Remove unused variable.
* semantics.c (begin_class_definition): Tweak.
1998-09-04 Mark Mitchell <mark@markmitchell.com>
* inc/typeinfo (type_info::type_info(const char*)): Make

View File

@ -4148,8 +4148,6 @@ finish_struct (t, list_of_fieldlists, attributes, warn_anon)
{
tree fields = NULL_TREE;
tree *tail = &TYPE_METHODS (t);
tree specializations = NULL_TREE;
tree *specialization_tail = &specializations;
tree name = TYPE_NAME (t);
tree x, last_x = NULL_TREE;
tree access;
@ -4259,19 +4257,6 @@ finish_struct (t, list_of_fieldlists, attributes, warn_anon)
if (last_x)
TREE_CHAIN (last_x) = next_x;
if (DECL_TEMPLATE_SPECIALIZATION (x))
/* We don't enter the specialization into the class
method vector since specializations don't affect
overloading. Instead we keep track of the
specializations, and process them after the method
vector is complete. */
{
*specialization_tail = x;
specialization_tail = &TREE_CHAIN (x);
TREE_CHAIN (x) = NULL_TREE;
continue;
}
/* Link x onto end of TYPE_METHODS. */
*tail = x;
tail = &TREE_CHAIN (x);
@ -4359,27 +4344,6 @@ finish_struct (t, list_of_fieldlists, attributes, warn_anon)
t = finish_struct_1 (t, warn_anon);
TYPE_BEING_DEFINED (t) = 0;
/* Now, figure out which member templates we're specializing. */
for (x = specializations; x != NULL_TREE; x = TREE_CHAIN (x))
{
int pending_specialization;
pending_specialization
= TI_PENDING_SPECIALIZATION_FLAG (DECL_TEMPLATE_INFO (x));
check_explicit_specialization
(lookup_template_function (DECL_NAME (x), DECL_TI_ARGS (x)),
x, 0, 1 | (8 * pending_specialization));
TI_PENDING_SPECIALIZATION_FLAG (DECL_TEMPLATE_INFO (x)) = 0;
/* Now, the assembler name will be correct for fn, so we
make its RTL. */
DECL_RTL (x) = 0;
make_decl_rtl (x, NULL_PTR, 1);
DECL_RTL (DECL_TI_TEMPLATE (x)) = 0;
make_decl_rtl (DECL_TI_TEMPLATE (x), NULL_PTR, 1);
}
if (current_class_type)
popclass (0);
else

View File

@ -35,7 +35,6 @@ Boston, MA 02111-1307, USA. */
(TREE_MANGLED) (in IDENTIFIER_NODE) (commented-out).
1: IDENTIFIER_VIRTUAL_P.
TI_PENDING_TEMPLATE_FLAG.
TI_PENDING_SPECIALIZATION_FLAG.
TEMPLATE_PARMS_FOR_INLINE.
DELETE_EXPR_USE_VEC (in DELETE_EXPR).
(TREE_CALLS_NEW) (in _EXPR or _REF) (commented-out).
@ -1267,11 +1266,6 @@ struct lang_decl
#define TI_SPEC_INFO(NODE) (TREE_CHAIN (NODE))
#define TI_PENDING_TEMPLATE_FLAG(NODE) TREE_LANG_FLAG_1 (NODE)
/* TI_PENDING_SPECIALIZATION_FLAG on a template-info node indicates
that the template is a specialization of a member template, but
that we don't yet know which one. */
#define TI_PENDING_SPECIALIZATION_FLAG(NODE) TREE_LANG_FLAG_1 (NODE)
/* The TEMPLATE_DECL instantiated or specialized by NODE. This
TEMPLATE_DECL will be the immediate parent, not the most general
template. For example, in:

View File

@ -8016,6 +8016,8 @@ grokfndecl (ctype, type, declarator, orig_declarator, virtualp, flags, quals,
template_count,
2 * (funcdef_flag != 0) +
4 * (friendp != 0));
if (decl == error_mark_node)
return error_mark_node;
if ((! TYPE_FOR_JAVA (ctype) || check_java_method (ctype, decl))
&& check)
@ -8063,6 +8065,9 @@ grokfndecl (ctype, type, declarator, orig_declarator, virtualp, flags, quals,
template_count,
2 * (funcdef_flag != 0) +
4 * (friendp != 0));
if (decl == error_mark_node)
return error_mark_node;
if (ctype != NULL_TREE
&& (! TYPE_FOR_JAVA (ctype) || check_java_method (ctype, decl))
&& check)
@ -10392,10 +10397,15 @@ grokdeclarator (declarator, declspecs, decl_context, initialized, attrlist)
if (decl && DECL_NAME (decl))
{
if (template_class_depth (current_class_type) == 0)
decl
= check_explicit_specialization
(declarator, decl,
template_count, 2 * (funcdef_flag != 0) + 4);
{
decl
= check_explicit_specialization
(declarator, decl,
template_count, 2 * (funcdef_flag != 0) + 4);
if (decl == error_mark_node)
return error_mark_node;
}
t = do_friend (ctype, declarator, decl,
last_function_parms, flags, quals,
funcdef_flag);

View File

@ -1606,8 +1606,8 @@ grokfield (declarator, declspecs, init, asmspec_tree, attrlist)
init = NULL_TREE;
value = grokdeclarator (declarator, declspecs, FIELD, init != 0, NULL_TREE);
if (! value)
return value; /* friend or constructor went bad. */
if (! value || value == error_mark_node)
return NULL_TREE; /* friend or constructor went bad. */
/* Pass friendly classes back. */
if (TREE_CODE (value) == VOID_TYPE)

View File

@ -127,6 +127,8 @@ static int template_class_depth_real PROTO((tree, int));
static tree tsubst_aggr_type PROTO((tree, tree, tree, int));
static tree tsubst_decl PROTO((tree, tree, tree, tree));
static tree tsubst_arg_types PROTO((tree, tree, tree));
static void check_specialization_scope PROTO((void));
static tree process_partial_specialization PROTO((tree));
/* We use TREE_VECs to hold template arguments. If there is only one
level of template arguments, then the TREE_VEC contains the
@ -575,12 +577,44 @@ begin_template_parm_list ()
note_template_header (0);
}
/* This routine is called when a specialization is declared. If it is
illegal to declare a specialization here, an error is reported. */
void
check_specialization_scope ()
{
tree scope = current_scope ();
/* [temp.expl.spec]
An explicit specialization shall be declared in the namespace of
which the template is a member, or, for member templates, in the
namespace of which the enclosing class or enclosing class
template is a member. An explicit specialization of a member
function, member class or static data member of a class template
shall be declared in the namespace of which the class template
is a member. */
if (scope && TREE_CODE (scope) != NAMESPACE_DECL)
cp_error ("explicit specialization in non-namespace scope `%D'",
scope);
/* [temp.expl.spec]
In an explicit specialization declaration for a member of a class
template or a member template that appears in namespace scope,
the member template and some of its enclosing class templates may
remain unspecialized, except that the declaration shall not
explicitly specialize a class member template if its enclosing
class templates are not explicitly specialized as well. */
if (current_template_parms)
cp_error ("enclosing class templates are not explicit specialized");
}
/* We've just seen template <>. */
void
begin_specialization ()
{
note_template_header (1);
check_specialization_scope ();
}
/* Called at then end of processing a declaration preceeded by
@ -1209,17 +1243,10 @@ check_explicit_specialization (declarator, decl, template_count, flags)
if (ctype != NULL_TREE && TYPE_BEING_DEFINED (ctype))
{
if (!explicit_instantiation)
{
/* Since finish_struct_1 has not been called yet, we
can't call lookup_fnfields. We note that this
template is a specialization, and proceed, letting
finish_struct fix this up later. */
tree ti = perm_tree_cons (NULL_TREE,
TREE_OPERAND (declarator, 1),
NULL_TREE);
TI_PENDING_SPECIALIZATION_FLAG (ti) = 1;
DECL_TEMPLATE_INFO (decl) = ti;
}
/* A specialization in class scope. This is illegal,
but the error will already have been flagged by
check_specialization_scope. */
return error_mark_node;
else
/* It's not legal to write an explicit instantiation in
class scope, e.g.:
@ -1759,8 +1786,23 @@ build_template_decl (decl, parms)
struct template_parm_data
{
/* The level of the template parameters we are currently
processing. */
int level;
/* The index of the specialization argument we are currently
processing. */
int current_arg;
/* An array whose size is the number of template parameters. The
elements are non-zero if the parameter has been used in any one
of the arguments processed so far. */
int* parms;
/* An array whose size is the number of template arguments. The
elements are non-zero if the argument makes use of template
parameters of this level. */
int* arg_uses_template_parms;
};
/* Subroutine of push_template_decl used to see if each template
@ -1790,13 +1832,205 @@ mark_template_parm (t, data)
}
if (level == tpd->level)
tpd->parms[idx] = 1;
{
tpd->parms[idx] = 1;
tpd->arg_uses_template_parms[tpd->current_arg] = 1;
}
/* Return zero so that for_each_template_parm will continue the
traversal of the tree; we want to mark *every* template parm. */
return 0;
}
/* Process the partial specialization DECL. */
tree
process_partial_specialization (decl)
tree decl;
{
tree type = TREE_TYPE (decl);
tree maintmpl = CLASSTYPE_TI_TEMPLATE (type);
tree specargs = CLASSTYPE_TI_ARGS (type);
tree inner_args = innermost_args (specargs);
tree inner_parms = INNERMOST_TEMPLATE_PARMS (current_template_parms);
tree main_inner_parms = DECL_INNERMOST_TEMPLATE_PARMS (maintmpl);
int nargs = TREE_VEC_LENGTH (inner_args);
int ntparms = TREE_VEC_LENGTH (inner_parms);
int i;
int did_error_intro = 0;
int issued_default_arg_message = 0;
struct template_parm_data tpd;
struct template_parm_data tpd2;
/* [temp.class.spec]
The template parameter list of a specialization shall not
contain default template argument values. */
for (i = 0; i < ntparms; ++i)
{
if (TREE_PURPOSE (TREE_VEC_ELT (inner_parms, i)))
{
if (!issued_default_arg_message)
{
cp_error ("default argument in partial specialization `%T'",
type);
issued_default_arg_message = 1;
}
TREE_PURPOSE (TREE_VEC_ELT (inner_parms, i)) = NULL_TREE;
}
}
/* We check that each of the template parameters given in the
partial specialization is used in the argument list to the
specialization. For example:
template <class T> struct S;
template <class T> struct S<T*>;
The second declaration is OK because `T*' uses the template
parameter T, whereas
template <class T> struct S<int>;
is no good. Even trickier is:
template <class T>
struct S1
{
template <class U>
struct S2;
template <class U>
struct S2<T>;
};
The S2<T> declaration is actually illegal; it is a
full-specialization. Of course,
template <class U>
struct S2<T (*)(U)>;
or some such would have been OK. */
tpd.level = TMPL_PARMS_DEPTH (current_template_parms);
tpd.parms = alloca (sizeof (int) * ntparms);
bzero (tpd.parms, sizeof (int) * nargs);
tpd.arg_uses_template_parms = alloca (sizeof (int) * nargs);
bzero (tpd.arg_uses_template_parms, sizeof (int) * nargs);
for (i = 0; i < nargs; ++i)
{
tpd.current_arg = i;
for_each_template_parm (TREE_VEC_ELT (inner_args, i),
&mark_template_parm,
&tpd);
}
for (i = 0; i < ntparms; ++i)
if (tpd.parms[i] == 0)
{
/* One of the template parms was not used in the
specialization. */
if (!did_error_intro)
{
cp_error ("template parameters not used in partial specialization:");
did_error_intro = 1;
}
cp_error (" `%D'",
TREE_VALUE (TREE_VEC_ELT (inner_parms, i)));
}
/* [temp.class.spec]
The argument list of the specialization shall not be identical to
the implicit argument list of the primary template. */
if (comp_template_args (inner_args,
innermost_args (CLASSTYPE_TI_ARGS (TREE_TYPE
(maintmpl)))))
cp_error ("partial specialization `%T' does not specialize any template arguments", type);
/* [temp.class.spec]
A partially specialized non-type argument expression shall not
involve template parameters of the partial specialization except
when the argument expression is a simple identifier.
The type of a template parameter corresponding to a specialized
non-type argument shall not be dependent on a parameter of the
specialization. */
my_friendly_assert (nargs == DECL_NTPARMS (maintmpl), 0);
tpd2.parms = 0;
for (i = 0; i < nargs; ++i)
{
tree arg = TREE_VEC_ELT (inner_args, i);
if (/* These first two lines are the `non-type' bit. */
TREE_CODE_CLASS (TREE_CODE (arg)) != 't'
&& TREE_CODE (arg) != TEMPLATE_DECL
/* This next line is the `argument expression is not just a
simple identifier' condition and also the `specialized
non-type argument' bit. */
&& TREE_CODE (arg) != TEMPLATE_PARM_INDEX)
{
if (tpd.arg_uses_template_parms[i])
cp_error ("template argument `%E' involves template parameter(s)", arg);
else
{
/* Look at the corresponding template parameter,
marking which template parameters its type depends
upon. */
tree type =
TREE_TYPE (TREE_VALUE (TREE_VEC_ELT (main_inner_parms,
i)));
if (!tpd2.parms)
{
/* We haven't yet initialized TPD2. Do so now. */
tpd2.arg_uses_template_parms
= (int*) alloca (sizeof (int) * nargs);
tpd2.parms = (int*) alloca (sizeof (int) * nargs);
tpd2.level =
TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (maintmpl));
}
/* Mark the template paramters. But this time, we're
looking for the template parameters of the main
template, not in the specialization. */
tpd2.current_arg = i;
tpd2.arg_uses_template_parms[i] = 0;
bzero (tpd.parms, sizeof (int) * nargs);
for_each_template_parm (type,
&mark_template_parm,
&tpd2);
if (tpd2.arg_uses_template_parms [i])
{
/* The type depended on some template parameters.
If they are fully specialized in the
specialization, that's OK. */
int j;
for (j = 0; j < nargs; ++j)
if (tpd2.parms[j] != 0
&& tpd.arg_uses_template_parms [j])
{
cp_error ("type `%T' of template argument `%E' depends on template paramter(s)",
type,
arg);
break;
}
}
}
}
}
if (retrieve_specialization (maintmpl, specargs))
/* We've already got this specialization. */
return decl;
DECL_TEMPLATE_SPECIALIZATIONS (maintmpl) = CLASSTYPE_TI_SPEC_INFO (type)
= perm_tree_cons (inner_args, inner_parms,
DECL_TEMPLATE_SPECIALIZATIONS (maintmpl));
TREE_TYPE (DECL_TEMPLATE_SPECIALIZATIONS (maintmpl)) = type;
return decl;
}
/* Creates a TEMPLATE_DECL for the indicated DECL using the template
parameters given by current_template_args, or reuses a
previously existing one, if appropriate. Returns the DECL, or an
@ -1868,92 +2102,7 @@ push_template_decl_real (decl, is_friend)
if (TREE_CODE (decl) == TYPE_DECL && DECL_ARTIFICIAL (decl)
&& TREE_CODE (TREE_TYPE (decl)) != ENUMERAL_TYPE
&& CLASSTYPE_TEMPLATE_SPECIALIZATION (TREE_TYPE (decl)))
{
tree type = TREE_TYPE (decl);
tree maintmpl = CLASSTYPE_TI_TEMPLATE (type);
tree specargs = CLASSTYPE_TI_ARGS (type);
/* We check that each of the template parameters given in the
partial specialization is used in the argument list to the
specialization. For example:
template <class T> struct S;
template <class T> struct S<T*>;
The second declaration is OK because `T*' uses the template
parameter T, whereas
template <class T> struct S<int>;
is no good. Even trickier is:
template <class T>
struct S1
{
template <class U>
struct S2;
template <class U>
struct S2<T>;
};
The S2<T> declaration is actually illegal; it is a
full-specialization. Of course,
template <class U>
struct S2<T (*)(U)>;
or some such would have been OK. */
int i;
struct template_parm_data tpd;
int ntparms
= TREE_VEC_LENGTH (INNERMOST_TEMPLATE_PARMS (current_template_parms));
int did_error_intro = 0;
tpd.level = TMPL_PARMS_DEPTH (current_template_parms);
tpd.parms = alloca (sizeof (int) * ntparms);
for (i = 0; i < ntparms; ++i)
tpd.parms[i] = 0;
for (i = 0; i < TREE_VEC_LENGTH (specargs); ++i)
for_each_template_parm (TREE_VEC_ELT (specargs, i),
&mark_template_parm,
&tpd);
for (i = 0; i < ntparms; ++i)
if (tpd.parms[i] == 0)
{
/* One of the template parms was not used in the
specialization. */
if (!did_error_intro)
{
cp_error ("template parameters not used in partial specialization:");
did_error_intro = 1;
}
cp_error (" `%D'",
TREE_VALUE (TREE_VEC_ELT
(TREE_VALUE (current_template_parms),
i)));
}
/* [temp.class.spec]
The argument list of the specialization shall not be
identical to the implicit argument list of the primary
template. */
if (comp_template_args (specargs,
CLASSTYPE_TI_ARGS (TREE_TYPE (maintmpl))))
cp_error ("partial specialization `%T' does not specialize any template arguments", type);
if (retrieve_specialization (maintmpl, specargs))
/* We've already got this specialization. */
return decl;
DECL_TEMPLATE_SPECIALIZATIONS (maintmpl) = CLASSTYPE_TI_SPEC_INFO (type)
= perm_tree_cons (innermost_args (specargs),
INNERMOST_TEMPLATE_PARMS (current_template_parms),
DECL_TEMPLATE_SPECIALIZATIONS (maintmpl));
TREE_TYPE (DECL_TEMPLATE_SPECIALIZATIONS (maintmpl)) = type;
return decl;
}
return process_partial_specialization (decl);
args = current_template_args ();
@ -5194,7 +5343,6 @@ tsubst_decl (t, args, type, in_decl)
return r;
}
/* Substitue into the ARG_TYPES of a function type. */
tree
@ -5204,7 +5352,6 @@ tsubst_arg_types (arg_types, args, in_decl)
tree in_decl;
{
tree remaining_arg_types;
tree result;
tree type;
if (!arg_types || arg_types == void_list_node)

View File

@ -1234,9 +1234,9 @@ begin_class_definition (t)
&& TREE_CODE (TYPE_CONTEXT (t)) != NAMESPACE_DECL
&& ! current_class_type)
push_template_decl (TYPE_STUB_DECL (t));
maybe_process_partial_specialization (t);
pushclass (t, 0);
TYPE_BEING_DEFINED (t) = 1;
maybe_process_partial_specialization (t);
/* Reset the interface data, at the earliest possible
moment, as it might have been set via a class foo;
before. */

View File

@ -5,11 +5,13 @@ struct S
{
template <class T>
void foo(T t);
template <>
void foo<int>(int) {}
};
template <>
template <>
void S<char*>::foo<int>(int) {}
int main()
{
S<char*> s;

View File

@ -7,13 +7,14 @@ struct S
template <class T>
void foo(T t);
template <>
void foo(int) { }
template <class T>
void bar(T t) { this->template foo<U>(3.74); }
};
template <>
template <>
void S<int>::foo(int) { }
int main()
{
S<int> s;

View File

@ -4,11 +4,11 @@ struct S
{
template <class T>
void foo(T t);
template <>
void foo<int>(int i) { }
};
template <>
void S::foo<int>(int i) { }
int main()
{
S s;

View File

@ -4,12 +4,13 @@ struct S
struct Y {
template <class T>
void foo(T t);
template <>
void foo<int>(int i) { }
};
};
template <>
template <>
void S::Y<char>::foo<int>(int i) { }
int main()
{
S::Y<char> s;

View File

@ -4,9 +4,6 @@ struct S
{
template <class T>
void foo(T t);
template <>
void foo(int i);
};

View File

@ -4,9 +4,6 @@ struct S
{
template <class T>
void foo(T t);
template <>
void foo(int i);
};

View File

@ -7,9 +7,9 @@ struct S3
static char* h(U);
};
template <class T>
template <>
char* S3<T>::h(int) { return __PRETTY_FUNCTION__; }
template <>
char* S3<double>::h(int) { return __PRETTY_FUNCTION__; }
template <>
template <>

View File

@ -5,14 +5,15 @@ struct S
{
template <int i>
int f(int j) { abort(); return 0; }
template <>
int f<7>(int j) { return j + 7; }
template <>
int f<8>(int j) { return j + 8; }
};
template <>
template <>
int S<double>::f<7>(int j) { return j + 7; }
template <>
template <>
int S<double>::f<8>(int j) { return j + 8; }
int main()
{

View File

@ -7,9 +7,9 @@ struct S
int f(U u);
};
template <class T>
template <>
int S<T>::f(int i) { return 1; }
template <>
int S<char>::f(int i) { return 1; }
int main()
{

View File

@ -8,9 +8,9 @@ struct S
};
template <class T>
template <>
int S<T>::f<int>(int i) { return 1; }
template <>
int S<char>::f<int>(int i) { return 1; }
int main()
{

View File

@ -14,11 +14,11 @@ struct S1
template <class T>
void f(T* t);
template <>
void f(int* ip) {}
};
template <>
void S1::f(int* ip) {}
template <class U>
struct S2
{
@ -27,11 +27,12 @@ struct S2
template <class T>
void f(T* t);
template <>
void f(int* ip) {}
};
template <>
template <>
void S2<double>::f(int* ip) {}
int main()
{
int* ip;

View File

@ -28,9 +28,9 @@ struct S3
static int h(U);
};
template <class T>
template <>
int S3<T>::h(int) { return 0; }
template <>
int S3<double>::h(int) { return 0; }
template <>
template <>

View File

@ -0,0 +1,11 @@
// Build don't link:
template <class T>
struct S {
template <class U> void f(U);
template <> void f<int>(int); // ERROR - specialization
template <class V> struct I {};
template <class V> struct I<V*> {};
template <> struct I<int>; // ERROR - specialization
};

View File

@ -0,0 +1,16 @@
// Build don't link:
template <class T> struct S {};
template <class T = int> struct S<T*> {}; // ERROR - default argument
template <int I, int J> struct A {};
template <int I> struct A<I+5, I*2> {}; // ERROR - argument involves parameter
template <class T, T t> struct C {};
template <class T> struct C<T, 1>; // ERROR - type depends on parameter
int i;
template <class T> struct C<T*, &i>; // ERROR - type depends on parameter
template< int X, int (*array_ptr)[X] > class B {};
int array[5];
template< int X > class B<X,&array> { }; // ERROR - type depends on parameter

View File

@ -0,0 +1,15 @@
// Build don't link:
template <class T>
struct S
{
template <class U>
void f();
};
template <class T>
template <> // ERROR - enclosing classes not specialized
void S<T>::f<int> ()
{
}

View File

@ -4,21 +4,23 @@ struct S1
{
template <class T>
void f(T t1, T t2);
template <>
void f(int i1, int i2);
};
template <>
void S1::f(int i1, int i2);
template <class U>
struct S2
{
template <class T>
void f(T t1, T t2);
template <>
void f(int i1, int i2);
};
template <>
template <>
void S2<char>::f(int i1, int i2);
void h()
{
S1 s1;