31

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.
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • So, you want `A` to inherit from `B` without actually changing `A`? – NathanOliver Jun 02 '17 at 13:04
  • @NathanOliver: Yes. To refine that sentence a bit, I want A and B to behave as though A inherits B in code that knows about them both. – einpoklum Jun 02 '17 at 13:07
  • 4
    Isn't your `class B` more of a contract? Could you use concepts/templates on the consuming library side then? – Zdeněk Jelínek Jun 02 '17 at 13:10
  • @ZdeněkJelínek: Maybe. I know contracts are being worked on by the standards committee, but I haven't read the papers and it's not in the language yet (not even in C++17). – einpoklum Jun 02 '17 at 13:12
  • Maybe [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) pattern might suit your situation? – Galik Jun 02 '17 at 13:29
  • 2
    @Galik CRTP is only useful if both the base and the derived know what’s going on. `A` is not a template, so you can’t use CRTP to inherit from it, and it already doesn’t inherit from `B`. – Daniel H Jun 02 '17 at 13:30
  • 1
    Instead of using something inheritance-like with pointers, would it work if the compiler just new how to convert from an `A` to a `B` directly? If so, then in `B`, you can write an implicit conversion constructor. – Daniel H Jun 02 '17 at 13:34
  • Can you add sample usage? What's stopping you from deriving `struct B : A` and then redefining the functions from `A` (not trying to override but hiding) – AndyG Jun 02 '17 at 13:41
  • @AndyG That would defeat the point of making it general – Daniel H Jun 02 '17 at 13:43
  • 1
    @ZdeněkJelínek probably has the best idea. You don’t need an actual contract as defined by the language; just make everything that would take a `B` instead be a template and act like all the required members are there. Compilation will fail if they aren’t. – Daniel H Jun 02 '17 at 13:45
  • Is there some reason not to simply apply an adapter pattern? Your use case sounds pretty much word for word like what an adapter pattern is meant to solve. – H Walters Jun 02 '17 at 16:01
  • @HWalters: The reason is that I was hoping I can do better than that. And, indeed, it seems I can in some respects. – einpoklum Jun 02 '17 at 16:24
  • @AndyG: About `struct B : A`: 1. Defeats generality 2. Perhaps having all of `A` is costly. Not in my example, but what if it, say, allocates something on the heap and keeps a unique_ptr to it? Or just much bigger than `B`? – einpoklum Jun 02 '17 at 16:29
  • In your library you may accept template type T instead of B. You won't have inheritance but it will still work on everything that have y,z and bar. Will it solve your problem? – RiaD Jun 03 '17 at 12:43
  • @RiaD: If it was a header-only library, then possible; but if it's compiled independently of the code using it, then no... which is why I mentioned `B*`s. Yours is a fair suggestion, though. – einpoklum Jun 03 '17 at 13:17

5 Answers5

28

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.

