Hello,

I have a particular set up of cast operators and constructors which is leading to a compile-time ambiguity error. I am wondering if somebody can explain why this is happening and what can be done to fix it.

For example, here we have 2 classes:

class A
{
    int val;
    public:
    A(){val=0;}
    A(const A& o){val=o.val;}
    A(int i){val=i;}
    A&operator=(const A&o){val=o.val;return *this;}
    A&operator=(int i){val=i;return *this;}
    operator int(){return val;}
};
class B
{
    int val;
    public:
    B(int i){val=i;}
    operator A(){return A(val);}
    operator double(){return 0.0;}
};

They work fine but don't really do anything at all. The purpose of the A operator in B is to allow such constructs as a=(A)b. However when I try to compile this:

A a;
B b(7);
a=(A)b;

or even this:

A a;
B b(7);
a=b;

I get a compiler error telling me that there is an ambiguous call. I can understand which functions are being called ambiguously, but I would have thought that conversion would only go one level deep (b becomes type A, gets copied to a) why would the compiler even try the implicit int conversion on the A version of b? or the conversion path of B->double->int->A?

Recommended Answers

All 4 Replies

Clearly, the paths that are ambiguous are: B -> A -> const A& -> A; and, B -> double -> int -> A. I know this because if you remove the double conversion operator it solves the problem (in both cases). Or, you can also make the operator explicit (C++11):

class B
{
    int val;
    public:
    B(int i){val=i;}
    operator A(){return A(val);}
    explicit operator double(){return 0.0;}
};

Now, a reasonable person might expect that the B -> A -> const A& -> A should be preferred over the double-int-path because the binding of an A object to a const-reference const A& seems like a much more straight-forward conversion than the double to int conversion. However, these primitive types are considered arithmetic types, and as such, are given an equal status to other implicit conversions. This is just one of those annoying realities (and due to the C legacy of C++) that you have to watch out for. It is generally dangerous to play with implicit conversions because of these kinds of pitfalls. I tend to stay away from implicit conversions in general. At least, be conservative and mark most of your conversion operators / constructors as "explicit", and selectively make implicit conversions. That's my advise.

Thanks for the clarification, but what really has me confused is that I can't seem to force explicit conversion either. It makes sense why a=b would be ambiguous, because it has to choose between B->A->const A&->A and B->double->int->A but when I explicitly say a=(A)b why does it call the A constructor on an implicitly cast version of b instead of calling the A operator in b? Is there a way to explicitly call a typecast operator?

The difference between a=b and a=(A)b (or a=A(b)) is that in the former case it says "assign b to a", and in the latter case it says "create a A object with b, and assign it to a". The point is that because the assignment operators of A and its constructors both have the same acceptable signatures, const A& or int, the ambiguity is the same whether you have "assign b to a" or "create A with b".

Is there a way to explicitly call a typecast operator?

Yes and no. Whether you use (A)b, A(b) or static_cast<A>(b), the situation is the same "create A with b", and as such, will be ambiguous. However, operators can be called explicitly, i.e., the operator functions are fundamentally just functions with a special name that allows the compiler to apply the operator syntax (e.g., operator + allows the compiler to apply it to a + b). But, as functions, they remain callable explicitly with the function syntax, so you can do this:

a = b.operator A();

to explicitly call the operator A member function of the B class. This trick applies to all other operators as well and can be useful to remove an ambiguity. This explicit call will say to the compiler that it should first call that function, which returns an A object, and then assign it to a, which is unambiguous.

Ah, perfect. Thank you. :)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.