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?
Whenever a small integer type is used in an expression, it is implicitly converted to int which is always signed. This is known as the integer promotions or the integer promotion rule.
For example, an int value is assigned to a double variable. This conversion is legal because doubles are wider than ints. Here are the Type Promotion Rules: All byte and short values are promoted to int .
An implicit conversion sequence is the sequence of conversions required to convert an argument in a function call to the type of the corresponding parameter in a function declaration. The compiler tries to determine an implicit conversion sequence for each argument.
Keyword explicit tells compiler to not use the constructor for implicit conversion. For example declaring Bar's constructor explicit as - explicit Bar(int i); - would prevent us from calling ProcessBar as - ProcessBar(10); .
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With