C++ Friends, Templates, and Friends of Templates
This post is mostly to keep my own memory fresh. It’s been a while since I’ve written any hard-core C++ code, especially code that uses the language to its fullest potential.
Let’s assume for some strange reason that you have a class template within another class template. In other words, consider the following code:
template <typename T> class outer {
int x = 0;
public:
template <typename U> struct inner {
void foo(outer<U> const& o) const
{ cout << o.x << endl; }
};
};
with the following main function to drive it:
int main()
{
outer<char> o;
outer<int>::inner<char> i;
i.foo(o);
}
If you compile this code as-is, you will get an error about the
variable x being private to outer<char>
. This makes total sense,
since each instantiation of the class template outer is a distinct
class object, and thus the class template inner
will only be friends
with versions of outer that have the same template parameters. In
other words, if we change the declaration of i
to read
outer<char>::inner<char> i;
everything will be a-okay.
Luckily C++ gives us a mechanism for working around this without
breaking encapsulation: the friend declaration. So the question
becomes, what is the correct friend declaration for ensuring that for
all T
, every inner for each U
will be a friend of it? I’ll save
you the guess work. If we add the statement
template <typename V>
template <typename W> friend class outer<V>::inner;
to the class template outer, just after the definition of the class
template inner
, everything will work as we want it to.
I was thinking about this the other day, and it took me longer than I’d like to admit (being practically a human C++ compiler, I should have nailed this within seconds), hence the blog post – it’s here now for all eternity. So to wrap up, changing the class template definition to
template <typename T> class outer {
int x = 0;
public:
template <typename U> struct inner {
void foo(outer<U> const& o) const
{ cout << o.x << endl; }
};
template <typename V>
template <typename W> friend class outer<V>::inner;
};
will allow that pathological block of code in main
to compile.
I both love and fear C++ some times.