c++ 2 重载具有类似的转换,具体取决于运算符是成员函数还是全局函数

c++ 2 overloads have similar conversions depending on whether the operator is a member function or a global one

考虑一个简单的向量class实现:

#include <algorithm>

class Vector {
public:
    Vector(int _elementsCount)
        : elementsCount(_elementsCount)
        , elements(new float[_elementsCount])
    {}
    ~Vector() {
        delete[] elements;
    }
    Vector(const Vector& rhs) {
        elementsCount = rhs.size();
        elements = new float[elementsCount];
        for (int i = 0; i < elementsCount; ++i)
            (*this)[i] = rhs[i];
    }

    float& operator [](int i) {
        return elements[i];
    }
    float operator [](int i) const {
        return const_cast<Vector&>(*this)[i];
    }
    int size() const {
        return elementsCount;
    }
    /*// Dot product
    float operator *(const Vector& v) {
        float res = 0;
        for (int i = 0; i < size(); ++i)
            res += (*this)[i] * v[i];
        return res;
    }*/
private:
    int elementsCount;
    float* elements;
};

// Multiplication by a scalar
Vector operator *(const Vector& v, float k) {
    Vector res(v.size());
    for (int i = 0; i < v.size(); ++i)
        res[i] = v[i] * k;
    return res;
}

// Dot product
float operator *(const Vector& v1, const Vector& v2) {
    float res = 0;
    for (int i = 0; i < std::min(v1.size(), v2.size()); ++i)
        res += v1[i] * v2[i];
    return res;
}

void main()
{
    Vector v(2);
    v * 3; // ambiguous
}

此代码编译通过。但是如果我们取消注释掉class中的*运算符实现,注释掉它的全局实现(点积函数),那么就会出现“'Vector::operator *': 2 overloads have similar conversions”的错误,因为这里有歧义: 是通过标量调用乘法还是将 3 解释为参数化构造函数的参数并调用点积。这是有道理的。但我不明白将 * 运算符声明为成员函数或全局函数有什么区别。我以为他们在上面的例子中应该是一样的,但事实并非如此。

已添加。我最感兴趣的不是如何避免歧义,而是为什么在一种情况下(当*被声明为成员时)会出现歧义,并且另一个中没有一个(当 * 声明为全局函数时)。

你需要让你的构造函数explicit:

explicit Vector(int _elementsCount) { ... }

产生歧义的原因是编译器无法决定是否应该将 int 值隐式转换为 Vector 并调用 Vector::operator*,或者隐式转换 Vector::operator* =14=] 值到 float 并使用 operator*(const Vector&, float).

通过使用explicit,这样的转换是被禁止的,如果你想让“3”变成Vector,你必须使用Vector(3)

作为旁注,您应该将运算符设为 const,因为它不会修改对象。使它成为 const 也将允许它与 const Vector:

一起使用
float operator *(const Vector& v) const { ... }

注意这仍然会与您的其他重载冲突:

float operator *(const Vector& v1, const Vector& v2) 

没有理由两者兼得。选择成员函数或全局函数并删除另一个。