4

The following won't compile for me. I'm out of ideas... Any help?

template<>
inline
std::ostream& operator<< <const std::map<std::string, std::string> > (std::ostream& stream, const std::map<std::string, std::string>& some_map)
{
  return stream;
}

g++ gives me the following error:

error: expected initializer before '<' token

Edit: 1 Okay, since everyone is telling me to overload, let me give you an example that wouldn't make sense for overloading. What if I have this:

template <typename T>
inline
std::ostream& operator<<(std::ostream& stream, const T& something)
{
  stream << something.toString();
  return stream;
}

class Foo
{
public:
  Foo(std::string s)
  {
    name = s;
  }

  std::string toString() const 
  {
    return name;
  }

private:
  std::string name;
};

class Bar
{
public:
  Bar(int i)
  {
    val = i;
  }

  std::string toString() const 
  {
    std::ostringstream stream;
    stream << val;
    return stream.str();
  }

private:
  int val;
};

int main(int, char**)
{
  Foo foo("hey");
  Bar bar(2);

  std::cout << foo << std::endl;
  std::cout << bar << std::endl;
  return 0;
}

Now this won't work either.

I just want to avoid having to overload operator<< over and over by using a template like above. This seems like it should be possible. I would like to know if it is, and if so, how?

In such a scenario, overloading for both Foo and Bar to do the same thing would be a waste, that is why I am trying to avoid it.

Edit: 2 Okay, it appears that I am being misunderstood. Here is another attempt to clarify:

template <typename T>
std::ostream& operator<<(ostream& stream, const T& t)
{
  for(typename T::const_iterator i = t.begin(), end = t.end(); i != end; ++i)
  {
    stream << *i;
  }
  return stream;
}

int main(int, char**) 
{
  set<int> foo;
  list<string> bar;
  vector<double> baz;

  cout << foo << " " bar << " " << baz << endl;
};

The above code won't work mind you. Complains about ambiguity. But it seems like the better solution for printing out containers. If I did it the with overloading, I would need to write a version of operator<< for each container/datatype combination, which would yield a ridiculous amount of code duplication.

anio
  • 8,903
  • 7
  • 35
  • 53
  • What are you trying to do and why are you trying to use templates for this? – James McNellis Oct 27 '10 at 22:27
  • I don't understand what you want to do here. Do you want to *overload* `operator<<` for `std::map<...>`? If so, then you don't need templates. – Oliver Charlesworth Oct 27 '10 at 22:28
  • Imagine that all my classes have a toString method on them. I don't want to define operator<< for all those classes, because they will all do the same thing in the body, call: stream << object.toString(); So I had the idea of creating a template for it. Going from there, I thought I could specialize it for objects that don't have toString on them. – anio Oct 27 '10 at 22:32
  • @anio No need to specialize when you can just overload. – Dingo Oct 27 '10 at 22:34
  • Why are you programming Java in C++? The Java "toString" method should be implemented as operator<< to begin with if you are using c++. Then if you want to convert to a string, you use stringstream. – Benjamin Lindley Oct 27 '10 at 22:36
  • @PigBen I'm not programming Java in C++. You don't understand what I am trying to accomplish. I know I can overload. Thats not the point. I don't want to overload. I am trying to use templates to make my life easier. Why should I overload everything for every class, if they all have the same interface for stringifying themselves? – anio Oct 27 '10 at 22:46
  • @anio -- But don't you see? You are overloading. You are just overloading a different function. Whereas my method overloads operator<<, and has one global template toString function. Your method overloads toString, and tries to have one global operator<<. The advantage of mine is that it's consistent with the standard C++ way of doing things. And all the classes that already have an overload for operator<< will now have a toString function as well. But if you still insist on doing things this way, I will try to think of a solution. – Benjamin Lindley Oct 27 '10 at 23:19
  • Overloading for operator<< would require that function to access my classes internals, which would mean I would need to make it a friend. I'm not fond of that. Hence I decided to make a const public toString method for operator<<. Though you are correct, we are trying to solve the same problem from different angles. It just seems that this should work. Even if its not the c++ way of doing it, it should still work theoretically, if not, I would like an explanation of why not. Not just be told to do it differently. The compiler is complaining, I want to know and understand why. – anio Oct 27 '10 at 23:25
  • @anio: The problem is that people tend to use the << operator to convert there object into a string. Thus by makeing your object have a toString()you are doing the work that operator << should be doing then passing it to the stream thus replicating work. Also their is nothing wrong with making the stream operator << a friend. So what if that makes it a part of the public interface. If you ever changed the internals of your object you will need to change how it interacts with the stream so why not make the relationship explicit. – Martin York Oct 27 '10 at 23:38
  • @anio: As such PigBen is the correct C++ way of doing it. Get over it and accept his answer. – Martin York Oct 27 '10 at 23:40
  • @Martin York: PigBen gave a good answer, I upvoted him, but its not really answer I'm looking for. I've been overloading operator<< for years. Today by a whim I wanted to try something different. Its an experiment. It looks like it should compile. The examples I've provided look like perfectly legal c++ syntax to me. But it doesn't work. And so far no one has explained why it doesn't. I'm trying to learn why its not working, and if it can work, or if its not allowed in the language. Is that too much to ask? – anio Oct 28 '10 at 01:47

3 Answers3

5

This doesn't need to be a template function.

std::ostream & operator<<(std::ostream & stream, const std::map<std::string, std::string> & some_map)
{
    return stream;
}

Edit:

In reference to my comment about writing Java in C++(and sorry if it sounded rude, I didn't intend to be smarmy). Tell me if this doesn't work better for you. Instead of writing a "toString" method in the first place, just overload the operator<< to begin with. The function is nearly identical. Then, you can write a non-member template toString function that will automatically work with all of your classes, like this:

#include <sstream>
#include <string>

template<typename T>
std::string toString(const T & val)
{
    std::ostringstream ostr;
    ostr << val;
    return ostr.str();
}

Edit 2

Here's my alternative if you still insist on doing it your way. Make all of your classes with the toString method inherit from an abstract class with a virtual toString method, then write one operator<< to handle all of them.

class Stringifiable
{
public:
    virtual std::string toString() const = 0;
};

std::ostream & operator<<(std::ostream & ostr, const Stringifiable& something)
{
    return ostr << something.toString();
}

Now the compiler will choose your overload over templates.

Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • +1: This solves the problem, and does in keeping with the C++ stream paradigm. – Oliver Charlesworth Oct 27 '10 at 23:03
  • The problem with the questioner's second example, seems to be that the compiler is getting confused with the templated operator<< and the use of the ostream's member operator<< in the templates implemetation. I think that confusion will only get worse. Thus, this solution seems the most reasonable. – Dusty Campbell Oct 27 '10 at 23:31
4

You can use SFINAE to remove the template overload from consideration. Note that this has to be a part of the signature of the function. Thus you can use boost::enable_if:

template < typename T >
typename boost::enable_if< meta_function_to_check_for_concept<T>, std::ostream&>::type
operator << (std::ostream & out, T const& t)
{
  ...
}

If you don't do this then your template will attempt to match almost anything and will explode for every use of << that is not in line with the concept you're trying to match.

Your example is a bit contrived and might not lend itself to this answer, but there are situations in which it's warranted. This is how to do it.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
  • I never heard of SFINAE until you mentioned it. I am reading up on it right now. Because of you I learned something new. Thank you. From your answer I'm going to try and derive a solution that doesn't use boost. – anio Oct 28 '10 at 13:05
1

In Crazy Eddie's answer, he mentions SFINAE. Now, C++11 has that built in. If all types derive from the same base class, this is made pretty simple. Here's a more complete example using C++11:

#include <type_traits>
#include <iostream>
#include <ostream>

class MyBaseType {};
class MyClass : MyBaseType {};
class MyOtherClass : MyBaseType {};
class SomeType {};

template <typename T>
typename std::enable_if<std::is_base_of<MyBaseType,T>::value,std::ostream&>::type
operator<<(std::ostream& out, const T& x)
{
    out << 1;
}

int main()
{
    MyBaseType x;
    MyClass y;
    MyOtherClass z;
    SomeType i;
    std::cout << x << y << z; // success!
    std::cout << i; // compile-time failure!
}
kirbyfan64sos
  • 10,377
  • 6
  • 54
  • 75