Search code examples
c++inheritanceconstructorc++14member

Directly access members of members in a class interface without inheritance


The problem that I am facing is that in my application it matters in which order the members of my class are initialized in the constructor. And thus I need to use clumsy syntax to get the behaviour I am aiming for.

This should outline my problem (see below code): I want my class Widget to include the interface of class WidgetSignals from the user's point of view. I can't use inheritance because that requires me to initialize the inherited WidgetSignals before the member elements of the Widget which should be used to initialize WidgetSignals.

template<typename... Elems>
class WidgetSignals
{
    WidgetSignals(const std::tuple<Elems...> elems)
        : // initialize members using elems
    {}
    // members ...
};

template<typename... Elems>
class Widget : public WidgetSignals<Elems...>
{
    std::tuple<Elems...> elements;

    Widget(vec4 quad)
        : WidgetSignals<Elems...>(elements) // uh-oh! elements are not initialized yet!
        , elements(initalizeElements(quad))
};

Here are my previous solutions to this problem:

Use a mediator helper class:

template<typename... Elems>
class Widget : public WidgetSignals<Elems...>
{
    std::tuple<Elems...> elements;

    struct Data
    {
        Data(vec4 quad)
            : elems(initializeElements(quad))  // initialize elements here
        {}        
        std::tuple<Elems...> elems;
    };
    Widget(Data data)
        : WidgetSignals<Elems...>(data.elems) // bingo!
        , elements(data.elems)
    {}
};

Encapsulate WidgetSignals and expose references to WidgetSignals' members in Widget:

template<typename... Elems>
class Widget
{
    std::tuple<Elems...> elements;
    WidgetSignals<Elems...> signals;

    Widget(vec4 quad)
        : elements(initializeElements(quad))
        , signals(elements) // initialized after elements because of order of member declaration
    {}
    // WidgetSignal member references
    const typename WidgetSignals<Elems...>::SignalType& enter = signals.enter;
    const typename WidgetSignals<Elems...>::SignalType& leave = signals.leave;
    // ... remaining members
};

With both of these solutions I can then use the interface of WidgetSignals through a Widget:

Widget widget(vec4(0, 0, 20, 5));
foo(widget.enter);

But both of these solution are rather hacky and messy, so I would really like to have a better syntax for this, something like:

using signals;

or

using signals::enter;

in Widget. using WidgetSignals<Elems...>::enter; is actually something that works, but only if Widget already inherits from WidgetSignals and that would mean I had to use a mediator helper class again, which I'd like to avoid.


Solution

  • Just stick the elements you want to initialise first into a [private] base class you inherit from first. For example:

    template<typename... Elems>
    struct WidgetPbase {
        std::tuple<Elems...> elements;
    }
    template<typename... Elems>
    class Widget
        : private WidgetPbase<Elems...>
        , public WidgetSignals<Elems...>
    {
    public:
        Widget(vec4 quad)
            : WidgetPbase<Elems...>{initializeElements(quad)}
            , WidgetSignals<Elems...>(elements) {
        }
    };
    

    As base classes as inherited left-to-right/top-to-bottom this arranges for the elements member to be initialised at the point it is accessed. The only potential caveat is that virtual bases are initialised first: if WdigetSignals is a virtual base it may be necessary to stick a virtual in front of WidgetPbase.