spockwang
spockwang

Reputation: 917

How to define a friend template function of a template class outside of its declaration?

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.

Upvotes: 2

Views: 432

Answers (2)

Johan Lundberg
Johan Lundberg

Reputation: 27038

@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.

Upvotes: 3

jrok
jrok

Reputation: 55395

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);
}

Upvotes: 6

Related Questions