Techniques for post-facto multiple inheritance

The other week on the std-proposals mailing list, TPK Healy asked for a way of dealing with the following classical-polymorphism scenario, which arises when dealing with wxWidgets.

Throughout this post, I’ll use non-wxWidgets-flavored identifiers; but if you want to translate this example into wxWidgets, the mapping is

  • TextwxTextEntry
  • ColorfulwxControl
  • set_textSetValue
  • set_colorSetBackgroundColour
  • Placard, BillboardwxComboBox
  • ColorfulTextwxTextCtrl (sort of)
  • TelevisionwxSearchCtrl

Suppose we have a bunch of types of widgets in our program. Some of those widgets involve Text, and some of them are Colorful. Some of them, like Placard and Billboard, are both.

struct Widget {
    virtual ~Widget() = default;
};

struct Text : virtual Widget {
    void set_text() { do_set_text(); }
private:
    virtual void do_set_text() = 0;
};

struct Colorful : virtual Widget {
    void set_color() { do_set_color(); }
private:
    virtual void do_set_color() = 0;
};

struct Placard : Colorful, Text {
private:
    void do_set_text() override { puts("P::DST"); }
    void do_set_color() override { puts("P::DSC"); }
};

struct Billboard : Text, Colorful {
private:
    void do_set_text() override { puts("B::DST"); }
    void do_set_color() override { puts("B::DSC"); }
};

Assume that we aren’t allowed to change anything above this line.

Now we want to write a polymorphic free function named set_text_and_color that sets the text and color of its argument object. This of course requires that the argument object be derived from Text and also from Colorful. The “obvious” way to do this is to introduce an abstract base class corresponding to the interface required by our polymorphic function:

struct ColorfulText : Colorful, Text {};

void set_text_and_color(ColorfulText *p) {
    p->set_text();
    p->set_color();
}

int main() {
    Placard placard;
    set_text_and_color(&placard);
}

Sadly, this doesn’t compile. Although Placard does derive from both Colorful and Text, it doesn’t derive from our made-up intermediate interface ColorfulText.

There are a few ways to make this work, though.

Solution 1. Templates

The simplest solution is to move set_text_and_color’s definition into a header file and make it a function template. Here I show a C++20 constrained template (just to prove it’s possible), but removing the constraint doesn’t break anything.

template<class T>
    requires std::derived_from<T, Colorful> &&
             std::derived_from<T, Text>
void set_text_and_color(T *p) {
    p->set_text();
    p->set_color();
}

int main() {
    Placard placard;
    Billboard billboard;
    set_text_and_color(&placard);
    set_text_and_color(&billboard);
}

This approach simply asks the compiler to stamp out two different functions — set_text_and_color(Placard*) and set_text_and_color(Billboard*) — each with its own entry point and codegen and everything. This requires that the definition of set_text_and_color be visible at the call-site. In practice that means “templates always go in header files.”

Making things into templates isn’t always feasible; so let’s look at some non-template alternatives.

Solution 2. Open-coded dynamic_cast via root object type

When you hear “classical polymorphism” and “ad-hoc icky escape hatch,” you should immediately be thinking “dynamic_cast”; and indeed we can use dynamic_cast here, although I don’t recommend it. (Godbolt.)

void set_text_and_color(Widget *p) {
    dynamic_cast<Text*>(p)->set_text();
    dynamic_cast<Colorful*>(p)->set_color();
}

int main() {
    Placard placard;
    Billboard billboard;
    set_text_and_color(&placard);
    set_text_and_color(&billboard);
}

The function set_text_and_color(Widget*) claims to accept any kind of Widget, but in fact it works only for widgets that derive from both Colorful and Text.

Non-solution: dynamic_cast without root object type

The above implementation works only because we already had a common “root object” type Widget from which both Placard and Billboard already derived. If such a root object type didn’t exist, as far as I can tell, we’d be out of luck. You might think (as I did) that we could fix it up with just a light dusting of undefined behavior…

void set_text_and_color(void *p) {
    struct Dummy { virtual ~Dummy() = default; };
    Dummy *pd = static_cast<Dummy*>(p); // Lie to the compiler
    dynamic_cast<Text*>(pd)->set_text();
    dynamic_cast<Colorful*>(pd)->set_color();
}

…but in fact this won’t work (Godbolt). The problem is that dynamic_cast isn’t allowed merely to trace a path up from the MDT to the destination type (e.g. Text) via publicly accessible relationships; it’s also required to trace its path down from the static source type of the pointer (e.g. Dummy), avoiding relationships that are in fact private or protected. That is (Godbolt):

