How to overload C++ operator, but for only certain inputs? - c++

I have a class for which I would like to overload the addition operators. For my use case, it makes sense only to allow addition in the case where the a variable of each object is equal.
What is the best way to handle the case where they are not? Throw an exception, something else?
class A {
private:
int a, b;
public:
A(int a, int b)
:a(a), b(b) {}
A& operator+=(const A& rhs) {
if (this->a == rhs.a) {
this->b += rhs.b;
return *this;
}
else { //this->a != rhs.a
//what should I put here?
}
}
};
.
Edits:
These objects are created at run-time during file io.
These objects represent a data point in a spectrum. It only makes sense to add the intensities of two data points if they are at the same position.
a is limited to the range (-180.0, 360.0)

This smells like a is the property of the type, not a property of the value... What exactly does this class represent?
The minimally viable (IMHO) way to approach this is to make explicit the transition from an "anything goes" type to a "type compatible with a particular value of a". I.e.:
MyClass x(1,2), y(1,5);
x += y; // won't compile
x.makeCompatibleWith(y) += y; // will compile
It's usually a pessimization to have arithmetic operators like += throw. Instead, have something else assume the cost - then the cost is explicit, and you can keep += nothrow. It's also easy to search the project for costly operations (well, makeCompatibleWith is not super expensive, just more expensive than += since it adds the overhead of exception handling).
Assuming that the invalid cases are meant to be caught in testing, the makeCompatibleWith function could assert the condition it requires, but in release builds it would return some dummy object that turns the += into a no-op since it won't modify x - while still keeping += very simple and quick.
As to what exactly should makeCompatibleWith return: it's up to you. It can be a type that carries a reference, for example:
class MyClass
{
int a, b;
struct Internal
{
MyClass &val;
Internal(MyClass &val) : val(val) {}
MyClass &operator+=(const MyClass &o) noexcept {
val.b += o.b;
return val;
}
MyClass operator+(const MyClass &o) const noexcept {
return { val.a, val.b + o.b };
}
};
public:
MyClass() : a{}, b{} {}
MyClass(int a, int b) : a(a), b(b) {}
Internal makeCompatibleWith(const MyClass &o) noexcept {
thread_local static MyClass dummy;
assert(a == o.a);
if (a != o.a)
return { dummy };
return { *this };
}
};
Note that makeCompatibleWith would be undefined behavior when used from multiple threads if dummy wasn't thread-local.

Related

What is the meaning of assigning to return value of getter taking this pointer by value?

This doubt came to me when I jumped on an existing code and mistakenly used a getter to set a property,
obj.getProp() = otherProp;
instead of calling the setter,
obj.setProp(otherProp);
I did not realize the mistake because there was no error at compilation or runtime; the assignment resulted in a no-op.
So I came up with the following example, which outputs 337:
#include <iostream>
struct A {
int x = 0;
A(int x) : x(x) {}
A(A& a) : x(a.x) {}
void operator=(A const& other) { x = other.x; }
};
struct B {
A a{3};
int x{3};
A getAbyVal() { return a; }
A& getAbyRef() { return a; }
int getXbyVal() { return x; }
};
int main() {
B b;
std::cout << b.a.x; // this and the other two cout print what I expect, but...
b.getAbyVal() = A{7}; // ... I expected this to fail at compilation time in the first place...
//b.getXbyVal() = 3; // ... just like this fails.
std::cout << b.a.x;
b.getAbyRef() = A{7};
std::cout << b.a.x;
}
So my question is two folds:
what in b.getAbyVal() = A{7}; is different from b.getXbyVal() = 3; so that the former compiles and the latter doesn't (beside the fact that the types are A and int)?
changing void operator=(A const& other) { x = other.x; } to void operator=(A const& other) & { x = other.x; } makes b.getAbyVal() = A{7}; fail to compile. Why is this the case?
what in b.getAbyVal() = A{7}; is different from b.getXbyVal() = 3; so
that the former compiles and the latter doesn't (beside the fact that
the types are A and int)?
Surprisingly, the difference in types is exactly what makes one compile correctly, and other to fail.
A has an assignment operator defined for it, so compiler dutifully invokes it on the return value (only to discard the whole object later). But the code you wrote supports this. From the compiler view, some other interesting things might have happened in your assignment operator, despite the fact that the object will be eradicated (side effects in formal parlance).
With int as a return value, compiler knows there are no side effects of assigning value to int, so assigning any value to object which is to be eradicated immediately does not make any sense.
what in b.getAbyVal() = A{7}; is different from b.getXbyVal() = 3; so that the former compiles and the latter doesn't (beside the fact that the types are A and int)?
The difference is precisely that one functions returns a class type, and the other function returns a POD type. A temporary int, for example can't be assigned to:
42 = x; // error
so similarly the language disallows assigning to a temporary int returned from a function as well. This is not the default behavior for user-defined class types so assigning to a temporary A compiles:
A{} = x; // ok
changing void operator=(A const& other) { x = other.x; } to void operator=(A const& other) & { x = other.x; } makes b.getAbyVal() = A{7}; fail to compile. Why is this the case?
Adding a & at the end of is called a ref-qualifier, and allows a user-defined class to have the same semantics as a POD type when it comes to assigning to a temporary. Adding a & at the end of operator= constrains it to only be used on an l-value (basically, a named variable, or a reference returned from a function).
A{} = x; // now error
A a;
a = x; // still ok

