Continuing from my previous message to Mark, here's yet another refinement of the mechanism.

Assume we have this structure:

class Base {
public:
    void setup() {
        try {
            setupImpl();
        }
        catch(...) {
            cleanup();
        }
    }

    void cleanup() noexcept {
        cleanupImpl();
    }

protected:
    tail_extensible void setupImpl() {
        acquireResource1();
    }

    head_extensible void cleanupImpl() noexcept {
        releaseResource1();
    }
};

class Derived : public Base
{
protected:
    void setupImpl() extension {
        acquireResource2();
    }

    void cleanupImpl() noexcept extension {
        releaseResource2();
    }
};


This code can be translated as follows.

class Base {
// the public part stays the same
protected:
    virtual void setupImpl() {
        Base_setupImpl_extension();
    }

    void Base_setupImpl_extension() {
        acquireResource1();
    }

    // similar for the cleanup
};

class Derived : public Base {
protected:
    void setupImpl() override {
        Base_setupImpl_extension();
        Derived_setupImpl_extension();
    }

    void Derived_setupImpl_extension() {
        acquireResource2();
    }

    void cleanupImpl() noexcept override {
        Derived_cleanupImpl_extension();
        Base_cleanupImpl_extension();
    }

    void Derived_cleanupImpl_extension() noexcept {
        releaseResource2();
    }
};

So, instead of calling the base class _virtual_ directly from the derived class override, each class defines a "private" (protected here to illustrate the feasibility) implementation of its portion of the extension (let's call it the "extension fragment"), and then each override of the virtual method calls a sequence of non-virtual fragment methods defined in the base classes in top-down (tail extension) or bottom-up (head extension) order.

In order to address the diamond pattern, the compiler would have to sort the extension fragments in a depth-first manner (tail extension) or reversed, and then create an override that calls the fragments in sequence.  This does not require any change in the structure of a class or its v-table.  I do expect the compiler to contain the knowledge for sorting, because it uses it when creating constructors and destructors (for sure, a base destructor in the diamond pattern cannot be invoked more or less than one time).

In your example, we will have:

void A::setupImpl() override {
    D_setupImpl_extension();
    B_SetupImpl_extension();
    C_SetupImpl_extension();  //< B, C are ordered the same way as the inheritance is declared
    A_setupImpl_extension();
}


I hope that this does demonstrate the feasibility.

It also nicely follows Sutter's recommendations as pointed to by Tony without directly calling any virtual implementation in the base class, only with plain override.  But it really calls for automation.

As for coverage, at least in my experience, and in the context demonstrated in the first example -- resource acquisition and release, tail and head extension should be a fairly common pattern.  It follows the c-tor/d-tor pattern except with an actual named method.  I cannot rule out other orders, but to me there is enough ground for this form by itself.

The major question which is still open for me is actually about the return value (a  smaller question is about parameter passing).  Even if A::setupImpl() makes no use of the return values from D, B and C, they cannot be so simply ignored.  Consider the case in which setupImpl() returns a Boolean that indicates its success or failure.  Somehow, the final return value would have to be computed.  Supposedly, a natural form is to check each extension fragment's return value and if it fails, stop and return failure.  If all the extensions are completed successfully then return "success".

This, of course, is over-simplifying because the logic can be as complex as we want, and the return type itself can be as complex as we want.  So, arguably, it should involve some functional object, passed as a parameter of the extension declaration, which is applied to the return value of each extension fragment in order to form a final unified outcome.  It can aggregate individual fragment returns, reduce them by some binary operator (like && in the case of success indicator), or whatever.

And this does increase the complexity of the syntax etc., and should decrease the Committee's interest in continuing to pursue the proposal.

How about requiring an _extensible_ method to take its return type as a (const &, anyone?) parameter?  Then, each fragment can decide by itself how to deal with it and what to forward to the next fragment in line.  Maybe.  Something such as:

SomeReturnType A::setupImpl(SomeReturnType const & /*, more parameters?*/) override
{
    return A_setupImpl_extension(C_setupImpl_extension(B_setupImpl_extension(D_setupImpl_extension /*, ... for D?*/) /*, ... for B?*/) /*, ... for C?*/) /*, ... for A?);
}

Interim conclusion:

I think that I did demonstrate the theoretical feasibility of the head and tail extension pattern by showing a manual translation to "conventional" code which, in principle, can be automated.  I think that the use case of setup-acquire/cleanup-release is relevant, and that it's an interesting pattern to learn from.  Following this path, I also think that it is feasible to develop a generalization of return-value handling through a functional object.  Of course, none of this would cover ALL the unimaginable cases of mixing base class fragments in a derived class override, but as long as we stay with relatively uniform patterns, I have reason to believe that it is doable.

The down side is that the complexity of expressing head and tail extension when return values are involved may be frighteningly large, maybe too much for a language keyword, especially given the relatively narrow coverage of use cases.  But who knows, if we continue to look at this we might fall on a solution that's elegant enough.  Or maybe this feature can become a library function/class instead of a keyword.

Above all, to me this _is_ an interesting discussion, and I don't want to stop here.  But to continue, I will need some encouragement :-).  Will anyone -- Matthew or other -- care to continue?  If not, I'm not going to continue writing to myself.


Thanks for all the feedback,

Ofri

On Mon, May 20, 2019 at 7:10 PM Matthew Woehlke <mwoehlke.floss@gmail.com> wrote:
On 19/05/2019 05.02, Ofri Sadowsky via Std-Proposals wrote:
> Consider the even more complicated and, well, disagreeable, case of
> multiple inheritance with the diamond pattern, virtual inheritance and
> all.  If the final derived class calls its two (or more) bases, and each in
> turn calls the shared base, then the shared base is called twice while in
> reality it should have been called once.  An automated generation of the
> call sequence could remedy this.

But how would *that* work?

Let's say we have A : B, C; B: D; C : D.

In order to call D::foo() only once, the compiler would have to generate
*two* versions of B::foo() and C::foo(); one that auto-calls D::foo()
and one that doesn't. I think this would only work if derived methods
*only* ever call the versions that don't auto-call bases.

That said, as you later mentioned (snipped), I could see this being a
better motivation than the more general case.

> I am proposing two mechanisms for these common cases in the hope that this
> is where it stops.  Had there been more than two cases, then the idea does
> seem somewhat futile.  But IMHO these two cases are common enough and
> important enough to look at anyhow.

Again, what about the case that I need to call the base implementation
in the middle of my derived implementation? What about the case that the
method returns something, and the derived implementation needs to use or
modify that return value?

I'm concerned that pure-head and pure-tail only cover a fraction of use
cases. I'm also concerned that the rest of the committee will feel that
the work of adding a language feature does not justify the purported
benefit, when a compiler warning would achieve most of the benefit with
far less cost.

--
Matthew


--
Ofri Sadowsky, PhD
Computer Science Consulting and Training
7 Carmel St., #37
Rehovot  76305
Israel
 
Tel: +972-77-3436003
Mob: +972-54-3113572