Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implicit conversion operator priority

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?

like image 786
YSC Avatar asked Feb 15 '17 14:02

YSC


People also ask

What is implicit type promotion?

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.

Is int promoted to Double?

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 .

What are implicit conversions C++?

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.

How do you prevent implicit conversions in C++?

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


1 Answers

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
like image 135
Barry Avatar answered Sep 29 '22 11:09

Barry