For any of you who can help explain to me when and when not to overload an operator as a member of a class or struct for example or not as a member, like global one.
The thing is that I am learning all about vectors and 3D vectors etc for mathematical learning purposes and trying to simulate the common operations on them to better understand their behaviour when doing so.
So, I bought a book which lightly touches on the same aspect as I do but I got to the part of it where the author of the book have written a simple vector struct in C++ and got bunch of overloaded operators that some where declared and defined inside the struct and some weren't.
Let me give you this exact example that left me completely baffled. The following example will show two different overloaded operators by which the two of them do the same "overall" purpose (I think?) which is (vector/scalar multiplication), however, one is being a member of the struct and the other isn't.
struct Vector3D
{
float x, y, z;
Vector3D() = default;
Vector3D(float a, float b, float c)
{
x = a;
y = b;
z = c;
}
Vector3D& operator*=(float s)
{
x *= s;
y *= y;
z *= z;
return(*this);
}
Vector3D& operator/=(float s)
{
s = 1.0f / s;
x *= s;
y *= s;
z *= s;
return(*this);
}
float& operator[](int i)
{
return ((&x)[i]);
}
const float& operator[](int i) const
{
return ((&x)[i]);
}
};
inline Vector3D operator*(const Vector3D& v, float s)
{
return (Vector3D(v.x * s, v.y * s, v.z * s));
}
The one that I am specifically asking about is the
Vector3D& operator*=(float s)
{
x *= s;
y *= s;
z *= s;
return(*this);
}
And
inline Vector3D operator*(const Vector3D& v, float s)
{
return (Vector3D(v.x * s, v.y * s, v.z * s));
}
Please if anyone has a clear explanation to share it
I have of course tried the above code to see what difference they make and I have found couple of things where they differ:
is when I try to print out to the screen the final result of multiplying a vector with a scalar (any float number) using both methods, only the member overloaded operator will printed out right away. The one that outside the struct can not be printed out unless the return got assigned to another vector instance.
For example:
int main()
{
float s = 2; //Defining the scalar
// Invoking the first overloaded operator
Vector3D vecA(2, 2, 2);
vecA *= 3;
for(int i = 0; i < 3; ++i)
{
std::cout << vecA[i] << std::endl; // 6, 6, 6 (Worked)
}
// Now Invoking the global overloaded operator
Vector3D vecB(2, 2, 2);
vecB * s;
for(int i = 0; i < 3; ++i)
{
std::cout << vecB[i] << std::endl; // 2, 2, 2 (No change)
}
}
One reason to choose a non-member operator is symmetry. By symmetry, I mean that certain operators should be commutative.
Scalar multiplication is a good example. It is reasonable to want both
(s * vec)and(vec * s)to be allowable expressions. If you want to usesas the left operand, however, you must use a non-member function. You cannot use a member function, becausesis not aVector3Dobject. In fact, you must define twooperator*functions, one for(s * vec), and the other for(vec * s).Stream operators are another time when you must use non-member operator functions, because the left operand is not a Vector3D object.
The comments in the following program explain further. It contains several idioms you may find useful, including the hidden friend idiom. In addition, the program fixes the two problems identified by @Pete Becker:
operator*should be implemented by invokingoperator*=. Coding the same set of operations in two different functions is error-prone. Sooner or later, the two will get out of sync!operator[]functions in the OP invoke undefined behavior, because they make the unwarranted assumption that member variablesx,y, andzare stored as if they were the elements of an array. The code below uses an actual array.Output: