I am learning C++ and I was wondering if I could gain some insight into the preferred way of creating binary operators that work on instances of two different types. Here is an example that I've made to illustrate my concerns:
class A;
class B;
class A
{
private:
int x;
public:
A(int x);
int getX() const;
int operator + (const B& b);
};
class B
{
private:
int x;
public:
B(int x);
int getX() const;
int operator + (const A& A);
};
A::A(int x) : x(x) {}
int A::getX() const { return x; }
// Method 1
int A::operator + (const B& b) { return getX() + b.getX(); }
B::B(int x) : x(x) {}
int B::getX() const { return x; }
// Method 1
int B::operator + (const A& a) { return getX() + a.getX(); }
// Method 2
int operator + (const A& a, const B& b) { return a.getX() + b.getX(); }
int operator + (const B& b, const A& a) { return a.getX() + b.getX(); }
#include <iostream>
using namespace std;
int main()
{
A a(2);
B b(2);
cout << a + b << endl;
return 0;
};
If I would like to have symmetry among the two types, which method is the best approach in the above code. Are there any possible dangers in choosing one method over the other? Does this vary with the return type? Please explain! Thank you!
The best way is to define (outside of either class)
int operator+ (const A& a, const B& b)
, and make it a friend function of both classes if needed. In addition, defineTo make it symmetric.
The big risk with this approach is that people tend to perceive + as a symmetric operator. The way this is written, it is not (unless your implementations re the same).
At a minimum, you should overload + as an external binary operator (not as a member), and then play with overloading it several times.
You have to be careful, though, to make sure that nothing becomes ambiguous.
Can you explain what you're trying to do? I can't think of many cases of different types where it makes sense to have the symmetric heterogenous operators.
I read in a comment that your intended use is adding vectors and matrices. Maybe you should consider using only matrices where vectors are one dimensional matrices. Then you are left with just one type and one set of operators:
If you want to keep the vector class then you should consider whether you also want a transposed vector (maybe transpose is just an internal property of vector).
The set of operations is not really symmetric:
and you should offer those three operations (assuming transpose is a property of vector):
All as free functions if possible. Even if the above set is not symmetric, neither is the real world and your users will expect it.
The main argument for method 2 is that you get implicit type conversion on both operands, not just the second one. This might save confusion somewhere down the line.
Speaking of which, your example code defines implicit conversions from int to A and from int to B, via the 1-arg constructors on both classes. This could result in ambiguity later. But if you left out the "explicit" for brevity, fair enough.
I agree with Uri's warning, though: if you find yourself doing this, you may be writing an API that others will find confusing. How come an A plus a B is an int? Does it really make things easier for users that they are adding a and b, rather than calling getX themselves and adding the results?
Is it because users know perfectly well that A and B are wrappers for ints? If so, then another option is to expose conversions from A to int and B to int, via operator int(). Then a+b will return an int for a sensible reason, and you'll get all the other arithmetic operators too: