Implicit conversion operator priority

2019-04-20 13:42发布

问题:

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?

回答1:

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


回答2:

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().