C++ comparison operator overload confusing behavior

I have implemented two classes (parent and derived) comparison operator, but testing it I have noticed a confusing behavior in case of using pointers. In addition, I have some other questions about good practices.
Here it is the code:
struct A
{
int getN() { return _n; }
virtual bool equals(A &other) {
return getN() == other.getN();
}
friend bool operator==(const A &one, const A &other);
A(int n) : _n(n) { }
private:
int _n;
};
bool operator==(const A &one, const A &other) {
return one._n == other._n;
}
struct B : public A
{
friend bool operator==(const B &one, const B &other);
B(int n, int m = 0) : A(n), _m(m) { }
private:
int _m;
};
bool operator==(const B &one, const B &other) {
if( operator==(static_cast<const A&>(one), static_cast<const A&>(other)) ){
return one._m == other._m;
} else {
return false;
}
}
int main()
{
A a(10), a2(10);
B b(10, 20), b2(10, 20), b3(10, 30);
A *a3 = new B(10, 20);
bool x1 = a == a2; // calls A operator (1)
bool x2 = b == b2; // calls B operator (2)
bool x3 = a == b; // calls A operator (3)
bool x4 = b == a; // calls A operator (4)
bool x5 = b == *a3; // calls A operator (5)
bool x6 = a == b3; // calls A operator (6)
bool x7 = b3 == a; // calls A operator (7)
return 0;
}
Questions
Comparing A instances with B ones, the A class operator is called, is this the correct behavior?
The point 5 is the one that seems confusing to me. a3 is declared as A but instanced as B, but the class A operator is called. Is there any way to solve this?
If the operator was implemented as a instance method, depending on it is called with an A object or a B one, the executed method is different. For example:
a == b // executes A::operator==
b == a // executes B::operator==
I assume that this is confusing and error prone, and must be avoided. Am I right?
Comparing A instances with B ones, the A class operator is called, is this the correct behavior?
Yes, because this is the only applicable overload.
If the operator was implemented as a instance method, depending on it is called with an A object or a B one, the executed method is different. [...] I assume that this is confusing and error prone, and must be avoided.
Right, this is not a good idea, because equality operator must be symmetric. Although it is possible to implement it symmetrically with two separate operators, it introduces a maintenance liability in the code.
One approach to solve this is to expan your equals member function, and have each subclass implement equality for its own type:
struct A {
int getN() const { return _n; }
virtual bool equals(A &other) const {
return getN() == other.getN();
}
friend bool operator==(const A &one, const A &other);
A(int n) : _n(n) { }
private:
int _n;
};
struct B : public A
{
B(int n, int m = 0) : A(n), _m(m) { }
virtual bool equals(B &other) const {
return A::equals(*this, other) && _m == other._m;
}
private:
int _m;
};
bool operator==(const A &one, const A &other) {
return typeid(one)==typeid(two) && one.equals(two);
}
"At the heart" of this implementation is typeid operator, which lets you check for type equality at run-time. The virtual call to one.equals(two) happens only if the types of one and two are exactly the same.
This approach puts responsibility for equality comparison with the class itself. In other words, each class needs to know how to compare its own instances, and optionally rely on its base for comparison of its state.
Comparing A instances with B ones, the A class operator is called, is
this the correct behavior?
B& can be converted to const A& but A& can't be converted to const B&. So comparing a == b there's really only one choice.
"Class operators" isn't a thing. In your code you've implemented operator== as non-member functions, that you've also made friends of A and B.
The point 5 is the one that seems confusing to me. a3 is declared as A
but instanced as B, but the class A operator is called. Is there any
way to solve this?
Dereferencing a3 returns A&, not B&.
If the operator was implemented as a instance method, depending on it
is called with an A object or a B one, the executed method is
different. For example:
a == b // executes A::operator==
b == a // executes B::operator==
I assume that this is confusing and error prone, and must be avoided. Am
I right?
If operator== is implemented as an instance method it's called on the left hand operand. In the first case it's a, in the second it's b. So b == a is only valid if you've implemented bool B::operator==(const A&).
Comparing A instances with B ones, the A class operator is called, is this the correct behavior?
Since you do not have any virtual functions in A you only deal with function overloading which means that compiler decides which function to call at compile time. As B publicly inherited from A, pointer or reference to B can be implicitly converted to A but not vice versa. In this case it means "unless both arguments are B only first resolution would be called". Note when you dereference a pointer only type of pointer matters to determine type at compile time, so if A *pointer points to instance of A or B does not matter in this case at all, *pointer always has type A.
If you want functions to be called based on actual type you need to use virtual functions, details on how to do that for operator== can be found here implementing operator== when using inheritance

