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?
Implicit conversion symmetry of operator overloads
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 byvec3on the left hand side, even ifvec3was convertible tovec2; however,vec2::operator*(vec2)could still accept avec3on the right hand side if there was avec3 -> vec2conversionThis means that
vec2 * vec3andvec3 * vec2work differently, and such asymmetry is surprising and undesirable. Similarly, the cppreference examplecomplex + integershould work the same asinteger + complex, assuming there is an implicit conversion fromintegertocomplex. This would require+to be implemented as:If there was a member function
complex::operator+instead,integer + complexwould be impossible.Commutative operator overloads
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:
Similar to the
complexandintegerexample, you could avoid writing these operator overloads if there was a conversion fromdoubletovec3. 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.