19

Possible Duplicate:
Overload resolution failure when streaming object via implicit conversion to string

I know it's not such a good idea to do this, but I really want to know the reason why the code below does not compile (i.e. why there is "no acceptable conversion"):

#include <iostream>
#include <string>


class Test
{
public:
    operator std::string () const;
};

Test::operator std::string () const
{
    return std::string("Test!");
}

int main ()
{
    std::string str = "Blah!";
    std::cout << str << std::endl;

    Test test;

    str = test;//implicitly calls operator std::string without complaining

    std::cout << str << std::endl;

    std::cout << test;//refuses to implicitly cast test to std::string

    return 0;
}

On Visual Studio 2010 I get this error: "error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'Test' (or there is no acceptable conversion)"

Does the << operator implicitly cast std::string to something else in order to make use of it? If yes, what operator do I need to overload in my class to make such a thing work? I refuse to believe that I would actually need to use operator char *.

2
  • 1
    If you want to use a Test intance directly with ostreams and << you should override the correct method: msdn.microsoft.com/en-us/library/1z2f6c2k(v=vs.100).aspx
    – Jack
    Commented Dec 14, 2012 at 17:02
  • 4
    @Jack I think, the OP is more interested on why it is not working and not how to fix it using a completely different approach. Commented Dec 14, 2012 at 17:06

5 Answers 5

18

operator<<(std::basic_ostream&, std::basic_string) is a function template and user defined conversions are not considered during template argument deduction. You need to overload operator<< for your class.

Another option, of course, is a cast

std::cout << static_cast<std::string>(test);
5
  • 2
    "explicit cast" is redundant. Commented Dec 14, 2012 at 18:27
  • Not all operator<< are templates, but the string one is; it might be worth precising. Commented Dec 14, 2012 at 20:52
  • "user defined conversions are not considered during template argument deduction" - Interesting. So, basically, it should compile if I define operator std::basic_string<char, std::char_traits<char>, std::allocator<char> > () const instead of operator std::string () const;? Unfortunately, it doesn't seem to like it either. I'm just trying to understand templates better, so don't worry, this will never end up in real code :) Commented Dec 15, 2012 at 3:54
  • 2
  • @LightnessRacesinOrbit Thanks for pointing out the duplicate! I think it's starting to sink in now, slowly... Makes my brain hurt. I'll accept this answer, based on your comment. Commented Dec 17, 2012 at 14:49
12

The problem is that std::string is a specialisation of a template, std::basic_string<char>, and the required overload of operator<< is itself a template:

template<class charT, class traits, class Allocator>
basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>&& os,
               const basic_string<charT,traits,Allocator>& str);

In order to be used for template argument deduction, a user-defined type has to be an exact match; conversions are not considered.

You will need to either provide an overload of operator<< for your class, or explicitly convert to std::string.

2
  • Out of sheer curiosity, how would a user-defined type look like in order to be an exact match for the above case? I just want to understand templates better. Commented Dec 15, 2012 at 4:01
  • @MihaiTodor: To be an exact match, it would have to be a specialisation of std::basic_string. Commented Dec 15, 2012 at 15:50
5

Generally it depends on whether the stream insertion operator << for the class is a concrete function or a template.

With << as a concrete function, the overload is found, and the conversion done (as long as it's not ambiguous):

#include <iostream>
using namespace std;

template< class CharType >
struct String {};

ostream& operator<<( ostream& stream, String<char> const& s )
{
    return (stream << "s");
}

struct MyClass
{
    operator String<char> () const { return String<char>(); }
};

int main()
{
    cout << "String: " << String<char>() << endl;
    cout << "MyClass: " << MyClass() << endl;
}

However, with << as a function template, the template matching finds no match, and then conversion via a user-defined operator is not attempted:

#include <iostream>
using namespace std;

template< class CharType >
struct String
{
};

template< class CharType >
ostream& operator<<( ostream& stream, String< CharType > const& s )
{
    return (stream << "s");
}

struct MyClass
{
    operator String<char> () const { return String<char>(); }
};

int main()
{
    cout << "String: " << String<char>() << endl;
    cout << "MyClass: " << MyClass() << endl;       // !Oops, nyet! Not found!
}

And in your case, std::string is really just a typedef for std::basic_string<char>.

Fix: define a << operator for your class or, if you want to avoid the header dependency (thinking build time), define a conversion to e.g. char const*, or, simplest and what I recommend, make that conversion a named one so that it has to be invoked explicitly.

Explicit is good, implicit is bad.

3
  • +1 for Explicit is good and showing the template effect with a reduced test case! Commented Dec 14, 2012 at 20:53
  • So, perhaps a stupid question: if std::string is just typedef basic_string<char, char_traits<char>, allocator<char> > and I define operator std::basic_string<char, std::char_traits<char>, std::allocator<char> > () const; inside my Test class, why is it still not considered "an exact match"? I mean, based on what you (and the others) said, this should make it compile, right? I really would like to understand the reasoning behind it. Commented Dec 15, 2012 at 3:46
  • 2
    @MihaiTodor: oh, the argument deduction tries to match MyClass (the actual argument type for the << invocation) with std::basic_string<C, T, A> (the formal argument type), where C, T and A are template parameters. But there is no choice of these parameters that turns MyClass into an exact match, or any kind of match. For template parameter matching only considers direct, exact matches, not any user defined conversions. The only "conversion" supported by the template parameter matching is derived to base. Commented Dec 15, 2012 at 4:25
1

You need to override the operator<< method.

std::ostream & operator <<(std::ostream & os, const Test & t) {
    return os << std::string(t);
}
0

I think the operator you need to override is "<<" .

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.