#c #operator-overloading #ambiguous
Вопрос:
Рассмотрим простую реализацию векторного класса:
#include lt;algorithmgt; class Vector { public: Vector(int _elementsCount) : elementsCount(_elementsCount) , elements(new float[_elementsCount]) {} ~Vector() { delete[] elements; } Vector(const Vectoramp; rhs) { elementsCount = rhs.size(); elements = new float[elementsCount]; for (int i = 0; i lt; elementsCount; i) (*this)[i] = rhs[i]; } floatamp; operator [](int i) { return elements[i]; } float operator [](int i) const { return const_castlt;Vectoramp;gt;(*this)[i]; } int size() const { return elementsCount; } /*// Dot product float operator *(const Vectoramp; v) { float res = 0; for (int i = 0; i lt; size(); i) res = (*this)[i] * v[i]; return res; }*/ private: int elementsCount; float* elements; }; // Multiplication by a scalar Vector operator *(const Vectoramp; v, float k) { Vector res(v.size()); for (int i = 0; i lt; v.size(); i) res[i] = v[i] * k; return res; } // Dot product float operator *(const Vectoramp; v1, const Vectoramp; v2) { float res = 0; for (int i = 0; i lt; std::min(v1.size(), v2.size()); i) res = v1[i] * v2[i]; return res; } void main() { Vector v(2); v * 3; // ambiguous }
Этот код компилируется. Но если мы раскомментируем реализацию оператора * в классе и прокомментируем его глобальную реализацию (функцию точечного продукта), то возникнет ошибка «» Вектор::оператор*»: 2 перегрузки имеют аналогичные преобразования», потому что возникает двусмысленность: следует ли вызывать умножение на скаляр или интерпретировать 3 как аргумент параметризованного конструктора и вызывать точечное произведение. В этом есть смысл. Но я не понимаю, в чем разница между объявлением оператора * как функции-члена или как глобальной функции. Я думал, что они должны быть одинаковыми в примере, приведенном выше, но это не так.
Добавлено. Меня больше всего интересует не то, как избежать двусмысленности, а то, почему в одном случае (когда * объявлен как член) существует двусмысленность, а в другом-никого (когда * объявлен как глобальная функция).
Комментарии:
1. Сделайте конструктор
explicit
, и двусмысленность исчезнет.2. @Quimby, да, но почему в одном случае есть двусмысленность, а в другом-нет? (Я добавил явный вопрос в конец поста.)
3. Смотрите мой ответ в комментариях к моему ответу. Операторы, которые вы предполагаете эквивалентными, на самом деле не являются таковыми из-за различной постоянства .
Ответ №1:
Вам нужно создать свой конструктор explicit
:
explicit Vector(int _elementsCount) { ... }
Причина неоднозначности заключается в том , что компилятор не может решить, следует ли ему неявно преобразовывать int
значение в a Vector
и вызывать Vector::operator*
или неявно преобразовывать int
значение в a float
и использовать operator*(const Vectoramp;, float)
.
При использовании explicit
такие преобразования запрещены , и вы должны использовать Vector(3)
, если хотите, чтобы «3» было a Vector
.
В качестве примечания вы должны сделать оператор const
, так как он не изменяет объект. Сделав его постоянным, вы также сможете использовать его с const Vector
:
float operator *(const Vectoramp; v) const { ... }
Будьте осторожны, это все равно будет конфликтовать с вашей другой перегрузкой:
float operator *(const Vectoramp; v1, const Vectoramp; v2)
Нет причин иметь и то, и другое. Выберите либо функцию-член, либо глобальную функцию и удалите другую.
Комментарии:
1. Спасибо за ответ. Но можете ли вы сказать, почему неоднозначность зависит от того, как объявлена функция (член против глобального)?
2. Ваши членские и глобальные декларации не эквивалентны. Смотрите мой комментарий о корректности const. Как вы написали, перегрузка вашего члена эквивалентна глобальной перегрузке
float operator*(Vectoramp;, const Vectoramp;)
…. это означает, что значение слева от*
может быть изменено оператором. Если вы определяете свою глобальную перегрузку таким образом, вы получите ту же ошибку. Под капотом компилятор предпочитает сопоставлятьconst
ссылки при использовании операторов.