11

In the following piece of code (live on coliru):

#include <iostream>
#include <string>

int main()
{
    struct S {
        operator bool        () const { return false; }
        operator std::string () const { return "false"; }
    } s;
    std::cout << s << "\n"; // outputs 0
}

How does the compiler choose to pick the implicit conversion to bool over std::string?

My hypothesis is that in this case, it might be purely the order of declaration of the different flavours of std::basic_ostream::operator<<, but is it all? Does the standard say something about picking a specific implicit conversion?

Barry
  • 286,269
  • 29
  • 621
  • 977
YSC
  • 38,212
  • 9
  • 96
  • 149

2 Answers2

11

Recall that std::string is not a standalone type, it's really a class template specialization - std::basic_string<char>. The very important detail is that the potential overload for streaming a std::string does not take a std::string const& argument, it is a function template that deduces a std::basic_string const&:

template <class CharT, class Traits, class Allocator>
std::basic_ostream<CharT, Traits>& 
    operator<<(std::basic_ostream<CharT, Traits>& os, 
               const std::basic_string<CharT, Traits, Allocator>& str);

Template deduction never considers conversions. Name lookup will find this function template, and then discard at as being non-viable due to deduction failure. S is not a basic_string<CharT, Traits, Allocator> for any such types, so we're done. The only viable stream operators would be all the integral ones, of which bool is the best match.

If there specifically was a function with signature:

std::ostream& operator<<(std::ostream&, std::string const& );    

Then the call would be ambiguous - you'd get two user-defined conversions that would be equivalently ranked.


This is easy to verify by using our own functions instead of the million overloads for operator<<:

void foo(bool ); // #1
void foo(std::string ); // #2

void bar(bool );  // #3
template <class C, class T, class A>
void bar(std::basic_string<C,T,A> ); // #4

foo(S{}); // error: ambiguous
bar(S{}); // calls #3
Barry
  • 286,269
  • 29
  • 621
  • 977
  • @YSC : I have edited mine to correct it. I hadn't realized at the time it is now an imperfect version of Barry's. I've still left it, because I think the "don't do that, then" advice is useful. – Martin Bonner supports Monica Feb 15 '17 at 15:32
  • @Barry, deduction succeeds for template operator <<. The compiler choose the member function because it is not a template. See my answer. – Oliv Feb 15 '17 at 18:08
  • A clear way to see this for OP is to comment out `operator bool` from the original code... then it fails to compile (it doesn't call the string convertor) – M.M Feb 17 '17 at 22:13
3
ostream& operator<<( bool value );

Is a member function of std::ostream. On the other hand:

std::ostream& operator<<(std::ostream& os, const std::string& str);

Is a standalone function - which is actually declared as a template. A reference to S doesn't match any of the templates - so it isn't considered for template expansion.


It is possible to uniquely determine which overload should be selected, but I suggest you don't do that.

a) This is always one of the tricky corners of the standard (so you may encounter compiler bugs;

b) future developers will always find the code hard to read.

My suggestion is to avoid the problem entirely by just making your conversion operators explicit, or give them names like to_bool() and to_string().