1
class Test {
public:
    operator string() {
        return string{"TEST!"};
    }
};

int main() {
    cout << Test{};
}

I was expecting the Test object will be implicit converted to a string and output, but it gives me error:

error: cannot bind 'std::ostream {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'

This explicit conversion works:

cout << string{Test{}};

I got it working by casting to const char*:

class Test {
public:
    operator const char*() {
        return "TEST!";
    }
};

Then output:

cout << Test{}; //Yay it works.

I am assuming the cout << string is already an implicit conversion from string to char * and if I use casting to string, it will not perform a two level conversion from Test to string to char *. After directly casting to const char*, it works. (Please correct if the assumption is wrong)


TO PROVE THE ASSUMPTION IS RIGHT

class Test {
public:
    operator string() {
        return string{"TEST!"};
    }
};

ostream& operator<< (ostream& os, string s){
    os << s;
    return os;
}

This will perform a direct type deduction from Test to string and output string. and after I tried, it works!

cout << Test{}; //YAY WORKS! OUTPUT "TEST!"

Something special about cout << string is explained by Borgleader. Assumption is partially correct and partially wrong.

SwiftMango
  • 15,092
  • 13
  • 71
  • 136
  • You've got your definitions of explicit and implicit switched. `T{x}` *explicitly* specifies a `T`, just `x` alone does not. Your first line would *implicitly* convert to a `string`. – GManNickG Aug 09 '13 at 18:56
  • @GManNickG yeah they always confused me.. Edited. – SwiftMango Aug 09 '13 at 18:59
  • This should help you: http://stackoverflow.com/questions/17539555/why-does-outputting-a-class-with-a-conversion-operator-not-work-for-stdstring – chris Aug 09 '13 at 19:04
  • 3
    Your assumptions at the end are wrong. There is no implicit conversion from `string` to `(const) char *`. Neither does `cout << some_string` call `cout << some_string.c_str()` internally. [Here's](http://coliru.stacked-crooked.com/view?id=577ff2fa377ec4b96acb609d3e2134cb-6e9f1f680880347f6708b805c806db62) proof of that; printing the string prints past the embedded NULL character. The `const char *` conversion operator works because `operator<<(ostream, const char *)` is not a function template. So it makes it into the overload resolution set, and is chosen as a viable match. – Praetorian Aug 09 '13 at 19:24
  • @chris If I use `os< – SwiftMango Aug 09 '13 at 19:27
  • 1
    It's worth noting you're not allowed to overload `operator<<` to take `std::string` (this *also* came up in the linked dupe, so I suggest reading it if you haven't). I was assuming you meant to take a `Test`. – chris Aug 09 '13 at 19:28
  • @texasbruce that's because you're defining `ostream << string` in terms of `ostream << string`. – Borgleader Aug 09 '13 at 19:28
  • 1
    @texasbruce The proof of your assumption is wrong too. That works because by providing your own `ostream& operator<< (ostream& os, string s)` (which is not a function template!), you've taken template argument deduction out of the picture! So that operator makes it to overload resolution, and can be selected. – Praetorian Aug 09 '13 at 19:29
  • @Praetorian, Not to mention that the code is now invalid, which I believe means UB. – chris Aug 09 '13 at 19:30
  • ["In the event that they edit the question to significantly change its meaning, after several answers have already been posted, it would be appropriate to roll back those edits"](http://meta.stackexchange.com/questions/148175/what-should-i-do-when-an-edit-would-change-the-context-of-a-question) – Mooing Duck Aug 09 '13 at 19:39
  • @MooingDuck Thanks for the headsup. I just added some additional info/tests I did which is related to this question... I hope its ok – SwiftMango Aug 09 '13 at 19:41
  • 1
    @texasbruce: The assumption isn't "partially right and partially wrong" it's entirely wrong. `string` has no implicit conversion to `const char*`, and the `const char*` overload is not used by `ostream< – Mooing Duck Aug 09 '13 at 19:43
  • @MooingDuck The right part is it does one conversion attempt. – SwiftMango Aug 09 '13 at 19:45

2 Answers2

4

The explicit keyword means you have to explicitly do the conversion yourself like std::string(Test{}). It disables implicit conversions.

cout << string{Test{}}; // <-- this is explicit, you've got implicit and explicit confused

The rule is, during template argument deduction, no user defined conversion is attemped. However, as you've noted if you only have a conversion operator to int it compiles. That is because that overload is not a function template. Take a look at the reference page, and you will see:

basic_ostream& operator<<( int value );

This is a non-templated overload so the compiler will look for user defined conversions.

Borgleader
  • 15,826
  • 5
  • 46
  • 62
  • explicit and implicit always confused me... I removed the `explicit` keyword but it is still the same error. I don't think there is an `implicit` keyword right? – SwiftMango Aug 09 '13 at 18:58
  • 2
    @texasbruce: Nope, `implicit` is implicit. ;) – GManNickG Aug 09 '13 at 19:00
  • Can you please take a look at my edit? I removed the keyword and it is still not working... – SwiftMango Aug 09 '13 at 19:01
  • Even without the `explicit`, the code still doesn't compile. That's because there is no overload for `std::operator << (std::ostream &, Test &)` – zindorsky Aug 09 '13 at 19:02
  • @zindorsky That is correct. I thought the type conversion should implicitly work and convert to string so it can output. – SwiftMango Aug 09 '13 at 19:03
  • @texasbruce But how would the compiler know that it was to convert to `string`? For example, what if your class also had an `operator int` or an `operator float`? All of those would work with `ostream`'s `operator <<` as well. – zindorsky Aug 09 '13 at 19:09
  • 1
    @texasbruce No conversion is attempted during template argument deduction, either you have an overload specifically for your type or you have to explicitly convert yourself to a type for which an overload exists. – Borgleader Aug 09 '13 at 19:11
  • @zindorsky It actually works if you only put a `operator int`. More overloading will cause `ambiguous overload` error. I only put a `operator string` there and expect it would work because a single `operator int` works. – SwiftMango Aug 09 '13 at 19:11
  • 3
    @zindorsky Except an `operator int()` conversion operator [works](http://coliru.stacked-crooked.com/view?id=6bd217eded935af0d02e514292889eb8-6e9f1f680880347f6708b805c806db62). That `operator<<` is not a function template so it makes it into the overload resolution set, and can be selected as a viable candidate. That's not the case with function templates, they never make it past the template argument deduction phase. – Praetorian Aug 09 '13 at 19:12
  • @Praetorian Ah, I did not know that about `operator int`. Thanks. – zindorsky Aug 09 '13 at 19:21
  • I got it working using two methods (in my edit), and you are right: a user defined conversion will be attempted. However only one conversion is attempted (such as from Test to int). If it requires 2 attempts (such as from Test to string to char*), it will not work.. – SwiftMango Aug 09 '13 at 19:24
  • 3
    @texasbruce Again, that's not what's going on here. Yes, there is a rule that two user defined conversions are not attempted, but in this case the `operator<<(ostream, string)` is not even *considered* by overload resolution; it gets tossed out earlier during template argument deduction. – Praetorian Aug 09 '13 at 19:26
  • @Borgleader: Er, the first time I read the answer either there was no mention of templates, or I missed it. Downvote removed. However, "The compiler won't attempt to convert it to string, because for example:" etc is still wrong. The potential ambiguity is not the reason. [See?](http://coliru.stacked-crooked.com/view?id=ea76d03bfe756ec48bad89bf3d066939-cc73e281b3b6abc9bf6cf4f153b944a6) – Mooing Duck Aug 09 '13 at 19:33
  • 1
    @MooingDuck Not to mention there is [no ambiguity](http://coliru.stacked-crooked.com/view?id=c54ebbfbd96f27f2fb91411be45fc5e6-6e9f1f680880347f6708b805c806db62). That part of the answer is incorrect. – Praetorian Aug 09 '13 at 19:35
  • This is more likely the reference?: http://en.cppreference.com/w/cpp/string/basic_string/operator_ltltgtgt – SwiftMango Aug 09 '13 at 19:39
  • @Praetorian I just re-read that part of my answer, and wow I really didn't explain what i was originally going for. I'll just remove that part. – Borgleader Aug 09 '13 at 19:39
2

The operator<< overload that takes an std::string (or std::basic_string) is a function template. User defined conversions are not considered during template argument deduction, so the compiler doesn't think the string overload is a match.

To avoid the error, define an overload basic_ostream& operator<<(basic_ostream&, Test&) for your class.

Praetorian
  • 106,671
  • 19
  • 240
  • 328