Implicit conversion to std::string

12,559

Solution 1

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);

Solution 2

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.

Solution 3

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.

Share:
12,559
Mihai Todor
Author by

Mihai Todor

Software Engineer at your service. You can find me on: Linkedin GitHub Twitter Stack Overflow tools that I created: Top N users for tag located in city / country (for answers) Top N users for tag located in city / country (for questions)

Updated on June 05, 2022

Comments

  • Mihai Todor
    Mihai Todor almost 2 years

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

  • Pete Becker
    Pete Becker over 11 years
    "explicit cast" is redundant.
  • Matthieu M.
    Matthieu M. over 11 years
    Not all operator<< are templates, but the string one is; it might be worth precising.
  • Matthieu M.
    Matthieu M. over 11 years
    +1 for Explicit is good and showing the template effect with a reduced test case!
  • Praetorian
    Praetorian over 11 years
    @MatthieuM. Thanks, updated.
  • Mihai Todor
    Mihai Todor over 11 years
    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.
  • Mihai Todor
    Mihai Todor over 11 years
    "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 :)
  • Mihai Todor
    Mihai Todor over 11 years
    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.
  • Cheers and hth. - Alf
    Cheers and hth. - Alf over 11 years
    @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.
  • Lightness Races in Orbit
    Lightness Races in Orbit over 11 years
  • Mike Seymour
    Mike Seymour over 11 years
    @MihaiTodor: To be an exact match, it would have to be a specialisation of std::basic_string.
  • Mihai Todor
    Mihai Todor over 11 years
    @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.