SirGuy
  • 10,660
  • 2
  • 36
  • 66
  • Shouldn't the interfacer have a `const T& t` field? Making a copy could be rather problematic. – einpoklum Jun 02 '17 at 13:14
  • 7
    @einpoklum With a reference you have object-lifetime difficulties instead. It's up to you which you prefer. – SirGuy Jun 02 '17 at 13:15
  • 1
    @einpoklum Or rather, up to your situation which is better. I think if you go the route of the reference, each derived type would have a predictable size. I feel like you could take advantage of that somehow to avoid allocating memory with `new` too. – SirGuy Jun 02 '17 at 13:18
  • 3
    Note that this technique can be done without dynamic allocation completely, especially if you just want a "view" or reference of an existing object with no lifetime extension. – Yakk - Adam Nevraumont Jun 02 '17 at 15:44
  • Why not go whole hog and make the API take an explicit "interface" that `B` implements and you provide a "wrapper" around `A` that implements it? – jpmc26 Jun 02 '17 at 19:11
  • @jpmc26 There are many nice things about "interfaces as an implementation detail" and how it relates to duck-typing through type erasure. Your solution will definitely work too, but I like not having to implement interfaces when I can avoid it (even if just because I don't have to have all my classes have virtual destructors and/or clone methods). – SirGuy Jun 02 '17 at 19:16
15

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.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 4
    OP mentions that he cannot change the definition of the derived class. – Zdeněk Jelínek Jun 02 '17 at 13:03
  • I still think the above could apply, by deriving from the derived class. Let me write that out in a moment when I'm out of this meeting. – Bathsheba Jun 02 '17 at 13:05
  • 1
    @ZdeněkJelínek is right; perhaps I'll edit to emphasize this point. If you still think a CRTP-based mechanism could do the trick, please be more specific. – einpoklum Jun 02 '17 at 13:08
  • This won't really work, will it? The class `Class` will have fields of `A` and fields of `Superclass` completely separate. If I access it through `Superclass*` I won't really change the state of `A` - but that what I would expect reading the OP question. – CygnusX1 Jun 02 '17 at 13:26
  • 1
    IMHO it's hard to give a full solution to the question as too many specifics are missing. My intention here is to offer a suite of possibilities in the general sense. But do please DV if I've missed something glaringly obvious. I will promise to delete if the score drops below zero. (I personally dislike the +10 / -2 asymmetry). – Bathsheba Jun 02 '17 at 13:27
  • 1
    Will the `dynamic_cast` find `A` if `A` doesn't have a virtual method? – SirGuy Jun 02 '17 at 13:28
  • @SirGuy: A brilliant question. I bet lunch on it being reachable if Superclass is polymorphic or the inheritance is marked virtual. If you ask that as a question, I'll upvote it. – Bathsheba Jun 02 '17 at 13:29
  • If I set `Class`'s A::y, wouldn't its `B::y` stay the same? – einpoklum Jun 02 '17 at 13:31
  • 1
    I wonder if that's standard C++. Do feel free to ask the community. – Bathsheba Jun 02 '17 at 13:34
  • 2
    Well, [here's](https://stackoverflow.com/q/44330470/1277769) the question I have about this – SirGuy Jun 02 '17 at 13:46
  • 1
    @einpoklum BTW, this answer has nothing to do with CRTP. CRTP is when the derived class passes *itself* as a template argument to a templated base class. – jamesdlin Jun 02 '17 at 22:24
  • Didn't the OP want A to act as the derived class, not the base class? – Luigi Ballabio Jun 03 '17 at 07:50
  • I'm still struggling to see how this solves the "pass `A` as `B`" problem. You only introduced a class `C` that is both `A` and `B` which seems awfully close to an adapter that uses inheritance instead of composition, but you also made it a template for some reason. Not to mention this won't even compile with what was given by the OP as both `A` and `B` have the same members. – Zdeněk Jelínek Jun 03 '17 at 08:55
5

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.

Caleth
  • 52,200
  • 2
  • 44
  • 75
4

There is no way to change the behaviour of a class without changing the class. There is indeed no mechanism for adding a parent class after A has already been defined.

I can only modify the definition of B and code that knows about both A and B.

You cannot change A, but you can change the code that uses A. So you could, instead of using A, simply use another class that does inherit from B (let us call it D). I think this is the closest achievable of the desired mechanism.

D can re-use A as a sub-object (possibly as a base) if that is useful.

This should preferably be "scalable" to multiple superclasses.

D can inherit as many super-classes as you need it to.

A demo:

class D : A, public B, public C {
public:
    D(const A&);
    void foo(){A::foo();}
    void bar(){A::bar();}
    void baz(){A::baz();}
};

Now D behaves exactly as A would behave if only A had inherited B and C.

Inheriting A publicly would allow getting rid of all the delegation boilerplate:

class D : public A, public B, public C {
public:
    D(const A&);
};

However, I think that could have potential to create confusion between code that uses A without knowledge of B and code that uses knows of B (and therefore uses D). The code that uses D can easily deal with A, but not the other way 'round.

Not inheriting A at all but using a member instead would allow you to not copy A to create D, but instead refer to an existing one:

class D : public B, public C {
    A& a;
public:
    D(const A&);
    void foo(){a.foo();}
    void bar(){a.bar();}
    void baz(){a.baz();}
};

This obviously has potential to mistakes with object lifetimes. That could be solved with shared pointers:

class D : public B, public C {
    std::shared_ptr<A> a;
public:
    D(const std::shared_ptr<A>&);
    void foo(){a->foo();}
    void bar(){a->bar();}
    void baz(){a->baz();}
};

However, this is presumably only an option if the other code that doesn't know about Bor D also uses shared pointers.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • There is code that uses A already, not just the definition A itself. If there wasn't, I could just forget about A and define something else and only use that... – einpoklum Jun 02 '17 at 13:26
  • @einpoklum so what is stopping you? Why not change the code? – eerorika Jun 02 '17 at 13:27
  • 1. Because that would make my question moot. 2. Because it's not my code to change; it turns out there are other people in the world... :-P – einpoklum Jun 02 '17 at 13:32
  • 1
    @einpoklum if you can change neither `A`, nor the code that uses `A`, then you really cannot change how the code behaves (at least in the scope of C++, unless `A` provides some hooks that modify its behaviour at runtime). So, if that is your situation, then perhaps the really question **is** moot. It seems that you can't have what you want. – eerorika Jun 02 '17 at 13:43
  • I'm writing B. Then there will be new code, which depends on B, and I can enforce restrictions on it, or at least guidelines. But A exists in and of itself and is happily used by people who shouldn't have to change their code on my account. – einpoklum Jun 02 '17 at 15:02
  • @einpoklum so, write B, and D. Use D and let A exist elsewhere. Others don't need to change - but you also cannot make them behave as if `A` did inherit `B`. – eerorika Jun 02 '17 at 15:24
  • @einpoklum then we return to your question being moot: Your requirements are in conflict and at least one of them must give: `A` must be modified, or your code must use something other than `A` (for example `D`), or your code that uses `A` must be OK with the fact that it doesn't inherit `B`. If none of the requirements are relaxed, then you've painted yourself into a corner. – eerorika Jun 02 '17 at 15:53
  • I asked a question, people are giving answers, some are useful to the case as I stated it... what else can I say? – einpoklum Jun 02 '17 at 16:25
  • @einpoklum I think you'll find that all the other answers are also relaxing your requirement of having to use `A`. Bathsheba for example suggests that you use `Class` instead of `A`. That is an approach of using CRTP to generate one of `D` variations - will be useful if you have need for many separate classes rather than one class with many bases. SirGuy suggest using `C` in place of `A` and Caleth suggests `BSubclass` although their approaches aren't as similar to my suggestions. None of the our answers allow conversion from `A*` to `B*` and all require you to use something in place of `A`. – eerorika Jun 02 '17 at 16:51
3

This seems more like static polymorphism rather dynamic. As @ZdeněkJelínek has already mentioned, you could you a template to ensure the proper interface is passed in, all during compile-time.

namespace details_ {
   template<class T, class=void>
   struct has_bar : std::false_type {};

   template<class T>
   struct has_bar<T, std::void_t<decltype(std::declval<T>().bar())>> : std::true_type {};
}

template<class T>
constexpr bool has_bar = details_::has_bar<T>::value;

template<class T>
std::enable_if_t<has_bar<T>> use_bar(T *t) { t->bar(); }

template<class T>
std::enable_if_t<!has_bar<T>> use_bar(T *) {
   static_assert(false, "Cannot use bar if class does not have a bar member function");
}

This should do what you'd like (i.e. use bar for any class) without having to resort to a vtable lookup and without having the ability to modify classes. This level of indirection should be inlined out with proper optimization flags set. In other words you'll have the runtime efficiency of directly invoking bar.

Andrew
  • 603
  • 1
  • 5
  • 13