mic_e
mic_e

Reputation: 5840

Using base class operators for derived classes in C++11

Consider the example.

I have a container class (A), which overloads/implements all kinds of arithmetic operators (A::negate()).

I now wish to create derived classes (B and C).

B and C should have all operators implemented by A.

However, those operators should use derived-class objects as arguments.

The prototype for B::negate should be: B B::negate(), instead of the A B::negate().

The derived classes do not need any own fields, but may implement own methods (B::foo(), C::bar()). It is a requirement that B and C be incompatible, i.e., a B object can not be assigned to a C object, or used with any of C's operators.

Here is the example code, how I want it to work:

struct A {
        int val;

        A negate() {
                return A{-val};
        }
};

struct B: A {void foo(){}};
struct C: A {void bar(){}};

int main() {
        B obj0 = {5};
        B obj1 = obj0.negate();
}

I understand that this is probably impossible using standard inheritance, and might be something C++11 simply isn't capable of, so I'm asking for something as close as possible to it.

The currently best solution I've come up with involves not using inheritance at all, but instead adding an integer template parameter to the base class, defining derived classes as using B = A<1>;,using C = A<2>;, and implementing member methods only for some specializations (only A::foo<1>(){} and A::bar<2>(){}).

However, I'm highly unhappy with this solution.

Upvotes: 4

Views: 1113

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275840

template<typename Child>
struct A {
  Child* self() {
    static_assert( std::is_base_of< A<Child>, Child >::value, "CRTP failure" );
    return static_cast<Child*>(this);
  }
  Child const* self() const {
    static_assert( std::is_base_of< A<Child>, Child >::value, "CRTP failure" );
    return static_cast<Child const*>(this);
  }
  Child negate() {
    return Child{-val};
  }
};
struct B:A<B> {
  explicit B(int v):A<B>(v) {}
};

here, we inject information into the base class template about its child. B is then relatively free to be a normal class.

In the parent, you can get self() in order to access your this pointer as a B (or other derived class).

Another approach involves free functions. You write a free negate template function that checks if its argument is derived from A, and if so does the negate action, and returns the negative version of the type passed in.

A mixture of these also works, where your free function takes A<D>s and returns a D.

Upvotes: 4

user2249683
user2249683

Reputation:

Covariant return types:

#include <iostream>

struct A {
    virtual A& operator ! () { std::cout << "A" << std::endl; return *this; }
};

struct B : public A {
    virtual B& operator ! () { std::cout << "B" << std::endl; return *this; }
};

int main() {
    B b;
    A* a = &b;
    ! *a;
}

Upvotes: 1

Dale Wilson
Dale Wilson

Reputation: 9434

If you don't want to use templates:

Make A::negate() protected.

In B:

struct B : public A
{
   B & negate()
   {
      A:negate();
      return *this
   }
  /// and so on
}

Because negate is defined in B, it totally hides the negate defined in A so the B implementation gets called and can delegate to A.

If you plan to hide ALL of A from the user, then B should contain an A rather than inheriting from it. (make A::negate public again)

struct B
{
   private:
   A m_a;
  public:
   B & negate()
   {
      m_a.negate();
      return *this
   }
  /// and so on
}

Upvotes: 0

Related Questions