Sign in to follow this  
thedustbustr

templated stream operator for list

Recommended Posts

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

Share this post


Link to post
Share on other sites
thedustbustr    191
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').

Share this post


Link to post
Share on other sites
Nitage    1107
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.

Share this post


Link to post
Share on other sites
T2k    220
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!

Share this post


Link to post
Share on other sites
me22    212
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?

Share this post


Link to post
Share on other sites
Enigma    1410
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

Share this post


Link to post
Share on other sites
iMalc    2466
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.

Share this post


Link to post
Share on other sites
Zahlman    1682
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>).

Share this post


Link to post
Share on other sites
Enigma    1410
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

Share this post


Link to post
Share on other sites
Moomin    332
sorry slightly off topic but ... @iMalc - His way will/should work because if the list is empty, obj.head and obj.tail will both = NULL. So the 'for' conditional 'node != obj.tail;' == 'NULL != NULL' and so the for loop will never be run.

Share this post


Link to post
Share on other sites
me22    212
Quote:
Original post by Zahlman
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>).


Actually, it's not going to work, because std containers have at least 2 template arguments ( the value_type and the allocator, plus as many others as they want so long as those extra ones have defaults ).

template <typename Container>
std::ostream& operator << (std::ostream& os, const Container& c) {
os << '[';
std::copy(c.begin(), c.end(),
std::ostream_iterator<typename Container::value_type>(os, " "));
return os << "\x08]";
}


Not as dangerous as it looks because of SFINAE ( it only applies for types with the appropriate interface ) and because any less general function is a "better" choice in overload resolution ( basically anything other than operator<<(ostream&, ...) )

Share this post


Link to post
Share on other sites
iMalc    2466
Quote:
Original post by Moomin
sorry slightly off topic but ... @iMalc - His way will/should work because if the list is empty, obj.head and obj.tail will both = NULL. So the 'for' conditional 'node != obj.tail;' == 'NULL != NULL' and so the for loop will never be run.
Actually, I was indeed very much correct.
Even if the for-loop never executes, it's followed by
	n=n->next; //seperate tail so no trailing space
os << *n << "]";
Which is surely going to crash as it's dereferencing NULL for a start.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this