Inline classes.

So, lets get started with some real blogging around C++ standard proposals. I will start out not with one of my own proposals, although I did try to find a solution to this problem before. I'm experimenting also with how to get the text into blogger in a good and easy way. This text was written using Texts, a markdown editor, and then I converted that to HTML and pasted into the html view of the blogging web page. Results is not that good, but somewhat useful. I will try to improve this way of working for future posts. One big problem seems to be that blogger removes the empty lines between paragraphs, in the markdown there is quite a long break after this paragraph for instance, and in the code examples I must place one empty line at the top and two at the bottom to get it to look this decent.

The problem relates to nested classes. There are many APIs which are designed so that the user is supposed to subclass a certain base class to modify a behavior or get a callback called. One possible way to get this done in your class is to inherit from the API base class. However, this is rarely a case where your class is in a is-a relationship with the API base class, instead inheritance (most often multiple inheritance) is used as a shortcut to creating a specific subclass of the API base class and let it forward to your class. This shortcut is taken for two reasons, one is pure laziness, that it takes some typing to create the subclass, and one is performance as the subclass has to have a pointer to your object which has both a time and size penalty. Here is an example where MyClass needs to capture a tick from a Timer class and call a method on the m_clientManager object.

With the multiple inheritance style this could be implemented like this:

class MyClass : public MyBase, Timer {
public:
    void tick() override { m_clientManager.checkTime(); }

private:
    ClientManager m_clientManager;
};

As you can see this is concise but the fact that tick() is an override from Timer and not from MyBase is not very clear, and it is easy to overlook the fact that MyClass inherits from Timer. As the proposer noted it is also strange that this inheritance prevents any subclass of MyClass from inheriting from Timer in turn, which is due to the language rule that you can’t inherit the same class twice. This in turn is probably as you can’t then define from which of those bases you want to take a member access! Clearly in this case the problem is that MyClass is not a Timer. It should have a timer. To implement that today requires code similar to this:

class MyClass : public MyBase {
private:
    struct MyTimer : public Timer {
        MyTimer(MyClass& outer) : m_outer(outer) {}
        void tick() override { m_outer.m_clientManager.checkTime(); }

    private:
        MyClass& m_outer;   
    } m_timer{*this};

    ClientManager m_clientManager;
};

That’s considerably more verbose and the m_outer pointer is not really needed as the compiler knows the offset between m_timer and its containing MyClass object.

The original proposal to correct this had the somewhat scary notion that if a by value member has a virtual method this method can be overridden in the outer class without actually creating a new subclass of the member’s type, just changing a vtable entry of this particular member (in all its instances). This reinstates quite a lot of elegance and as such has something speaking for it:

class MyClass : public MyBase {
public:
    Timer m_timer;
    void tickImpl() override(m_timer.tick()) { m_clientManager.checkTime(); }
    ClientManager m_clientManager;
};

However, the fact that tick() is overridden in the Timer class and not in MyBase is again not that obvious, as it only appears inside the override() parenthesis. Also I never understood why the tick() signature had to be repeated twice. This is the proposal as I got to see it on the std-proposals@isocpp.org mailing list.

This proposal provoked quite a lot of criticism, mostly motivated by the statement that m_timer is not of a subclass of Timer but of the same class. The original proposer made it clear that this fact was not an important part of the story which instead focused on the terseness of the code and the absence of unwarranted multiple inheritance and extra reference member. Unfortunately the std-proposals list is often hostile if you don't get everything right in the first post, and if you don't have a known name you are presupposed to be lesser knowing. Newcomers to the list are however rarely newcomers to the C++ language and usually have as good ideas as anyone. Some writers seem more interested in bragging about their deeper knowledge of the obscurest corners of the language than to actually carry it forwards.

I thought that the proposer deserved praise for trying to solve the original problem which indeed is something that crops up often. However, I agreed with the criticism that the inheritance from the Timer class should be visible in the code. I argued that this feature should be connected to the solution of the general inner class problem and the indirect inheritance ideas used to solve the operator.() conundrum, but this didn’t pan out to a terse enough solution. I will write about these problems (that do seem to have a common solution) in a later post.

My proposal however spurred to original proposer (who is still anonymous to me) to think again and s/he came up with a new idea: inline nested classes. With this the example would look something like this:

class MyClass : public MyBase {
public:
    inline class : Timer {
        void tick() override { m_clientManager.checkTime(); }
    } m_timer;

    ClientManager m_clientManager;
};

As you can see this is almost as terse as the original proposal, but it places the overridden method in the Timer subclass where it belongs. What the inline keyword means on a nested class is that there may be only one by value instance and that this instance has access to the surrounding object without any special prefixing unless there is an ambiguity, in which case the regular class name prefixing is used to disambiguate.

I think this is a nice extension of the language and I don’t think it should create any parsing problems. The compiler would have to check when class instances are created that their classes are not inline but that should be trivial. In this example the subclass of Timer is anonymous which makes it hard to create such instances anyway.

I think that in its present form this proposal is worth pursuing for standardization. It solves a common use case, is explainable, easy to implement in compilers and improves performance.

Kommentarer

Populära inlägg i den här bloggen

Starting a new blog