Adding const correctness

I have some code which has been written without regard for const correctness. Are there any circumstances in which changing this
class X
{
public:
X(X& rhs); // does not modify rhs
...
};
to this
class X
{
public:
X(const X& rhs);
...
};
would change the behaviour of an existing program? I know this change will allow code which doesn't currently compile to compile, but I'm interested if there is any circumstance in which code that already compiles would change it's behaviour.
Similar question, is there any merit in making this change instead?
class X
{
public:
X(X& rhs); // does not modify rhs
X(const X& rhs);
...
};
For copy constructor I don't think so. But note that in general, yes, declaration of const can affect which method is called. The only example that comes to mind is with array overloading - see e.g. this question.
In fact a copy constructor should, imho, always take a const reference as its argument.
X(X& rhs) { } // does not modify rhs
This does not allow to copy const objects and hence the following code will not compile.
While non-const objects can serve as const arguments, the other way round is impossible
X const test;
X new_x(test);
I can't imagine why someone should preclude the copy of a const object.
Concerning the changes you want to make:
Does the copy constructor rely on any X member function that is defined non-const?
This will work like a charm but permit copying const objects:
class X
{
private:
int a;
public:
X(X &rhs) { a = rhs.value(); }
int& value (void) { return a; }
};
The next example will not compile since rhs is const but value() is not const.
class X
{
private:
int a;
public:
X(X const &rhs) { a = rhs.value(); }
int& value (void) { return a; }
};
If you want to make your class const correct you'll probably have to examine the whole class.
It should only affect your in-class-implementations. Since I don't know a case where external code should rely on "non-constness" of a class member function.
Except when non-const-references are returned by any public member-functions as in my example.
The following snippet will do as intended.
class X
{
private:
int a;
public:
X(int const &b) : a(b) { }
X(X const &rhs) { a = rhs.value(); }
int const & value (void) const { return a; }
};
But be aware that this will interfere with any code like:
X test(100);
test.value() = 12;
This would work using int& value (void) { return a; } but fails with int const & value (void) const { return a; }.
You could of course provide both to be on the safe side.
Since you are changing the class that has the copy constructor I am assuming you can inspect the copy constructors code. If you can make this change and the copy constructor does not give a compiler error you are probably good. One conor case to consider is what the copy assignment operator does as there is no assurance which will be called especially in optimized code. So also make sure that your copy assignment will work with a const parameter.
djechlin is making an important point in his answer, although somewhat unclear, so I will try to explain better.
The constness of an object or reference affects overload resolution. For instance, if you have an object with a const based overload of a member function, different overloads will be chosen:
struct foo {
void do_stuff();
void do_stuff() const;
};
int main() {
foo f;
const foo& fr = f;
f.do_stuff(); // calls non-const version
fr.do_stuff(); // calls const version
}
Now, if one of the overloads has side-effects the other doesn't, you'd get different behavior after changing the signature, given that (or rather even though) the program compiles fine.

