What is the right way to define a friend function outside a template class?
Asked Answered
M

1

6

If I have a normal class I can "inject" a non-free friend function inside the class. (That among other things can be only be found by ADL).

case 1:

class A{
  double p_;
  friend double f(A const& a){return a.p_;}
};

If instead this is a template class I can do:

case 2:

template<class T>
class A{
  double p_;
  friend double f(A const& a){return a.p_;} // apparently A const& is a synomyn for A<T> const&
};

Now suppose that I need to implement f in terms of a class that needs to be defined later. I such case I tried doing this:

case 3:

template<class T>
class A{
    double p_;
    friend double f(A const& a);
};
...

This already gives a warning: "warning: friend declaration ‘double f(const A&)’ declares a non-template function [-Wnon-template-friend]".

Following the advice from the compiler I can do this:

template<class T> class A;

template<class T> double f(A<T> const& a);

template<class T>
class A{
    double p_;
    friend double f<>(A const& a);
};

template<class T> double f(A<T> const& a){return a.p_;}

Which requires so much more code and I am not even sure it is 100% equivalent to case 2 abov which is what I want, because now I have a truly free function that happens to be a friend instead of an injected friend.

Can case 3 be modified to be 100% equivalent to case 2 and still have a definition of f outside the class? In other words can one inject a friend function that is defined out of the class?


I tried this also, which gives a compiler error:

template<class T>
class A{
    double p_;
    friend double f(A<T> const& a);
};

template<class T> double A<T>::f(A<T> const& a){return a.p_;}

This answer finds the same solution but doesn't answer the question about case 3 being equivalent to case 2. What is the right way to write friend function declarations in template class?

Mainsheet answered 10/10, 2018 at 21:46 Comment(1)
"A const& is a synomyn for A<T> const&": Look at injected class nameBedard
B
9

Friend functions have special visibility rule (special case for ADL), so defining function outside the class is different than inside anyway.

Moreover, in case 2, the function is not template. even if you have one for every template. So to implement it outside of the class, you would have to implement each friend double f(A<T> const& a); for every T.

The advice is the most close workaround:

  • Your function (only the specialization) is friend.
  • but your function is template (so deduction should occurs:
    with friend double f(A<T> const& a, T); (case 2), f(A<float>{}, 42); would succeed
    whereas friend double f<>(A<T> const& a, T); would not
    (T would be float for A<float> and int for 42))

  • your function is declared outside, so its visibility is "different".

Now suppose that I need to implement f in terms of a class that needs to be defined later. I such case I tried doing this:

Other work around is to declare a private method which will do the job, that allow you to have friend definition inside the class. That private method can then be defined later:

template<class T>
class A{
    double p_;

    double do_f() const;
    friend double f(A const& a){return a.do_f();}
};

// Thing needed by A<T>::do_f

template<class T>
double A<T>::do_f() const
{
    // ...
}

If the return type is an incomplete type you have to do a trick with auto return (this works in g++11 and clang++11).

template<class T> class A;
class B;

template<class T>
class A{
    B do_f() const;
    friend auto f(A const& a){return a.do_f();} // not friend B f(...
};

class B{};

template<class T> B A<T>::do_f() const{return B{};}

int main(){A<double> a; f(a);}
Bedard answered 10/10, 2018 at 22:19 Comment(12)
So, there is no way to make case 3 equivalent to case 2?Mainsheet
No, but there are methods to stay in case 2.Bedard
Then I don't understand what you are proposing. Can you please rewrite case 2 following your advice so I can see an example?Mainsheet
@alfC: Added example to stay with inlined defined friend function.Bedard
Ah, excellent. So it seems to be good to make the friend functions "self contained" and at worst delegate to a member function. I didn't say it in the question but I have an extra problem the class that will be defined later is the return type of the friend. I am sure that I cannot return a class that is not defined from the defintion of the friend function (class B instead of double here).Mainsheet
I tried B do_f() const; friend B f(A const& a){return a.do_f();} and indeed got issues about incomplete type B, but B do_f() const; friend auto f(A const& a){return a.do_f();} succeed with both gcc/clang.Bedard
that's right for me as well, it works with auto f(...) but not with B f(...)! I don't know why it works but it does. (good luck with C++98 then). i added this case to your answer if you don't mind.Mainsheet
Note sure it is valid though.Bedard
I am not sure either. Intuition tells me it shouldn't work. Perhaps it works because the auto is deduced much later when the template is instantiated. If it is not valid, I am back to square 1 (square 0 in C++) for the use case I had in mind.Mainsheet
It is ironic that it is easier to define a member function out-of-line than a friend function.Mainsheet
Asked incomplete-class-usage-with-auto-in-template-class to know validity of auto.Bedard
with the years I found compilers (nvcc, intel) for which auto friend will choke SFINAE (the function is not detected). That is one problem of auto friends. I think this happens when the compiler cannot deduce auto in the "first pass".Mainsheet

© 2022 - 2024 — McMap. All rights reserved.