...if I change the forward declaration of operator<< so that it doesn't match
A friend function should be seen as a very special type of declaration. In essence, the compiler does enough to parse the declaration however no semantic checking will take place unless you actually specialize the class.
After making your suggested modification, if you then instantiate test
you will get an error about the declarations not matching:
template class test<int>;
...However ... removing the forward declaration causes a problem
The compiler tries to parse the declaration to store it until the class template is specialized. During the parse, the compiler reaches the <
in the declaration:
friend std::ostream& operator<< <
The only way that operator<<
could be followed by <
is if it is a template, so a lookup takes place to check that it is a template. If a function template is found, then the <
is considered to be the start of template arguments.
When you remove the forward declaration, no template is found and operator<<
is considered to be an object. (This is also why when you add using namespace std
the code continues to compile as there must be declarations of templates for operator<<
).
...when I remove the forward declarations and use the alternative friend declaration in the code above. Note that the template parameter U doesn't appear in the following signature...
There is no requirement that all template parameters be used in the arguments of a function template. The alternative declaration is for a new function template that will only be callable if declared in the namespace and specifying explicit template arguments.
A simple example of this would be:
class A {};
template <typename T> A & operator<<(A &, int);
void foo () {
A a;
operator<< <int> (a, 10);
}
...is this code actually correct?..
Well there are two parts to this. The first is that the alternative friend function does not refer to the declaration later in the scope:
template <typename T>
class test {
template <typename U>
friend std::ostream& operator<<(std::ostream &out, const test<T> &t);
};
template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t); // NOT FRIEND!
The friend function would actually be declared in the namespace for each specialization:
template <typename U>
std::ostream& operator<<(std::ostream &out, const test<int> &t);
template <typename U>
std::ostream& operator<<(std::ostream &out, const test<char> &t);
template <typename U>
std::ostream& operator<<(std::ostream &out, const test<float> &t);
Every specialization of operator<< <U>
will have access to the specific specialization as per the type of its parameter test<T>
. So in essence the access is restricted as you require. However as I mentioned before these functions are basically unusable as operators, since you must use function call syntax:
int main ()
{
test<int> t;
operator<< <int> (std << cout, t);
operator<< <float> (std << cout, t);
operator<< <char> (std << cout, t);
}
As per the answers to the previous question, you either use the forward declaration as suggested by litb, or you go with defining the friend function inline as per Dr_Asik's answer (which would probably be what I would do).
UPDATE: 2nd Comment
...changing the forward declaration before the class; the one in the class still matches the function that I implement later...
As I pointed out above, the compiler checks if operator<<
is a template when it sees the <
in the declaration:
friend std::ostream& operator<< <
It does this by looking up the name and checking if it is a template. As long as you have a dummy forward declaration then this "tricks" the compiler into treating your friend as a template name and so the <
is considered to be the start of a template argument list.
Later, when you instantiate the class, you do have a valid template to match. In essence, you're just tricking the compiler into treating the friend as a template specialization.
You can do this here because (as I said earlier), no semantic checking takes place at this point in time.