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.