1

If I define the friend template function inside the template class declaration, as follows, it can compile.

#include <iostream>

template <typename T>
class X {
public:
    X(int i) : n(i) {}

private:
    T n;

    template <typename U> 
    friend void doStuff(const X<T>& x, const U& u)
    {
        std::cout << (x.n + u) << std::endl;
    }
};

int main()
{
    X<int> x(1);
    doStuff(x, 3);
    return 0;
}

But if I move the definition of doStuff() out of the class decalaration, just after the declaration of class X<>, as follows, it can't compile.

template <typename U>
template <typename T>
void doStuff(const X<T>& x, const U& u)
{
    std::cout << (x.n + u) << std::endl;
}

Neither does the following code.

template <typename U, typename T>
void doStuff(const X<T>& x, const U& u)
{
    std::cout << (x.n + u) << std::endl;
}

So how should I define the template function doStuff() outside the class declaration?

Thanks in advance.

spockwang
  • 897
  • 1
  • 6
  • 15
  • The only way I could make this work was with a friend class that encapsulates doStuff(). – ypnos Oct 13 '12 at 17:24

2 Answers2

5

Perhaps like this:

template <typename T>
class X {
public:
    X(int i) : n(i) {}

private:
    T n;

    template <typename U, typename V>
    friend void doStuff(const X<U>& x, const V& u);
};

template <typename U, typename V>
void doStuff(const X<U>& x, const V& u)
{
    std::cout << (x.n + u) << std::endl;
}

int main()
{
    X<int> x(1);
    doStuff(x, 3);
    X<double> y(1.0);
    doStuff(y, 3.5);
}
jrok
  • 54,456
  • 9
  • 109
  • 141
  • I know this works. But I want `doStuff()` to use the same template parameter as the class template (e.g `T`). – spockwang Oct 13 '12 at 17:33
  • 2
    @wbb, it will.. ! That's because template argument deduction will figure it out and basically set the T of the argument to the U of the parameter. Just think of it like this: You call doStuff with some object of type X. What else can it do than to use the correct Q? – Johan Lundberg Oct 13 '12 at 17:33
  • @wbb What Johan said, it will. If you try to force a different one, like `doStuff(x, 1);`, you get an error. – jrok Oct 13 '12 at 17:36
3

@jrok gives a good solution.

Let's elaborate on why it works, and why it's required.

By declaring the function as

template <typename U, typename V>
friend void doStuff(const X<U>& x, const V& u);

You say that there is a function doStuff for each X and V. But you then ask, what about if U is not T? Well, when you call doStuff with an argument of type X, int, the only acceptable result of template argument deduction is to set the type T of the class definition to int.

Now, why does it not work to just do this:

template <typename V>
friend void doStuff(const X<T>& x, const V& u);

?

Actually, it kind of does. But what it means is that for any type T you tell the compiler that you are going to define a function that takes an X<T>, for any required T, and where the second parameter is a template parameter. Note, that the first argument is not a template function parameter.

There's no way to generically say 'whenever the user uses a class of type X<T>, use this specific pattern for how to define a function doStuff where X<T> is the first parameter.

So, in the class you promised that it would exist, but there's no way to write the definition in a templated fashion. it would have to be something like

template <typename T,typename U>
for_any_seen_template_instance_anywhere_in_the_program_< X<T> >:: 
      void doStuff(const X<U>& x, const V& u){
   ... code.
}

There's no such thing. Another way to think about it: When you write

doStuff(a,b); 

if the first parameter is not function template parameter, there's nothing being automatically generated with respect to that first argument. The template parameter types of class templates are not automatically deduced like template function parameters..

However, you can define X<T> for any required T (as you promised in the declaration) but you have to do it yourself for each required type:

template<typename V>
void doStuff(const X<int>& x, const V& u){
   std::cout << (x.n + u) << std::endl;
}

EDIT, answering the comment below.

It is true that all doStuff(X<U>,int) for example, are friends of any X<T> regardless of the types T and U. This means that inside the body of doStuff you could access the privates of objects of type X<Q>. In this case you did not pass any such objects into the function but you could make an X<std::string> in there and play around with it's privates. This is the price you pay for not going with the inline you had originally.

Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97
  • But your answer makes `X` grant friendship to `doStuff` even if `T` is a different type from `U`. That is not my intention. – spockwang Oct 13 '12 at 19:29
  • 3
    All right, it is true that any f(X,int) is a friend of any type X regardless of the types T and U. But the only way you can ever use f is if U and T are the same. – Johan Lundberg Oct 13 '12 at 20:09