8

Here is the code:

class A{
public:
    int val;
    char cval;
    A():val(10),cval('a'){ }
    operator char() const{ return cval; }
    operator int() const{ return val; }
};
int main()
{
    A a;
    cout << a;
}

I am running the code in VS 2013, the output value is 10, if I comment out operator int() const{ return val; }, the output value will then become a.

My question is how does the compiler determine which implicit type conversion to choose, I mean since both int and char are possible options for the << operator?

T.C.
  • 123,516
  • 14
  • 264
  • 384
richard.g
  • 3,285
  • 1
  • 14
  • 23
  • 1
    If a class can convert to more than one "meaning" of output, I would use getters rather than conversion operators. So an int and float conversion which return (as close as they can) the same value is one thing, but two different values is not a good idea in my opinion. – Neil Kirk Aug 19 '14 at 14:07
  • 5
    This code does not compile on [ideone](http://ideone.com/AcMMw8). I think the compiler error is what the expected output should be, but someone with a better knowledge of the standard would be a better source on that. – wolfPack88 Aug 19 '14 at 14:09
  • 4
    Same here: `error: ambiguous overload for 'operator< – tgmath Aug 19 '14 at 14:10
  • Huh, VS _does_ compile this: http://rextester.com/DKHBS89358 – Mooing Duck Aug 20 '14 at 01:02

3 Answers3

6

Yes, this is ambiguous, but the cause of the ambiguity is actually rather surprising. It is not that the compiler cannot distinguish between ostream::operator<<(int) and operator<<(ostream &, char); the latter is actually a template while the former is not, so if the matches are equally good the first one will be selected, and there is no ambiguity between those two. Rather, the ambiguity comes from ostream's other member operator<< overloads.

A minimized repro is

struct A{
    operator char() const{ return 'a'; }
    operator int() const{ return 10; }
};

struct B {
    void operator<< (int) { }
    void operator<< (long) { }
};

int main()
{
    A a;
    B b;
    b << a;
}

The problem is that the conversion of a to long can be via either a.operator char() or a.operator int(), both followed by a standard conversion sequence consisting of an integral conversion. The standard says that (§13.3.3.1 [over.best.ics]/p10, footnote omitted):

If several different sequences of conversions exist that each convert the argument to the parameter type, the implicit conversion sequence associated with the parameter is defined to be the unique conversion sequence designated the ambiguous conversion sequence. For the purpose of ranking implicit conversion sequences as described in 13.3.3.2, the ambiguous conversion sequence is treated as a user-defined sequence that is indistinguishable from any other user-defined conversion sequence. *

Since the conversion of a to int also involves a user-defined conversion sequence, it is indistinguishable from the ambiguous conversion sequence from a to long, and in this context no other rule in §13.3.3 [over.match.best] applies to distinguish the two overloads either. Hence, the call is ambiguous, and the program is ill-formed.


* The next sentence in the standard says "If a function that uses the ambiguous conversion sequence is selected as the best viable function, the call will be ill-formed because the conversion of one of the arguments in the call is ambiguous.", which doesn't seem necessarily correct, but detailed discussion of this issue is probably better in a separate question.

Community
  • 1
  • 1
T.C.
  • 123,516
  • 14
  • 264
  • 384
4

It shouldn't compile, since the conversion is ambiguous; and it doesn't with my compiler: live demo. I've no idea why your compiler accepts it, or how it chooses which conversion to use, but it's wrong.

You can resolve the ambiguity with an explicit cast:

cout << static_cast<char>(a); // uses operator char()
cout << static_cast<int>(a);  // uses operator int()

Personally, I'd probably use named conversion functions, rather than operators, if I wanted it to be convertible to more than one type.

Mike Seymour
  • 235,407
  • 25
  • 414
  • 617
1

A debugging session gave the result. One is globally defined operator<< and other one is class method. You guess which one is calling which.

Test.exe!std::operator<<<std::char_traits<char> >(std::basic_ostream<char,std::char_traits<char> > & _Ostr, char _Ch)

msvcp120d.dll!std::basic_ostream<char,std::char_traits<char> >::operator<<(int _Val) Line 292 C++

I am not a language lawyer, but I believe compiler is giving precedence to member-function first.

Ajay
  • 16,823
  • 9
  • 50
  • 94