Search code examples
c++templatesoperator-overloadingimplicit-conversion

Why does a generic type that can be casted not get implicitly converted?


I have a class A and a class B, both generic with a type parameter T. An object of A<T> can be casted to B<T>. I have a generic operator overload on B that I want to be able to call on an A object and a B object, where the A object gets implicitly converted.

When I try this it does not compile:

template <typename T>
class A {};

template <typename T>
class B {
public:
    B() {}
    B(const A<T> &a) {}
};

template <typename T>
B<T> operator*(const B<T> &obj1, const B<T> &obj2) {
    return B<T>(); // doesn't matter
}

int main() {
    A<int> objA;
    B<int> objB;

    B<int> combined1 = objA * objB; // error: operator* isn't defined on these types
    B<int> combined2 = static_cast<B<int>>(objA) * objB; // fine

    return 0;
}

However when A and B aren't generic, it works fine:

class A {};

class B {
public:
    B() {}
    B(const A &a) {}
};

B operator*(const B &obj1, const B &obj2) {
    return B(); // doesn't matter
}

int main() {
    A objA;
    B objB;

    B combined1 = objA * objB; // fine
    B combined2 = static_cast<B>(objA) * objB; // also fine

    return 0;
}

Why is this? Is there something about making the operator overload generic that means the type can't be inferred?


Solution

  • In general, implicit conversions are not allowed while doing argument deduction, I can think of a derived to base as one that is allowed. The expression

    B<int> combined1 =  objA * objB;
    

    expect to find viable overloads for objA * objB, including those found by ADL, one possible is:

    template <typename T>
    B<T> operator*(const A<T> &obj1, const B<T> &obj2) {...}
    

    but none is found, the overload you provide is not a candidate, hence the call fail, but if you provide the explicit template argument to the operator, then there will be nothing to deduce and the implicit conversion through the converting constructor will allow the call:

     B<int> combined1 = operator*<int>(objA, objB);
    

    But I would not do that, stick with the cast it better explain the intent.