Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the closest thing in C++ to retroactively defining a superclass of a defined class?

Suppose I have the class

class A {
protected:
    int x,y;
    double z,w;

public:
    void foo();
    void bar();
    void baz();
};

defined and used in my code and the code of others. Now, I want to write some library which could very well operate on A's, but it's actually more general, and would be able to operate on:

class B {
protected:
    int y;
    double z;

public:
 void bar();
};

and I do want my library to be general, so I define a B class and that's what its APIs take.

I would like to be able to tell the compiler - not in the definition of A which I no longer control, but elsewhere, probably in the definition of B:

Look, please try to think of B as a superclass of A. Thus, in particular, lay it out in memory so that if I reinterpret an A* as a B*, my code expecting B*s would work. And please then actually accept A* as a B* (and A& as a B& etc.).

In C++ we can do this the other way, i.e. if B is the class we don't control we can perform a "subclass a known class" operation with class A : public B { ... }; and I know C++ doesn't have the opposite mechanism - "superclass a known class A by a new class B". My question is - what's the closest achievable approximation of this mechanism?

Notes:

  • This is all strictly compile-time, not run-time.
  • There can be no changes whatsoever to class A. I can only modify the definition of B and code that knows about both A and B. Other people will still use class A, and so will I if I want my code to interact with theirs.
  • This should preferably be "scalable" to multiple superclasses. So maybe I also have class C { protected: int x; double w; public: void baz(); } which should also behave like a superclass of A.
like image 416
einpoklum Avatar asked Jun 02 '17 12:06

einpoklum


3 Answers

You can do the following:

class C
{
  struct Interface
  {
    virtual void bar() = 0;
    virtual ~Interface(){}
  };

  template <class T>
  struct Interfacer : Interface
  {
    T t;
    Interfacer(T t):t(t){}
    void bar() { t.bar(); }
  };

  std::unique_ptr<Interface> interface;

  public:
    template <class T>
    C(const T & t): interface(new Interfacer<T>(t)){}
    void bar() { interface->bar(); }
};

The idea is to use type-erasure (that's the Interface and Interfacer<T> classes) under the covers to allow C to take anything that you can call bar on and then your library will take objects of type C.

like image 162
SirGuy Avatar answered Oct 19 '22 13:10

SirGuy


I know C++ doesn't have the opposite mechanism - "superclass a known class"

Oh yes it does:

template <class Superclass>
class Class : public Superclass
{    
};

and off you go. All at compile time, needless to say.


If you have a class A that can't be changed and need to slot it into an inheritance structure, then use something on the lines of

template<class Superclass>
class Class : public A, public Superclass
{
};

Note that dynamic_cast will reach A* pointers given Superclass* pointers and vice-versa. Ditto Class* pointers. At this point, you're getting close to Composition, Traits, and Concepts.

like image 21
Bathsheba Avatar answered Oct 19 '22 14:10

Bathsheba


Normal templates do this, and the compiler will inform you when you use them incorrectly.

instead of

void BConsumer1(std::vector<B*> bs)
{ std::for_each(bs.begin(), bs.end(), &B::bar); }

void BConsumer2(B& b)
{ b.bar(); }

class BSubclass : public B 
{
    double xplusz() const { return B::x + B::z; }
}

you write

template<typename Blike>
void BConsumer1(std::vector<Blike*> bs)
{ std::for_each(bs.begin(), bs.end(), &Blike::bar); }

template<typename Blike>
void BConsumer2(Blike& b)
{ b.bar(); }

template<typename Blike>
class BSubclass : public Blike 
{
    double xplusz() const { return Blike::x + Blike::z; }
}

And you use BConsumer1 & BConsumer2 like

std::vector<A*> as = /* some As */
BConsumer1(as); // deduces to BConsumer1<A>
A a;
BConsumer2(a); // deduces to BConsumer2<A>

std::vector<B*> bs = /* some Bs */
BConsumer1(bs); // deduces to BConsumer1<B>
// etc

And you would have BSubclass<A> and BSubclass<B>, as types that use the B interface to do something.

like image 5
Caleth Avatar answered Oct 19 '22 13:10

Caleth