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