Search code examples
c++operator-overloadingambiguous

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


Consider a simple vector class realization:

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

This code compiles. But if we uncomment * operator realization in the class and comment its global realization (dot product function), then there will be an error "'Vector::operator *': 2 overloads have similar conversions", because there is an ambiguity: whether to call the multiplication by a scalar or to interpret 3 as an argument to a parametrized constructor and to call the dot product. This makes sense. But I don't get what's the difference of declaring the * operator as a member function or as a global function. I thought they should be the same in the example like above, but it's not the case.

Added. The thing I most interested in is not how to avoid the ambiguity, but why there is an ambiguity in one case (when * is declared as a member) and there is no one in the other (when * is declared as a global function).


Solution

  • You need to make your constructor explicit:

    explicit Vector(int _elementsCount) { ... }
    

    The reason for the ambiguity is that the compiler can't decide whether it should implicitly convert a int value to a Vector and invoke Vector::operator*, or implicitly convert a int value to a float and use operator*(const Vector&, float).

    By using explicit, such conversions are forbidden, and you must use Vector(3) if you want "3" to be a Vector.

    As a side-note, you should make the operator const, since it does not modify the object. Making it const will also allow it to be used with a const Vector:

    float operator *(const Vector& v) const { ... }
    

    Beware that will still conflict with your other overload:

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

    There is no reason to have both. Choose either the member function or the global function and remove the other.