为什么可以强制转换的泛型类型不会被隐式转换

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

本文关键字:转换 泛型类型 为什么      更新时间:2023-10-16

我有一个类A和一个类B,两者都是泛型的,类型参数TA<T>的对象可以强制转换为B<T> 。我在B上有一个通用运算符重载,我希望能够调用A对象和B对象,其中A对象被隐式转换。

当我尝试这样做时,它无法编译:

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;
}

但是,当 A 和 B 不是通用的时,它可以正常工作:

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;
}

这是为什么呢?是否使运算符重载泛型,这意味着无法推断类型?

一般来说,在进行参数推导时不允许隐式转换,我可以认为派生到基数是允许的。表达式

B<int> combined1 =  objA * objB;

期望为objA * objB找到可行的重载,包括 ADL 发现的重载,一种可能是:

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

没有找到,你提供的重载不是候选的,因此调用失败,但是如果你向运算符提供显式模板参数,那么就没有什么可推断的,通过转换构造函数的隐式转换将允许调用:

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

但我不会那样做,坚持演员阵容,更好地解释意图。

您可以在调用模板函数class A中定义友元函数

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;
}

在参数推导期间,不会发生转换/提升,因此对于

objA * objB

在检查候选人超载的有效性时,不能推断出T

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

因此,过载被拒绝。

解决此问题的一种方法是创建一个非模板函数。Asit 应该适用于类模板,一种方法是使用 friend 函数:

template <typename T>
class B {
public:
    B() {}
    B(const A<T>&) {}
    friend B operator*(const B&, const B&) { return /*...*/; }
};

现在,objA * objB考虑过载B<int> operator*(const B<int>&, const B<int>&)并且可以进行转换以查看函数是否可行(确实可行)。

演示