C++ Logo

std-proposals

Advanced search

Re: Add a specialized "Extension" concept to the inheritance syntax

From: Ofri Sadowsky <sadowsky.o.phd_at_[hidden]>
Date: Wed, 29 May 2019 09:43:20 +0300
Hi Matthew & others,

I have not seen any continuation of this discussion on your side. Without
your responses, I probably will not continue it myself. But will you be so
kind to reply and tell me if you have an interest in the continuation or I
should just drop it? Just for the sake of closure, if nothing else.

Sincerely,

Ofri Sadowsky

On Mon, May 20, 2019 at 11:23 PM Ofri Sadowsky <sadowsky.o.phd_at_[hidden]>
wrote:

> 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_at_[hidden]>
> 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
>


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

Received on 2019-05-29 01:45:16