Wandering Fool
Wandering Fool

Reputation: 2278

Why is including implementation for a friend function that uses template parameters inside of a template class without a template declaration compile?

Given the following code.

#include <iostream>
template<typename T>
class Foo 
{
public:
    Foo(const T& value = T());

    friend Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs)
    {
    // ...
    }
    friend std::ostream& operator<< (std::ostream& o, const Foo<T>& x)
    {
    // ...
    }
private:
    T value_;
};

The compiler has no trouble compiling both friend functions which have template parameters without having the following syntax

template <typename T>
friend Foo<T> operator+ (const Foo<T>& lhs, const Foo<T>& rhs)

or

friend Foo<T> operator+ <>(const Foo<T>& lhs, const Foo<T>& rhs)

or

friend Foo<T> operator+ <T>(const Foo<T>& lhs, const Foo<T>& rhs)

because they have been defined by their implementation inside the template class itself.

How can the compiler compile these friend functions with their template parameters without including a template declaration? Why is just having their implementation inside of the class enough?

I learned about this concept from here under the section on "Why do I get linker errors when I use template friends?"

Upvotes: 0

Views: 240

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275330

Those two options, with and without template<class T>, do slightly different things.

When you introduce a friend function that way, you introduce it within the enclosing namespace in a way that is only reachable via ADL (argument dependent lookup).

The template<class T> introduces a function template, and the one without introduces an actual function.

So this:

template<class T>
struct foo {
  friend void bar(foo<T>){}
};

means that when foo<int> exists, a function bar(foo<int>) is created. Then foo<double> creates bar(foo<double>).

Each of these bars are not template functions. They are eaxh a function with a fixed signature, a new overload, imilar to if you wrote

void bar(foo<char>){}

right after foo. The exception is that the friend bar can only be found via ADL, which changes how conflicts and overload resolution works.

Now this:

template<class T>
struct foo {
  template <typename X>
  friend void bar(foo<X>){}
};

creates a template bar for each instance of foo. These do not conflict, because they are only found via ADL. And the only one that can be found is the one where the T matches the X (in this case -- with more arguments it could differ).

It is rarely a good idea to do the template version, in my experience.

Upvotes: 2

Related Questions