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