18

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 *.

Community
  • 1
  • 1
Mihai Todor
  • 8,014
  • 9
  • 49
  • 86
  • 1
    If you want to use a `Test` intance directly with ostreams and `<<` you should override the correct method: http://msdn.microsoft.com/en-us/library/1z2f6c2k(v=vs.100).aspx – Jack Dec 14 '12 at 17:02
  • 3
    @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. – Alessandro Teruzzi Dec 14 '12 at 17:06

5 Answers5

17

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);
Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • 2
    "explicit cast" is redundant. – Pete Becker Dec 14 '12 at 18:27
  • Not all `operator<<` are templates, but the `string` one is; it might be worth precising. – Matthieu M. Dec 14 '12 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, std::allocator > () 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 :) – Mihai Todor Dec 15 '12 at 03:54
  • 2
    Actually it's because [there are template parameters participating in deduction in the desired implicit conversion](http://stackoverflow.com/a/7505108/560648). – Lightness Races in Orbit Dec 15 '12 at 15:03
  • @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. – Mihai Todor Dec 17 '12 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.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • 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. – Mihai Todor Dec 15 '12 at 04:01
  • @MihaiTodor: To be an exact match, it would have to be a specialisation of `std::basic_string`. – Mike Seymour Dec 15 '12 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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • +1 for *Explicit is good* and showing the template effect with a reduced test case! – Matthieu M. Dec 14 '12 at 20:53
  • So, perhaps a stupid question: if `std::string` is just `typedef basic_string, allocator >` and I define `operator std::basic_string, std::allocator > () 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. – Mihai Todor Dec 15 '12 at 03:46
  • 2
    @MihaiTodor: oh, the argument deduction tries to match `MyClass` (the actual argument type for the `<<` invocation) with `std::basic_string` (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. – Cheers and hth. - Alf Dec 15 '12 at 04:25
1

You need to override the operator<< method.

std::ostream & operator <<(std::ostream & os, const Test & t) {
    return os << std::string(t);
}
template boy
  • 10,230
  • 8
  • 61
  • 97
0

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

CS Pei
  • 10,869
  • 1
  • 27
  • 46