re PR c++/5369 (template member friend declaration not honored)

PR c++/5369
	* friend.c (is_friend): Handle member function of a class
	template as template friend.
	(do_friend): Likewise.
	* decl2.c (check_classfn): Add template_header_p parameter.
	* decl.c (start_decl): Adjust check_classfn call.
	(grokfndecl): Likewise.
	* pt.c (is_specialization_of_friend): New function.
	(uses_template_parms_level): Likewise.
	(push_template_decl_real): Use uses_template_parms_level.
	(tsubst_friend_function): Adjust check_classfn call.
	* cp-tree.h (check_classfn): Adjust declaration.
	(uses_template_parms_level): Add declaration.
	(is_specialization_of_friend): Likewise.

	* g++.dg/template/memfriend1.C: New test.
	* g++.dg/template/memfriend2.C: Likewise.
	* g++.dg/template/memfriend3.C: Likewise.
	* g++.dg/template/memfriend4.C: Likewise.
	* g++.dg/template/memfriend5.C: Likewise.
	* g++.dg/template/memfriend6.C: Likewise.
	* g++.dg/template/memfriend7.C: Likewise.
	* g++.dg/template/memfriend8.C: Likewise.
	* g++.old-deja/g++.pt/friend44.C: Remove a bogus error.

From-SVN: r73833
This commit is contained in:
Kriang Lerdsuwanakij 2003-11-22 06:49:21 +00:00 committed by Kriang Lerdsuwanakij
parent 646118866e
commit d43f603d85
16 changed files with 691 additions and 45 deletions

View File

@ -1,3 +1,20 @@
2003-11-22 Kriang Lerdsuwanakij <lerdsuwa@users.sourceforge.net>
PR c++/5369
* friend.c (is_friend): Handle member function of a class
template as template friend.
(do_friend): Likewise.
* decl2.c (check_classfn): Add template_header_p parameter.
* decl.c (start_decl): Adjust check_classfn call.
(grokfndecl): Likewise.
* pt.c (is_specialization_of_friend): New function.
(uses_template_parms_level): Likewise.
(push_template_decl_real): Use uses_template_parms_level.
(tsubst_friend_function): Adjust check_classfn call.
* cp-tree.h (check_classfn): Adjust declaration.
(uses_template_parms_level): Add declaration.
(is_specialization_of_friend): Likewise.
2003-11-21 Mark Mitchell <mark@codesourcery.com>
PR c++/12515

View File

@ -3718,7 +3718,7 @@ extern void maybe_make_one_only (tree);
extern void grokclassfn (tree, tree, enum overload_flags, tree);
extern tree grok_array_decl (tree, tree);
extern tree delete_sanity (tree, tree, int, int);
extern tree check_classfn (tree, tree);
extern tree check_classfn (tree, tree, bool);
extern void check_member_template (tree);
extern tree grokfield (tree, tree, tree, tree, tree);
extern tree grokbitfield (tree, tree, tree);
@ -3877,6 +3877,7 @@ extern void redeclare_class_template (tree, tree);
extern tree lookup_template_class (tree, tree, tree, tree, int, tsubst_flags_t);
extern tree lookup_template_function (tree, tree);
extern int uses_template_parms (tree);
extern int uses_template_parms_level (tree, int);
extern tree instantiate_class_template (tree);
extern tree instantiate_template (tree, tree, tsubst_flags_t);
extern int fn_type_unification (tree, tree, tree, tree, tree, unification_kind_t, int);
@ -3894,6 +3895,7 @@ extern int is_member_template (tree);
extern int comp_template_parms (tree, tree);
extern int template_class_depth (tree);
extern int is_specialization_of (tree, tree);
extern bool is_specialization_of_friend (tree, tree);
extern int comp_template_args (tree, tree);
extern void maybe_process_partial_specialization (tree);
extern void maybe_check_template_type (tree);

View File

