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

Viewed 91

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?

3 Answers

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.

You can define friend function in class A which call your template function

template <class T>
class B;

template <typename T>
class A {
    friend B<T> operator*(const B<T> &obj1, const B<T> &obj2) {} # here call template function
};

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; // fine
    B<int> combined2 = static_cast<B<int>>(objA) * objB; // fine

    return 0;
}

During argument deduction, conversion/promotion doesn't occurs, so for

objA * objB

When checking validity of candidates overload, T cannot be deduced for:

template <typename T> B<T> operator*(const B<T> &, const B<T> &);

So that overload is rejected.

One way to fix that is to create a non template function. Asit should apply to classe template, one way to do it is with friend functions:

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

    friend B operator*(const B&, const B&) { return /*...*/; }
};

Now, objA * objB considers overload B<int> operator*(const B<int>&, const B<int>&) and conversion can occurs to see if function is viable (and it is).

Demo

Related