struct A { virtual ~A() = default; };
struct B { virtual ~B() = default; };
struct C : public A, private B {};

int main() {
    B *pb = (B*)new C;
    assert(dynamic_cast<A*>(pb) == nullptr);
}

In practice, when dynamic_cast checks to make sure it’s not accidentally pathing through a private relationship, it also naturally rejects any situation where there is no path from the static source type down to the dynamic MDT. This situation never arises in conforming C++ code, but it’s exactly what we’re doing with Dummy above. So this Dummy idea doesn’t actually work in practice, let alone in theory!

Solution 3. Type erasure

The previous approach tried to erase everything about the input type except its ability to dynamic_cast to either Text* or Colorful*. What if we made that interface explicit in our code? (Godbolt.)

struct ColorfulTextPtr {
    template<class T>
        requires std::derived_from<T, Colorful> &&
                 std::derived_from<T, Text>
    ColorfulTextPtr(T *p) : pc_(p), pt_(p) {}

    Colorful *asColorful() const { return pc_; }
    Text *asText() const { return pt_; }
private:
    Colorful *pc_;
    Text *pt_;
};

void set_text_and_color(ColorfulTextPtr p) {
    p.asText()->set_text();
    p.asColorful()->set_color();
}

int main() {
    Placard placard;
    Billboard billboard;
    set_text_and_color(&placard);
    set_text_and_color(&billboard);
}

Notice that we could generalize ColorfulTextPtr into some kind of ad-hoc-multiple-inheritance-encapsulating pointer template. In the original std-proposals thread, the name proposed was “chimeric_ptr<Ts...>.” Godbolt:

template<class... Ts>
struct ChimericPtr {
    template<class MDT>
        requires (std::derived_from<MDT, Ts> && ...)
    ChimericPtr(MDT *p) : ps_{(Ts*)p...} {}

    template<class T>
        requires (std::same_as<T, Ts> || ...)
    operator T*() const { return std::get<T*>(ps_); }
private:
    std::tuple<Ts*...> ps_;
};

void set_text_and_color(ChimericPtr<Colorful, Text> p) {
    static_cast<Text*>(p)->set_text();
    static_cast<Colorful*>(p)->set_color();
}

int main() {
    Placard placard;
    Billboard billboard;
    set_text_and_color(&placard);
    set_text_and_color(&billboard);
}

Alternatively, we could type-erase even more of the original type, to the point where we don’t even care whether it has that particular inheritance relationship or not, as long as it supports the set_text and set_color member functions. See “Type-erased UniquePrintable and PrintableRef (2020-11-24) for details of that approach.

One thing this approach loses is the idea that the function argument p passed to set_text_and_color ought to actually be a pointer to an object that is both Colorful and Text. We had that in the template and Widget/dynamic_cast-based approaches, but here our actual argument has changed from a simple pointer to a class type: struct ColorfulTextPtr. Woe betide the naïve client programmer who tries to static_cast<void*>(p) or asks for typeid(*p)!

Solution 4. Adapter Pattern

We can get back that useful inheritance relation of p by using an even more Java-like “design pattern”: the Adapter Pattern. This is essentially the same as the type-erasure approach used by UniquePrintable, but with all the plumbing exposed to view. We start by defining the polymorphic interface that we wanted all along:

struct ColorfulText : Colorful, Text {};

void set_text_and_color(ColorfulText *p) {
    p->set_text();
    p->set_color();
}

This polymorphic function will work out of the box with objects that implement ColorfulText directly:

struct Television : ColorfulText {
private:
    void do_set_text() override { puts("T::DST"); }
    void do_set_color() override { puts("T::DSC"); }
};

For all other types, there’s ColorfulTextAdapter (Godbolt):

template<class T>
    requires std::derived_from<T, Colorful> &&
             std::derived_from<T, Text>
struct ColorfulTextAdapter : ColorfulText {
    explicit ColorfulTextAdapter(T *p) : p_(p) {}
    ColorfulText *addr() { return this; }
private:
    void set_color() override { p_->do_set_color(); }
    void set_text() override { p_->do_set_text(); }
    T *p_;
};

int main() {
    Placard placard;
    Billboard billboard;
    Television television;
    set_text_and_color(ColorfulTextAdapter(&placard).addr());
    set_text_and_color(ColorfulTextAdapter(&billboard).addr());
    set_text_and_color(&television);
}

Previously on this blog:

Posted 2022-12-01