@ -3756,7 +3756,9 @@ start_decl (tree declarator,
}
else
{
tree field = check_classfn (context, decl);
tree field = check_classfn (context, decl,
processing_template_decl
> template_class_depth (context));
if (field && duplicate_decls (decl, field))
decl = field;
}
@ -5661,7 +5663,9 @@ grokfndecl (tree ctype,
{
tree old_decl;
old_decl = check_classfn (ctype, decl);
old_decl = check_classfn (ctype, decl,
processing_template_decl
> template_class_depth (ctype));
if (old_decl && TREE_CODE (old_decl) == TEMPLATE_DECL)
/* Because grokfndecl is always supposed to return a

View File

@ -643,10 +643,12 @@ check_java_method (tree method)
/* Sanity check: report error if this function FUNCTION is not
really a member of the class (CTYPE) it is supposed to belong to.
CNAME is the same here as it is for grokclassfn above. */
CNAME is the same here as it is for grokclassfn above.
TEMPLATE_HEADER_P is true when this declaration comes with a
template header. */
tree
check_classfn (tree ctype, tree function)
check_classfn (tree ctype, tree function, bool template_header_p)
{
int ix;
int is_template;
@ -669,7 +671,7 @@ check_classfn (tree ctype, tree function)
/* OK, is this a definition of a member template? */
is_template = (TREE_CODE (function) == TEMPLATE_DECL
|| (processing_template_decl - template_class_depth (ctype)));
|| template_header_p);
ix = lookup_fnfields_1 (complete_type (ctype),
DECL_CONSTRUCTOR_P (function) ? ctor_identifier :

View File

@ -60,25 +60,15 @@ is_friend (tree type, tree supplicant)
tree friends = FRIEND_DECLS (list);
for (; friends ; friends = TREE_CHAIN (friends))
{
if (TREE_VALUE (friends) == NULL_TREE)
tree friend = TREE_VALUE (friends);
if (friend == NULL_TREE)
continue;
if (supplicant == TREE_VALUE (friends))
if (supplicant == friend)
return 1;
/* Temporarily, we are more lenient to deal with
nested friend functions, for which there can be
more than one FUNCTION_DECL, despite being the
same function. When that's fixed, this bit can
go. */
if (DECL_FUNCTION_MEMBER_P (supplicant)
&& same_type_p (TREE_TYPE (supplicant),
TREE_TYPE (TREE_VALUE (friends))))
return 1;
if (TREE_CODE (TREE_VALUE (friends)) == TEMPLATE_DECL
&& is_specialization_of (supplicant,
TREE_VALUE (friends)))
if (is_specialization_of_friend (supplicant, friend))
return 1;
}
break;
@ -338,8 +328,6 @@ do_friend (tree ctype, tree declarator, tree decl, tree parmdecls,
tree attrlist, enum overload_flags flags, tree quals,
int funcdef_flag)
{
int is_friend_template = 0;
/* Every decl that gets here is a friend of something. */
DECL_FRIEND_P (decl) = 1;
@ -353,39 +341,70 @@ do_friend (tree ctype, tree declarator, tree decl, tree parmdecls,
if (TREE_CODE (decl) != FUNCTION_DECL)
abort ();
is_friend_template = PROCESSING_REAL_TEMPLATE_DECL_P ();
if (ctype)
{
/* CLASS_TEMPLATE_DEPTH counts the number of template headers for
the enclosing class. FRIEND_DEPTH counts the number of template
headers used for this friend declaration. TEMPLATE_MEMBER_P is
true if a template header in FRIEND_DEPTH is intended for
DECLARATOR. For example, the code
template <class T> struct A {
template <class U> struct B {
template <class V> template <class W>
friend void C<V>::f(W);
};
};
will eventually give the following results
1. CLASS_TEMPLATE_DEPTH equals 2 (for `T' and `U').
2. FRIEND_DEPTH equals 2 (for `V' and `W').
3. TEMPLATE_MEMBER_P is true (for `W'). */
int class_template_depth = template_class_depth (current_class_type);
int friend_depth = processing_template_decl - class_template_depth;
/* We will figure this out later. */
bool template_member_p = false;
tree cname = TYPE_NAME (ctype);
if (TREE_CODE (cname) == TYPE_DECL)
cname = DECL_NAME (cname);
/* A method friend. */
if (flags == NO_SPECIAL && ctype && declarator == cname)
if (flags == NO_SPECIAL && declarator == cname)
DECL_CONSTRUCTOR_P (decl) = 1;
/* This will set up DECL_ARGUMENTS for us. */
grokclassfn (ctype, decl, flags, quals);
if (is_friend_template)
decl = DECL_TI_TEMPLATE (push_template_decl (decl));
else if (DECL_TEMPLATE_INFO (decl))
;
else if (template_class_depth (current_class_type))
decl = push_template_decl_real (decl, /*is_friend=*/1);
if (friend_depth)
{
if (!uses_template_parms_level (ctype, class_template_depth
+ friend_depth))
template_member_p = true;
}
/* We can't do lookup in a type that involves template
parameters. Instead, we rely on tsubst_friend_function
to check the validity of the declaration later. */
if (processing_template_decl)
add_friend (current_class_type, decl, /*complain=*/true);
/* A nested class may declare a member of an enclosing class
to be a friend, so we do lookup here even if CTYPE is in
the process of being defined. */
else if (COMPLETE_TYPE_P (ctype) || TYPE_BEING_DEFINED (ctype))
if (class_template_depth
|| COMPLETE_TYPE_P (ctype)
|| TYPE_BEING_DEFINED (ctype))
{
decl = check_classfn (ctype, decl);
if (DECL_TEMPLATE_INFO (decl))
/* DECL is a template specialization. No need to
build a new TEMPLATE_DECL. */
;
else if (class_template_depth)
/* We rely on tsubst_friend_function to check the
validity of the declaration later. */
decl = push_template_decl_real (decl, /*is_friend=*/1);
else
decl = check_classfn (ctype, decl, template_member_p);
if (template_member_p && decl && TREE_CODE (decl) == FUNCTION_DECL)
decl = DECL_TI_TEMPLATE (decl);
if (decl)
add_friend (current_class_type, decl, /*complain=*/true);
@ -398,6 +417,8 @@ do_friend (tree ctype, tree declarator, tree decl, tree parmdecls,
@@ or possibly a friend from a base class ?!? */
else if (TREE_CODE (decl) == FUNCTION_DECL)
{
int is_friend_template = PROCESSING_REAL_TEMPLATE_DECL_P ();
/* Friends must all go through the overload machinery,
even though they may not technically be overloaded.

View File

@ -876,6 +876,140 @@ is_specialization_of (tree decl, tree tmpl)
return 0;
}
/* Returns nonzero iff DECL is a specialization of friend declaration
FRIEND according to [temp.friend]. */
bool
is_specialization_of_friend (tree decl, tree friend)
{
bool need_template = true;
int template_depth;
my_friendly_assert (TREE_CODE (decl) == FUNCTION_DECL, 0);
/* For [temp.friend/6] when FRIEND is an ordinary member function
of a template class, we want to check if DECL is a specialization
if this. */
if (TREE_CODE (friend) == FUNCTION_DECL
&& DECL_TEMPLATE_INFO (friend)
&& !DECL_USE_TEMPLATE (friend))
{
friend = DECL_TI_TEMPLATE (friend);
need_template = false;
}
/* There is nothing to do if this is not a template friend. */
if (TREE_CODE (friend) != TEMPLATE_DECL)
return 0;
if (is_specialization_of (decl, friend))
return 1;
/* [temp.friend/6]
A member of a class template may be declared to be a friend of a
non-template class. In this case, the corresponding member of
every specialization of the class template is a friend of the
class granting friendship.
For example, given a template friend declaration
template <class T> friend void A<T>::f();
the member function below is considered a friend
template <> struct A<int> {
void f();
};
For this type of template friend, TEMPLATE_DEPTH below will be
non-zero. To determine if DECL is a friend of FRIEND, we first
check if the enclosing class is a specialization of another. */
template_depth = template_class_depth (DECL_CONTEXT (friend));
if (template_depth
&& DECL_CLASS_SCOPE_P (decl)
&& is_specialization_of (TYPE_NAME (DECL_CONTEXT (decl)),
CLASSTYPE_TI_TEMPLATE (DECL_CONTEXT (friend))))
{
/* Next, we check the members themselves. In order to handle
a few tricky cases like
template <class T> friend void A<T>::g(T t);
template <class T> template <T t> friend void A<T>::h();
we need to figure out what ARGS is (corresponding to `T' in above
examples) from DECL for later processing. */
tree context = DECL_CONTEXT (decl);
tree args = NULL_TREE;
int current_depth = 0;
while (current_depth < template_depth)
{
if (CLASSTYPE_TEMPLATE_INFO (context))
{
if (current_depth == 0)
args = TYPE_TI_ARGS (context);
else
args = add_to_template_args (TYPE_TI_ARGS (context), args);
current_depth++;
}
context = TYPE_CONTEXT (context);
}
if (TREE_CODE (decl) == FUNCTION_DECL)
{
bool is_template;
tree friend_type;
tree decl_type;
tree friend_args_type;
tree decl_args_type;
/* Make sure that both DECL and FRIEND are templates or
non-templates. */
is_template = DECL_TEMPLATE_INFO (decl)
&& PRIMARY_TEMPLATE_P (DECL_TI_TEMPLATE (decl));
if (need_template ^ is_template)
return 0;
else if (is_template)
{
/* If both are templates, check template paramter list. */
tree friend_parms
= tsubst_template_parms (DECL_TEMPLATE_PARMS (friend),
args, tf_none);
if (!comp_template_parms
(DECL_TEMPLATE_PARMS (DECL_TI_TEMPLATE (decl)),
friend_parms))
return 0;
decl_type = TREE_TYPE (DECL_TI_TEMPLATE (decl));
}
else
decl_type = TREE_TYPE (decl);
friend_type = tsubst_function_type (TREE_TYPE (friend), args,
tf_none, NULL_TREE);
if (friend_type == error_mark_node)
return 0;
/* Check if return types match. */
if (!same_type_p (TREE_TYPE (decl_type), TREE_TYPE (friend_type)))
return 0;
/* Check if function parameter types match, ignoring the
`this' parameter. */
friend_args_type = TYPE_ARG_TYPES (friend_type);
decl_args_type = TYPE_ARG_TYPES (decl_type);
if (DECL_NONSTATIC_MEMBER_FUNCTION_P (friend))
friend_args_type = TREE_CHAIN (friend_args_type);
if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl))
decl_args_type = TREE_CHAIN (decl_args_type);
if (compparms (decl_args_type, friend_args_type))
return 1;
}
}
return 0;
}
/* Register the specialization SPEC as a specialization of TMPL with
the indicated ARGS. Returns SPEC, or an equivalent prior
declaration, if available. */
@ -2861,10 +2995,8 @@ push_template_decl_real (tree decl, int is_friend)
/* It is a conversion operator. See if the type converted to
depends on innermost template operands. */
if (for_each_template_parm (TREE_TYPE (TREE_TYPE (tmpl)),
template_parm_this_level_p,
&depth,
NULL))
if (uses_template_parms_level (TREE_TYPE (TREE_TYPE (tmpl)),
depth))
DECL_TEMPLATE_CONV_FN_P (tmpl) = 1;
}
}
@ -4602,12 +4734,22 @@ for_each_template_parm (tree t, tree_fn_t fn, void* data, htab_t visited)
return result;
}
/* Returns true if T depends on any template parameter. */
int
uses_template_parms (tree t)
{
return for_each_template_parm (t, 0, 0, NULL);
}
/* Returns true if T depends on any template parameter with level LEVEL. */
int
uses_template_parms_level (tree t, int level)
{
return for_each_template_parm (t, template_parm_this_level_p, &level, NULL);
}
static int tinst_depth;
extern int max_tinst_depth;
#ifdef GATHER_STATISTICS
@ -4917,7 +5059,7 @@ tsubst_friend_function (tree decl, tree args)
/* Check to see that the declaration is really present, and,
possibly obtain an improved declaration. */
tree fn = check_classfn (DECL_CONTEXT (new_friend),
new_friend);
new_friend, false);
if (fn)
new_friend = fn;

View File

@ -1,3 +1,16 @@
2003-11-22 Kriang Lerdsuwanakij <lerdsuwa@users.sourceforge.net>
PR c++/5369
* g++.dg/template/memfriend1.C: New test.
* g++.dg/template/memfriend2.C: Likewise.
* g++.dg/template/memfriend3.C: Likewise.
* g++.dg/template/memfriend4.C: Likewise.
* g++.dg/template/memfriend5.C: Likewise.
* g++.dg/template/memfriend6.C: Likewise.
* g++.dg/template/memfriend7.C: Likewise.
* g++.dg/template/memfriend8.C: Likewise.
* g++.old-deja/g++.pt/friend44.C: Remove a bogus error.
2003-11-21 Mark Mitchell <mark@codesourcery.com>
PR c++/12515

View File

@ -0,0 +1,54 @@
// { dg-do compile }
// Copyright (C) 2003 Free Software Foundation
// Contributed by Kriang Lerdsuwanakij <lerdsuwa@users.sourceforge.net>
// Member function of class template as friend
template<class T> struct A
{
void f();
};
class C {
int i;
template<class T> friend void A<T>::f();
};
template<class T> struct A<T*>
{
void f();
};
template<> struct A<char>
{
void f();
};
template<class T> void A<T>::f()
{
C c;
c.i = 0;
}
template<class T> void A<T*>::f()
{
C c;
c.i = 0;
}
void A<char>::f()
{
C c;
c.i = 0;
}
int main()
{
A<int> a1;
a1.f();
A<int *> a2;
a2.f();
A<char> a3;
a3.f();
}

View File

@ -0,0 +1,61 @@
// { dg-do compile }
// Copyright (C) 2003 Free Software Foundation
// Contributed by Kriang Lerdsuwanakij <lerdsuwa@users.sourceforge.net>
// Member function template of class template as friend
template <class T> struct A
{
template <class U> void f();
};
class C {
int i;
template <class T> template <class U> friend void A<T>::f();
};
template <class T> struct A<T*>
{
template <class U> void f();
};
template <> struct A<char>
{
template <class U> void f();
};
template <class T> template <class U> void A<T>::f()
{
C c;
c.i = 0;
}
template <class T> template <class U> void A<T*>::f()
{
C c;
c.i = 0;
}
template <class U> void A<char>::f()
{
C c;
c.i = 0;
}
template <> void A<char>::f<int>()
{
C c;
c.i = 0;
}
int main()
{
A<int> a1;
a1.f<char>();
A<int *> a2;
a2.f<char>();
A<char> a3;
a3.f<char>();
a3.f<int>();
}

View File

@ -0,0 +1,55 @@
// { dg-do compile }
// Copyright (C) 2003 Free Software Foundation
// Contributed by Kriang Lerdsuwanakij <lerdsuwa@users.sourceforge.net>
// Member function of class template as friend
template<class T> struct A
{
void f(T);
};
class C {
int i;
template<class T> friend void A<T>::f(T);
};
template<class T> struct A<T*>
{
void f(T*);
};
template<> struct A<char>
{
void f(char);
};
template<class T> void A<T>::f(T)
{
C c;
c.i = 0;
}
template<class T> void A<T*>::f(T*)
{
C c;
c.i = 0;
}
void A<char>::f(char)
{
C c;
c.i = 0;
}
int main()
{
A<int> a1;
a1.f(0);
A<int *> a2;
int *p = 0;
a2.f(p);
A<char> a3;
a3.f('a');
}

View File

@ -0,0 +1,63 @@
// { dg-do compile }
// Copyright (C) 2003 Free Software Foundation
// Contributed by Kriang Lerdsuwanakij <lerdsuwa@users.sourceforge.net>
// Member function of class template as friend
template<class T> struct A
{
template <T t> void f();
};
class C {
int i;
template<class T> template <T t> friend void A<T>::f();
};
template<class T> struct A<T*>
{
template <T* t> void f();
};
template<> struct A<char>
{
template <char t> void f();
};
template<class T> template <T t> void A<T>::f()
{
C c;
c.i = 0;
}
template<class T> template <T* t> void A<T*>::f()
{
C c;
c.i = 0;
}
template <char t> void A<char>::f()
{
C c;
c.i = 0;
}
template <> void A<char>::f<'b'>()
{
C c;
c.i = 0;
}
int d2 = 0;
int main()
{
A<int> a1;
a1.f<0>();
A<int *> a2;
a2.f<&d2>();
A<char> a3;
a3.f<'a'>();
a3.f<'b'>();
}

View File

@ -0,0 +1,31 @@
// { dg-do compile }
// Copyright (C) 2003 Free Software Foundation
// Contributed by Kriang Lerdsuwanakij <lerdsuwa@users.sourceforge.net>
// Member template function of member class template as friend
template <class T> struct A {
template <class U> struct B {
template <class V> void f(V);
};
};
class X {
int i;
template <class T> template <class U> template <class V>
friend void A<T>::B<U>::f(V);
};
template <class T> template <class U> template <class V>
void A<T>::B<U>::f(V)
{
X x;
x.i = 0;
}
int main()
{
A<char>::B<char> a1;
a1.f(0);
}

View File

@ -0,0 +1,23 @@
// { dg-do compile }
// Copyright (C) 2003 Free Software Foundation
// Contributed by Kriang Lerdsuwanakij <lerdsuwa@users.sourceforge.net>
// Member function of class template as friend
// Erroneous case: mismatch during declaration
template <class T> struct A {
template <class U> void f(U); // { dg-error "candidate" }
void g(); // { dg-error "candidate" }
void h(); // { dg-error "candidate" }
void i(int); // { dg-error "candidate" }
};
class C {
int ii;
template <class U> friend void A<U>::f(U); // { dg-error "not match" }
template <class U> template <class V>
friend void A<U>::g(); // { dg-error "not match" }
template <class U> friend int A<U>::h(); // { dg-error "not match" }
template <class U> friend void A<U>::i(char); // { dg-error "not match" }
};

View File

@ -0,0 +1,133 @@
// { dg-do compile }
// Copyright (C) 2003 Free Software Foundation
// Contributed by Kriang Lerdsuwanakij <lerdsuwa@users.sourceforge.net>
// Member function of class template as friend
// Erroneous case: mismatch during specialization
template <class T> struct A {
template <class U> void f(U);
void g();
void h();
void i(int);
template <T t> void j();
};
class C {
int ii; // { dg-error "private" }
template <class U> template <class V>
friend void A<U>::f(V);
template <class U> friend void A<U>::g();
template <class U> friend void A<U>::h();
template <class U> friend void A<U>::i(int);
template <class U> template <U t>
friend void A<U>::j();
};
template <class T> struct A<T*> {
void f(int);
template <class U> void g();
int h();
void i(char);
template <int> void j();
};
template <class T> void A<T*>::f(int)
{
C c;
c.ii = 0; // { dg-error "context" }
}
template <class T> template <class U> void A<T*>::g()
{
C c;
c.ii = 0; // { dg-error "context" }
}
template <class T> int A<T*>::h()
{
C c;
c.ii = 0; // { dg-error "context" }
}
template <class T> void A<T*>::i(char)
{
C c;
c.ii = 0; // { dg-error "context" }
}
template <class T> template <int> void A<T*>::j()
{
C c;
c.ii = 0; // { dg-error "context" }
}
template <> struct A<char> {
void f(int);
template <class U> void g();
int h();
void i(char);
template <int> void j();
};
void A<char>::f(int)
{
C c;
c.ii = 0; // { dg-error "context" }
}
template <class U> void A<char>::g()
{
C c;
c.ii = 0; // { dg-error "context" }
}
template <> void A<char>::g<int>()
{
C c;
c.ii = 0; // { dg-error "context" }
}
int A<char>::h()
{
C c;
c.ii = 0; // { dg-error "context" }
}
void A<char>::i(char)
{
C c;
c.ii = 0; // { dg-error "context" }
}
template <int> void A<char>::j()
{
C c;
c.ii = 0; // { dg-error "context" }
}
template <> void A<char>::j<0>()
{
C c;
c.ii = 0; // { dg-error "context" }
}
int main()
{
A<int *> a1;
a1.f(0); // { dg-error "instantiated" }
a1.g<char>(); // { dg-error "instantiated" }
a1.g<int>(); // { dg-error "instantiated" }
a1.h(); // { dg-error "instantiated" }
a1.i('a'); // { dg-error "instantiated" }
a1.j<1>(); // { dg-error "instantiated" }
A<char> a2;
a2.f(0);
a2.g<char>(); // { dg-error "instantiated" }
a2.g<int>();
a2.h();
a2.i('a');
a2.j<1>(); // { dg-error "instantiated" }
a2.j<0>();
}

View File

@ -0,0 +1,25 @@
// { dg-do compile }
// Origin: Martin Sebor <sebor@roguewave.com>
// PR c++/5369: Member function of class template as friend
template <class T>
struct S
{
int foo () {
return S<int>::bar ();
}
private:
template <class U>
friend int S<U>::foo ();
static int bar () { return 0; }
};
int main ()
{
S<char>().foo ();
}

View File

@ -23,7 +23,7 @@ public:
template <class T> int A<T>::f (T)
{
B b;
return b.a; // { dg-bogus "" "" { xfail *-*-* } }
return b.a;
}
template <class T> int A<T>::AI::f (T)