I have implemented a class vec3
:
struct vec3 {
double x = 0, y = 0, z = 0;
auto& operator*= (double d) {x *= d; y *= d; z *= d; return *this;}
};
auto operator* (const vec3 &a, double d) {auto ret = a; ret *= d; return ret;}
However, expressions of the form vec3{} * 5.
compile, while expressions like 5. * vec3{}
do not. This confuses me, because cppreference says that
Binary operators are typically implemented as non-members to maintain symmetry (for example, when adding a complex number and an integer, if operator+ is a member function of the complex type, then only complex + integer would compile, and not integer + complex).
Here, I have implemented operator*
as a non-member, so shouldn't both vec3{} * 5.
and 5. * vec3{}
compile, due to the symmetry mentioned in the cppreference quote?
What cppreference means when it says "symmetry" is symmetry of implicit conversions. When you implement *
as a member function, the left hand side is not subject to user-defined conversions. For example,
vec2::operator*
member function wouldn't be usable by vec3
on the left hand side, even if vec3
was convertible to vec2
; however,vec2::operator*(vec2)
could still accept a vec3
on the right hand side if there was a vec3 -> vec2
conversionThis means that vec2 * vec3
and vec3 * vec2
work differently, and such asymmetry is surprising and undesirable.
Similarly, the cppreference example complex + integer
should work the same as integer + complex
, assuming there is an implicit conversion from integer
to complex
. This would require +
to be implemented as:
complex operator+(complex, complex);
If there was a member function complex::operator+
instead, integer + complex
would be impossible.
The "symmetry" you're referring to is commutativity, i.e. the ability to swap the operands and yield the same result for some operation. C++ doesn't make operators commutative by default. It wouldn't be safe to do so for most anyway, especially not for *
. Matrix multiplication isn't commutative, but uses the *
symbol for example.
If you want commutativity, you'll have to do it yourself:
auto operator*(const vec3 &a, double d) { auto ret = a; ret *= d; return ret; }
auto operator*(double d, const vec3& a) { return a * d; }
Similar to the complex
and integer
example, you could avoid writing these operator overloads if there was a conversion from double
to vec3
. However, this conversion is likely undesirable in your example.
Note that since C++20, !=
and ==
are commutative even when overloaded. This is an exception to the rule that you have to define commutativity yourself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With