0

I'm trying to improve my understanding of templates. As a test I'd like to overload the stream operator<< so that it will operate on a templated class together with the (templated) basic_ostream from the STL. Here is a minimal example:

#include <iostream>

using namespace std;

//Forward declarations
template< typename T >
class Container;
template< typename T, typename charT, typename traits >
basic_ostream< charT, traits >& 
    operator<<( basic_ostream< charT, traits >& out, 
                               const Container< T >& a_container ); 

//Definitions
template< typename T >
class Container
{
public:
    Container( T a_value ): value( a_value ){}

private:
    T value;

    template< typename charT, typename traits >
    friend basic_ostream< charT, traits >& 
        operator<<( basic_ostream< charT, traits >& out, 
                    const Container< T >& a_container );
};

template< typename T, typename charT, typename traits >
basic_ostream< charT, traits >& 
    operator<<( basic_ostream< charT, traits >& out, 
                const Container< T >& a_container )
{
    out << a_container.value;
    return out;
}

//Main
int main( void )
{
    Container< int > my_container( 42 );

    cout << my_container;

    return 0;
}

However, when I try and compile this code the linker cannot seem to find the overloaded function:

/home/Ae6PGM/cc5xj2iM.o: In function `main':
prog.cpp:(.text.startup+0x21): undefined reference to `std::basic_ostream<char,
std::char_traits<char> >& operator<< <char, std::char_traits<char> >
(std::basic_ostream<char,     std::char_traits<char> >&, Container<int> const&)'
collect2: error: ld returned 1 exit status

I can fix this error if I make every template instance of my overloaded operator<< a friend, but it seems more correct if only instances which match the container type are friends. Here is a link to the working, but incorrect verion: http://ideone.com/DNQzlB.

Another alternative is to put the function definition inside the class declaration, but I'd like to avoid this if possible.

There is a similar, but slightly more simple question here. There is a great answer, but I can't figure out how to adapt it to my problem.

Thanks,

Andrew

Community
  • 1
  • 1
andypea
  • 1,343
  • 11
  • 22
  • I'm starting to think that this isn't possible as it would rely on a partially specialised function template which isn't supported in C++. Is that correct? – andypea Jan 30 '14 at 03:13
  • I think answer here might help: http://stackoverflow.com/questions/1458752/template-friendship – andypea Feb 05 '14 at 05:18
  • The `boost` libraries define the this type of friend function within the class itself. See: http://www.boost.org/doc/libs/1_55_0/boost/random/poisson_distribution.hpp – andypea Feb 05 '14 at 05:51

2 Answers2

0

The original declaration of your template took three template parameters { T, charT, traits } but the friend declaration only took 2 { charT, traits }. You're getting an undefined reference because the compiler sees these as two separate functions, one of which hasn't been defined.

You need to give it a 3rd template argument:

template< typename T2, typename charT, typename traits >
    friend basic_ostream< charT, traits >& 
        operator<< ( basic_ostream< charT, traits >& out, 
                    const Container< T2 >& a_container );
David G
  • 94,763
  • 41
  • 167
  • 253
  • This will allow the code to compile and link, but it will make every instance of the function a friend. Even instances where the type `T2` of the function does not match the type `T` of the Container. It's a reasonable workaround in practice, but I'd like to understand if there is a more correct way of doing this. – andypea Jan 30 '14 at 01:27
  • @andrew.punnett This *is* the correct way to do this. It gets even messier if you want to restrict it to one certain template argument however. – David G Jan 30 '14 at 01:38
  • @andrew.punnett What exactly are you trying to achieve with respect to restricting the inserter to a certain template argument? – David G Jan 30 '14 at 01:45
  • Just the correct encapsulation. If I was working with a non-templated stream object, such as `ostream`, I would definitely restrict friendship to templated functions which shared the same type as `Container`. I've banged my head against enough C++ to realise that sometimes pragmatism has to win over "correctness", but I'm not quite ready to give up on this one yet. – andypea Jan 30 '14 at 01:52
  • @andrew.punnett You're confusing `friend` functions with member functions. The `friend` function that you have defined here is not "apart" of the class, therefore it cannot use its template argument during the declaration. – David G Jan 30 '14 at 01:59
  • @andrew.punnett Friend functions/friend classes are simply constructs that are granted access to a class' private interface. You can change those functions to accept `Container`s of certain types, but you cannot do it as you have done. – David G Jan 30 '14 at 02:11
  • How can I change this function to accept only `Container` s of certain types? That would answer my question perfectly. See: http://ideone.com/fBhInb for an example of how this can be done easily for non-templated streams. The only way I can figure out how to do this for templated streams is to include the function definition inside the class declaration, which is very clunky. – andypea Jan 30 '14 at 02:29
  • @andrew.punnett [Take a look here.](http://ideone.com/bcFZy5) That's how you could restrict the template argument to an integer only. You could extend it for other types as well. – David G Jan 30 '14 at 02:42
  • I meant how can I restrict the permissions of the function so that it can only access private members of the type it is called with. I admit this is really pernickety, but I'm just trying to develop my understanding. Thanks for the help! – andypea Jan 30 '14 at 02:49
  • @andrew.punnett Can you give me an example of what you would like to have happen if it is called with a different type? – David G Jan 30 '14 at 02:55
  • I'd like functions called with a different type not to be friends and therefore not to be able to access the containers private members. Any attempts to access would generate errors at compilation time. The example here http://ideone.com/fBhInb does exactly what I want and works perfectly, but only for non-templated streams. – andypea Jan 30 '14 at 02:59
  • @andrew.punnett You can create a specialization of the class template and define a friend inserter for that specific specialization. – David G Jan 30 '14 at 03:06
  • Don't apologise man, discussing it with you helped a lot and I think I understand what is going on now. I believe that doing things the way I have written would only work if the compiler could create a partial specialisation of the function template. It can't do that, so instead it simply looks for a non-templated version which does not exist. – andypea Jan 30 '14 at 03:28
0

Thank you for asking this question. I have learned quite a bit by reviewing it.

That said, I think I understand what you are trying to ask. If I understand you correctly, you are attempting to implement and control implicit instantiation with your function template.

Although the example you posted here is of operator<< overloading, the mechanism underlying this generic programming using template is the same for function template implementation.

When you state cout << my_container, what you are instantiating is the overloaded operator<<. But if you want to instantiate a non-template stream object, you would have to modify what you are trying to read from.

Since the class data member value is private, you could use a pointer to explicitly point to that data member and use that pointer to instantiate a non-template stream object. Take a look at the following example:
int* private_value = (int*)&my_container;
The *private_value should allow you to access the private member despite the encapsulation.

If you want to enhance the security of data members, then deeper encapsulation is necessary to prevent such an implementation.

Template argument deduction can be achieved (if that is what you are seeking) by perhaps including a get() function and accessing value, thereby instantiating a non-template stream object.