templated stream operator for list

Started by
12 comments, last by iMalc 18 years, 4 months ago
I'm trying to implement the stream insertion operator so I can std::cout a list as an object instead of per-element. I'm getting a linker error. Relevant source:

namespace dust {
template <class T> class list
{
/* ... */
friend std::ostream& operator << (std::ostream &os,const list<T>& obj);
};

template<class T>
std::ostream& operator << (std::ostream &os,const list<T>& obj)
{
	//iterate through list
	os << "[";
	for (node<T>* n=obj.head; n!=tail; n=n->next) os << *n << " ";
	n=n->next; //seperate tail so no trailing space
	os << *n << "]";
	return os;
}
}; //namespace dust
//in main
list<int> a;
std::cout << a << std::endl;

list error LNK2019: unresolved external symbol "class 
std::basic_ostream<char,struct std::char_traits<char> > & __cdecl 
dust::operator<<(class std::basic_ostream<char,struct std::char_traits<char> > 
&,class dust::list<int> const &)" 
Is my syntax wrong? I can't for the life of me resolve this error. I'm using VC71.
Advertisement
template<T> friend std::ostream& operator<<(std::ostream &os,const list<T>& obj);
Negative, prepending template<T> or template<class T> to the declaration just results in compile errors, including the apparent effective loss of friend functionality ('cannot access private members').
Why does this need to be a friend function? Surely there's a publically accessable way to iterate through the list?

As to your problem, try:
friend std::ostream& operator<<(std::ostream &os,const list& obj);

If that doesn't solve your problem, then I'll have to think harder about it.
Quote:Original post by thedustbustr
Negative, prepending template<T> or template<class T> to the declaration just results in compile errors, including the apparent effective loss of friend functionality ('cannot access private members').


when i try your piece of code with my change in VS03 it compiles AND links without errors, so i can only assume that you put the operator << definition into a c(++) file => never do that when working with templates!
Better way:
#include <iterator>#include <algorithm>#include <list>#include <iostream>int main() {    std::list<int> my_list;    // Give it some values    std::generate_n( &std::rand, 10, std::back_inserter(my_list) );        // Sort it    my_list.sort();    // And print it    std::copy( my_list.begin(), my_list.end(),               std::ostream_iterator<int>( std::cout, ", " ) );    // Print again, using boost::lambda    std::for_each( my_list.begin(), my_list.end(),                   std::cout << _1 << ", " );}


In any case, the printing function shouldn't need to be a friend. If the public can't get to the elements in the list, then it's not very useful, is it? :P

If it's absolutely nessesary to have access to the private members for outputting, why not make a .write_to(std::ostream&) function in the class which the operator<<( std::ostream &, List<T> const &) can call?
Although I completely agree with me22 that you should use std::list and if you do have good reason not to use std::list that your overloaded insertion operator should not need to be a friend, for reference here is how you would make it a friend if you really needed to:
#include <iostream>namespace dust{	// forward declaration of list class needed for following forward declaration	template < class T >	class list;	// forward declaration of insertion operator needed to declare a specific template friend	template < class T >	std::ostream & operator<<(std::ostream & os, list< T > const & obj);	template <class T> class list	{		struct node		{			node * next;			T value;		};		node * head;		node * tail;		// note the additional <> which indicates that the friend is a template function		friend std::ostream & operator<<<>(std::ostream & os, list< T > const & obj);	};	template<class T>	std::ostream & operator<<(std::ostream & os, list< T > const & obj)	{		os << "[";		typename list< T >::node * node;		for (node = obj.head; node != obj.tail; node = node->next)		{			os << node->value << " ";		}		node = node->next;		os << node->value << "]";		return os;	}}int main(){	dust::list<int> a;	std::cout << a << std::endl;}

Note that this is not equivalent to the friend declaration template < typename T > friend std::ostream & operator<<(std::ostream & os, list< T > const & obj);, which makes all list insertion operators friends of all list template instantiations (i.e. operator<<(std::ostream &, list< int >) would be able to access private members of list< double >).

Enigma
Just thought I'd also point out that there's also a bug in your code. The bug is that it wont work with an empty list. You need to write it more like this:
	node<T>* n = obj.head;	os << "[";	while (n != NULL)	{		os << *n;		n = n->next;		if (n != NULL)			os << " ";	}	os << "]";
I've used while loop instead, but you can use whatever you like.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Sigh. More wheel reinvention every day around here eh?

#include <list>#include <iostream>#include <algorithm>template <typename Container, typename Contained>std::ostream& operator << (std::ostream& os, const Container<Contained>& c) {  os << '[';  std::copy(c.begin(), c.end(), std::ostream_iterator<Contained>(os, " "));  return os << ']';}


Should work; I don't have access to a C++ compiler right at the moment :/ This is designed to work with any STL container type (which you should be using, e.g. std::list from <list>).
Zahlman's syntax is slightly off, plus that doesn't quite do exactly what the OP wanted (it inserts an additional space character before the closing square bracket) but it's a good starting point. Assuming you can assume bidirectional iterators then you could modify it to:
#include <algorithm>#include <iostream>#include <iterator>template < template < typename T > class Container, typename Contained >std::ostream & operator<<(std::ostream & os, const Container< Contained > & c){	os << '[';	if (c.empty())	{		// if you want any special case for the empty list	}	else	{		typename Container< Contained >::const_iterator endButOne = c.end();		--endButOne;		std::copy(c.begin(), endButOne, std::ostream_iterator< Contained >(os, " "));		os << *endButOne;	}	return os << ']';}

Enigma

This topic is closed to new replies.

Advertisement