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.

 
comments powered by Disqus