Template friend function of a template class
Asked Answered
R

1

10

I was struggling with the issue described in this question (declaring a template function as a friend of a template class), and I believe the 2nd answer is what I want to do (forward declare the template function, then name a specialization as a friend). I have a question about whether a slightly different solution is actually correct or just happens to work in Visual C++ 2008.

Test code is:

#include <iostream>

// forward declarations
template <typename T>
class test;

template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t);

template <typename T>
class test {
  friend std::ostream& operator<< <T>(std::ostream &out, const test<T> &t);
  // alternative friend declaration
  // template <typename U>
  // friend std::ostream& operator<<(std::ostream &out, const test<T> &t);

  // rest of class
  };

template <typename T>
std::ostream& operator<<(std::ostream &out, const test<T> &t) {
  // output function defined here
  }

First, one strange thing I found was that if I change the forward declaration of operator<< so that it doesn't match (for example, std::ostream& operator<<(std::ostream &out, int fake);, everything still compiles and works correctly (to be clear, I don't need to define such a function, only declare it). However, as in the linked-to question, removing the forward declaration causes a problem as the compiler seems to think I'm declaring a data member instead of a friend function. I'm pretty sure that this behaviour is a Visual C++ 2008 bug.

The interesting thing is 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. This method also compiles and works correctly (without changing anything else). My question is whether this is conforming to the standard or an idiosyncrasy of Visual C++ 2008 (I couldn't find a good answer in my reference books).

Note that while a friend declaration template <typename U> friend ... const test<U> &t); also works, this actually gives each instance of the operator friend access to any instance of test, while what I want is that the private members of test<T> should only be accessible from operator<< <T>. I tested this by instantiating a test<int> inside the operator<< and accessing a private member; this should cause a compile error when I try to output a test<double>.

Synopsis: Removing the forward declarations and switching to the alternative friend declaration in the code above seems to produce the same result (in Visual C++ 2008) -- is this code actually correct?

UPDATE: Any of the above modifications to the code don't work under gcc, so I'm guessing that these are errors or "features" in the Visual C++ compiler. Still I'd appreciate insights from people familiar with the standard.

Ross answered 24/11, 2009 at 0:57 Comment(2)
Incidentally, I figured out that adding using namespace std; before the class declaration will remove the need for the forward declarations, which I'm guessing is a side-effect of the (possible) compiler bug.Ross
As per my comment - can you explicitly add the examples (including instantiation) which you are saying work? Verbally its quite difficult to understand what you mean for each example.Sheathe
S
7

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

Sheathe answered 24/11, 2009 at 12:2 Comment(3)
Originally I thought that it worked something like you've described, but it doesn't (which is why I asked this question). When I said "everything compiles and works correctly," I actually meant that I was able to successfully instantiate the class as well as use the << operator in the usual way. I also checked that the operator function had the correct access (e.g. it couldn't access private members of a test<int> if it was called to print a test<double>. This is the case with the code as written, with a "wrong" forward declaration, and with the alternative friend declaration.Ross
Also I realized that you probably misunderstood what I said about the "fake" declaration -- I'm talking only about changing the forward declaration before the class; the one in the class still matches the function that I implement later (so at instantiation, the needed function template has already been parsed).Ross
@Sumudu: I've added an updated to address your second comment. As for the first, can you please modify your question to include the example which you say "compiles and works correctly" but uses the alternative friend function template? When I do that here, I get errors for the access to the members in the namespace template.Sheathe

© 2022 - 2024 — McMap. All rights reserved.