Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this use pattern of virtual inheritance for "method injection" a known paradigm?

Yesterday, I came across this question: forcing unqualified names to be dependent values Originally, it seemed like a very specific question related to broken VC++ behaviour, but while trying to solve it, I stumbled upon a use pattern of virtual inheritance I hadn't come across before (I will explain it in a second, after telling you the question I have). I found it interesting, so I looked for it on SO and google, but I couldn't find anything. Maybe, I just don't know the right name for it ("method injection" was one of my guesses) and it is actually widely known. This is also part of my question for the community: Is this a common use pattern or a special case of another known paradigm? Do you see any problems/pitfalls that can be avoided by a different solution?

The problem this pattern can solve is the following: Suppose you have a class Morph with a method doWork(). Within doWork(), several functions are called whose implementation is supposed to be selectable by the user (that's why the class is called Morph). Let's call those called functions chameleons (since the Morph class doesn't know what they will be in the end). One way to achieve this would of course be to make the chameleons virtual methods of the Morph class, so the user can derive from Morph and override selected methods. But what if the user is expected to use different combinations for selecting the implementation for different chameleons. Then, for every combination, a new class has to be defined. In addition, what if there are multiple Morph-like classes where the same functions are supposed to be chameleons? How can the user reuse the replacements she already implemented?

As for the "multiple classes have to be defined" problem, immediately, templates leap into one's mind. Can't the user select the chameleon implementations he wants by passing classes as template parameters which define the desired implementation? I.e. something like Morph<ReplaceAB> which should effectively replace the chameleons A() and B() in doWork() with some implementation, leaving the possible other chameleons, e.g., C(), untouched. With C++11 and variadic templates, even the combinations would not be a problem: Morph<ReplaceAB, ReplaceC, WhateverMore...> Well, that's exactly, what this pattern can do (see below for explanation):

#include <iostream>

using namespace std;

// list all chameleons, could also be make some of them
// pure virtual, so a custom implementation is *required*.
struct Chameleons
{
  virtual void A() { cout << "Default A" << endl; }
  virtual void B() { cout << "Default B" << endl; }
  virtual void C() { cout << "Default C" << endl; }
};

// Chameleon implementations for A and B
struct ReplaceAB : virtual Chameleons
{
  virtual void A() { cout << "Alternative A" << endl; }
  virtual void B() { cout << "Alternative B" << endl; }
};

// Chameleon implementation for C
struct ReplaceC : virtual Chameleons
{
  virtual void C() { cout << "Alternative C" << endl; }
};

// A(), B(), C() in this class are made chameleons just by
// inheriting virtually from Chameleons
template <typename... Replace>
struct Morph : virtual Chameleons, Replace...
{
  void doWork() {
    A();
    B();
    C();
    cout << endl;
  }
};

int main()
{
  //default implementations
  Morph<>().doWork();
  //replace A and B
  Morph<ReplaceAB>().doWork();
  //replace C
  Morph<ReplaceC>().doWork();
  //replace A, B and C;
  Morph<ReplaceAB,ReplaceC>().doWork();
}

The output of which is the following:

Default A
Default B
Default C

Alternative A
Alternative B
Default C

Default A
Default B
Alternative C

Alternative A
Alternative B
Alternative C

Seeing this working solution only, the problem with the above idea is actually not so obvious: Couldn't Morph just derive from the classes specified as template parameters, so the chameleons A(), B() and C() are just taken from whatever Morph inherits from? This is actually not possible, because the calls to the chameleons do not depend on the template parameters, and such non-dependent names are not looked up in dependent inherited classes (try it, if you want). This means, we somehow have to achieve that the chameleon calls bind to something that can later be replaced by the desired implementation.

That's where the virtual inheritance comes in: By letting Morph inherit from Chameleons (non-dependent on the template parameters), the unqualified chameleon calls in doWork() bind to the virtual functions in Chameleons. Because Morph and the Replacement classes inherit virtually from Chameleons, there will only be a single Chameleons object in any Morph object, and the virtual function calls will be dispatched at run-time to the implementation in the most derived class, which we "smuggle in" through the templated inheritance. So, although the unqualified chameleon names in doWork() cannot be resolved to the desired implementation at compile time (in accordance with the standard), they can still be called by a layer of indirection through a virtual base class. Funny, huh? (Unless you tell me this is much easier to do in a different way or the pattern is widely known.)

like image 648
Oguk Avatar asked Jun 20 '14 02:06

Oguk


1 Answers

Your solution works fine. Virtual inheritance avoids ambiguity errors. Variadic template brings elegant instantiation syntax. Instead of something like:

 class M1 : public ReplaceAB, ReplaceC {} i1;
 i1.doWork();

you have just one line:

Morph<ReplaceAB, ReplaceC>().doWork();

From my point of view the proposed pattern is not common. A the same time, is this something really new? Well, there are millions and millions lines of code... The chance that somebody used something similar is not zero at all. Most likely you will never know this for sure.

like image 77
Kirill Kobelev Avatar answered Nov 09 '22 03:11

Kirill Kobelev