How do I use a chain of operator overloads without modifying the operands?

Let's say we have this class A:
class A
{
public:
int a;
A(int b)
{
a = b;
}
};
I would like to create a + overload such that I could use it like this
A a(1),b(2),c(3),&d;
d = a + b + c;
without modifying the content of each object. The next logical thing would be allocating a new chunk of memory each time like this:
A &operator+ (const A &b)
{
A *c = new A(a+b.a);
return *c;
}
But this would create a new problem: intermediate results are lost, causing memory leaks.
I could have easily solved this problem by making a static function that takes three A object references and stores the sum of the first two in the third, but I'm willing to bet that there must be some way to make + overload happen the way I want.
So the question is: is there any way I could use a chain of operator overloads that do not modify the operands without causing memory leaks?
You can simply use pass by value and do this:
A operator+ (A other) //pass by value
{
other.a += a;
return other;
}
Or, since the member a is publicly accessible, you can (rather should) make operator+ a non-member function as:
A operator+(A left, A const &right)
{
left.a += right.a;
return left;
}
Notice that the first argument is accepted by value, and second by reference. In this way, you don't need to declare a local variable in the function. You can use the first parameter; after all it is local to the function, you can do whatever you want to do with it: in this case, we just add right.a to it, and return it.
A better design of class would be this: (read the comments)
class A
{
int a; //make it private
public:
A(int b) : a(b) //use member initialization list
{
}
A& operator+=(A const & other) //add `+=` overload, as member of the class
{
a += other.a;
return *this;
}
};
//and make `+` non-member and non-friend
A operator+(A left, A const & right)
{
left += right; //compute this in terms of `+=` which is a member function
return left;
}
There is no need to use pointers inside operator+. You can allocate the intermediate object in the stack and then return it:
A operator+ (const A &b)
{
A c(a+b.a);
return c;
}
Or just:
A operator+ (const A &b)
{
return A(a+b.a);
}
Or even simpler:
A operator+ (const A &b)
{
return a+b.a;
}
Since this implicitly calls A::A(int).
Note that I removed the reference from the return type. You can't return a non-const reference to a local.
Then you would use it this way:
A a(1),b(2),c(3),d;
d = a + b + c;
Note that d is no longer a reference.

A question about auto_ptr

template<class Y>
operator auto_ptr_ref<Y>() throw() {
return auto_ptr_ref<Y>(release());
}
It is part of implementation of class auto_ptr in standard library.
What does this means to do?
Why there is an "auto_ptr_ref" between "operator" and "()"?
I'll tell you why that conversion operator happens to be there. Well, look at this example:
struct A;
struct B {
explicit B(A&a):a(a){ }
A &a;
};
struct A {
A() { }
A(B b){ move_from(a); }
A(A &a) { move_from(a); }
operator B() { return B(*this); }
void move_from(A &a) {
std::cout << "A::A(#" << &b.a << ")" << std::endl;
}
};
int main() {
A a = A();
}
We have move semantics for our class A: In its copy constructor, we want to "steal" away some stuff from the other instance. For auto_ptr, that is the pointer that is managed, for us we just output a message instead. What is important is that we can't use a usual copy constructor:
A(A const& a) {
/* oops, a is const, we can't steal something from it! */
}
But if we change that to A(A &a), we won't be able to construct from a by-value/temporary A: Those can't be bound to a reference-to-nonconst:
A(A &a) {
}
...
A a = A(); // fail, because A &a = A() doesn't work
auto_ptr and our A class uses the trick that non-const member functions can still be called on temporaries/by-value A's. That is, we could also have written:
struct A {
...
B get_b() { return B(*this); }
...
};
...
A a = A().get_b();
But that works, we don't want to be bothered with that, of course. We want that it just works to assign a A() or the return value of a function returning an A by-value. So what auto_ptr and our A class uses is a conversion operator, which automatically figures out that when A is converted to B, then we could construct a A instance using the B we created temporarily.
That is the conversion operator in action, casting from auto_ptr to auto_ptr_ref<Y>.