I have a large class, Base
, that implements several different binary operations, some overloading the operators like *=
, *
, /
etc, and some extra ones. The typical return type involves Base
explicitly, for example:
class Base {
private:
std::vector<unsigned int> _arr;
public:
// These operations do something with _arr
Base& operator *= (const Base& rhs);
friend Base operator *(Base lhs, const Base& rhs);
std::vector<Base> myoperation(const Base& rhs);
};
I have a derived class, Derived
, which simply adds extra structure, so it looks like this:
class Derived : public Base {
private:
std::vector<int> _arr_derived;
public:
// This operation does something with _arr and _arr_derived;
Derived& my_derived_operation(const Derived& rhs);
// These operations are exactly the same implementation as the one in Base!
Derived& operator *= (const Derived& rhs) {
Base::operator*=(rhs);
return *this;
}
friend Derived operator *(Derived lhs, const Derived& rhs) {
lhs *= rhs;
return lhs;
}
std::vector<Derived> myoperation(const Derived& rhs) // <---- what to do here?
};
What do I need to do with the operation myoperation
? That returns a vector. There is no extra logic on all operations that are inherited, the only difference is in the return type. Is there any way to avoid the code duplication? Even the declaration of the operations is bothersome.
I thought perhaps defining the base operations as template functions returning the same type as the argument, and requiring the template parameter to be a derived class:
class Base {
// on C++20
template<class C> requires std::is_base_of_v<Base,C>
C& operator*=(const C& rhs);
}
Does this work? Even if it does, is there a better mechanism?
Edit to reply to Remy Lebeau's comment: I want to emphasize that all operations that appear both in Derived
and in Base
are meant to be the same, there is not extra logic except the returning type. The example of operator *=
already shows this, just calling the Base
version. A possible implementation, albeit stupid, of what I mean by myoperation
could be this:
std::vector<Base> Base::myoperation(const Base& rhs) {
std::vector<Base> ret{};
ret.push_back(rhs*rhs);
return ret;
}
std::vector<Derived> Derived::myoperation(const Derived& rhs) {
std::vector<Derived> ret{}
ret.push_back(rhs*rhs);
return ret;
}
With the templated version I could write only
class Base {
teplate<class C> requires std::is_base_of_v<Base,C>
auto myoperation(const C& rhs) {
std::vector<C> ret;
ret.push_back(rhs*rhs);
return ret;
}
};
And that's what I meant by the templated solution above, so that I do not need to even declare myoperation
on Derived
.