给出一个具有多个隐式转换函数(非显式构造函数和转换运算符)的简单类模板,如以下示例所示:
template<class T>
class Foo
{
private:
T m_value;
public:
Foo();
Foo(const T& value):
m_value(value)
{
}
operator T() const {
return m_value;
}
bool operator==(const Foo<T>& other) const {
return m_value == other.m_value;
}
};
struct Bar
{
bool m;
bool operator==(const Bar& other) const {
return false;
}
};
int main(int argc, char *argv[])
{
Foo<bool> a (true);
bool b = false;
if(a == b) {
// This is ambiguous
}
Foo<int> c (1);
int d = 2;
if(c == d) {
// This is ambiguous
}
Foo<Bar> e (Bar{true});
Bar f = {false};
if(e == f) {
// This is not ambiguous. Why?
}
}
涉及原始类型(bool
,int
)的比较运算符是模棱两可的-编译器不知道是否应使用转换运算符将左侧模板类实例转换为基本类型或使用转换构造函数将右侧基本类型转换为预期的类模板实例。
但是,最后的比较(包含一个简单的struct
)并不是模棱两可的。为什么?将使用哪种转换功能?
使用编译器msvc 15.9.7进行了测试。
答案 0 :(得分:19)
根据[over.binary] / 1
因此,对于任何二进制运算符
@
,x@y
都可以解释为 为x.operator@(y)
或operator@(x,y)
。
根据此规则,对于e == f
,编译器只能将其解释为e.operator==(f)
,而不能解释为f.operator==(e)
。因此没有歧义。您定义为operator==
成员的Bar
根本不是超载解决方案的候选人。
对于a == b
和c == d
,内置候选operator==(int, int)
(请参见[over.built] / 13)与定义为operator==
的成员。
答案 1 :(得分:5)
实现为成员函数的运算符重载不允许对其左侧操作数(即调用它们的对象)进行隐式转换。
始终有助于写出操作员重载的显式调用,以更好地准确了解其作用:
Foo<Bar> e (Bar{true});
Bar f = {false};
// Pretty explicit: call the member function Foo<Bar>::operator==
if(e.operator ==(f)) { /* ... */ }
这不能与Bar
中的比较运算符混淆,因为它需要左侧的隐式转换,这是不可能的。
定义Bar
及其比较运算符时,您可以触发类似于内置类型的歧义:
struct Bar { bool m; };
// A free function allows conversion, this will be ambiguous:
bool operator==(const Bar&, const Bar&)
{
return false;
}
在Scott Meyers's Effective C++,第24条中对此进行了很好的演示和解释。