Topic: Inheriting from reference


Author: "Andrei Polushin" <polushin@gmail.com>
Date: Wed, 8 Nov 2006 23:26:22 CST
Raw View
Alberto Ganesh Barbati wrote:
> Andrei Polushin ha scritto:
>> So I propose an explicit
>>
>>     class Widget : public Window& {     // *derive from reference*
>>     public:
>>         Widget()
>>             : Window(createWindow())    // initialize reference
>>         {}
>>         // ...
>>     };
>>
>
> I admit that I'm not too familiar with the problem. I don't have a copy
> of D&E and googling for "smart reference" gives a lot less meaningful
> hits than I expected.

I suspect that's why this proposal gains no feedback from others.

I've recently discovered that current proposal about operator.() is
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1671.pdf
and it contains both discussions and parts of D&E in a squeezed form.

That proposal have too many serious objections, to my taste, so I don't
even try to step into its shoes.


> However, the inheritance idea looks intriguing.
> Instead of adding a new syntax, I would prefer using a class template
> that, through some unspecified compiler magic, does the job. For example:
>
>   class Widget : public std::delegate<Window> {
>   public:
>     Widget()
>       : std::delegate<Window>(createWindow()) // initialize reference
>     {}
>     // ...
>   };
>
> Where std::delegate<T> is this "magic" class type that is initialized
> with a T& (without making a copy) and acts as a perfect delegate of the
> referenced T object.

D&E 12.7 describes the previously rejected proposal, similar to yours:

    class B {
        int b;
        void f();
    };

    class C : *p {
        B* p;
        void f();
    };

where : *p means that the object pointed to by p used as it would be
the base class for C.

    void f(C* q) {
        q->f();     // means q->p->f();
    }

Bjarne says that all users suffer from serious mistakes and confusion.
(Sorry, I'm translating D&E from Russian, thus the wording is inexact).

The reasons of the mistakes were:

    1. functions in C do not override the functions in B.
    2. function in B can neither use functions from C, nor transfer
       the control flow back into C.

You proposal suffers from the same objections.

My proposal is essentially the same as the "Delegation" from D&E, but
with reference and with the solution for the two problems described
there, using the dynamic type when neccessary (see my first augmenting
reply in this thread).

In addition, it solves the overloaded operator.() problem by
eliminating the need to overload it.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Alberto Ganesh Barbati <AlbertoBarbati@libero.it>
Date: Thu, 9 Nov 2006 11:19:33 CST
Raw View
Andrei Polushin ha scritto:
> Alberto Ganesh Barbati wrote:
>
> I've recently discovered that current proposal about operator.() is
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1671.pdf
> and it contains both discussions and parts of D&E in a squeezed form.

Thanks for the reference.

>
> That proposal have too many serious objections, to my taste, so I don't
> even try to step into its shoes.
>

Mmm... that might also explain why your proposal did not raise too much
attention.

>
> The reasons of the mistakes were:
>
>     1. functions in C do not override the functions in B.
>     2. function in B can neither use functions from C, nor transfer
>        the control flow back into C.
>
> You proposal suffers from the same objections.

I disagree that my proposal truly suffers from these objections. By
using a separate class instead of some obscure syntax, my proposal makes
it explicit that those are false expectations.

>
> My proposal is essentially the same as the "Delegation" from D&E, but
> with reference and with the solution for the two problems described
> there, using the dynamic type when neccessary (see my first augmenting
> reply in this thread).

What you say it's an advantage is also a disadvantage.

First: I may *not* want functions in C to override functions in B.
Allowing overriding is just a error prone as not allowing it, if it's
not explicit. The problem here is clarity: in both the D&E proposal and
yours it's not self-evident from the syntax whether overriding is
allowed or not.

Second: allowing overring makes the implementation of your proposal
quite problematic. An object of class C can never be a B, no matter
what, because there's no sub-object of type B in the layout of C. So
consider this case:

struct B
{
  virtual void f();
};

struct C : B&
{
  C(B& b) : B(b) {}
  virtual void f();  // assuming it overrides B::f
};

void foo(B& b)
{
  b->f();
}

int main()
{
  B b;
  C c(b);
  foo(c);  // Problem here!
}

To call foo(c), c need to be converted into a B&. You can't just pass a
reference to b to foo()! That's because a reference to b holds a vtable
of class B that refers to B::f. And you can't modify b either, tweaking
its vtable so that it uses C::f, because foo(b) shall still call B::f.

I'm not saying that this scenario can't be implemented as you propose,
in fact implementations are not required to use vtables at all. However
this example shows that your proposal interacts with the implementation
of "regular" virtual dispatch in non obvious ways that may require
existing valid approaches to be dropped. In that situation, I'm pretty
sure compiler implementors (and not only them) will lobby to reject the
proposal.

Ganesh

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Andrei Polushin" <polushin@gmail.com>
Date: Thu, 9 Nov 2006 16:25:00 CST
Raw View
Alberto Ganesh Barbati ha scritto:
> Andrei Polushin ha scritto:
>> The reasons of the mistakes were:
>>
>>     1. functions in C do not override the functions in B.
>>     2. function in B can neither use functions from C, nor transfer
>>        the control flow back into C.
>>
>> You proposal suffers from the same objections.
>
> I disagree that my proposal truly suffers from these objections. By
> using a separate class instead of some obscure syntax, my proposal makes
> it explicit that those are false expectations.

I tend to agree, but your class makes us expect that it is a
library-only solution, which is not true: it's a first "magic" class
proposed for C++. We may say that C++ suffers from the lack of
generalization in this area, if it has to invent some "magic" to screen
the hole.


> What you say it's an advantage is also a disadvantage.
>
> First: I may *not* want functions in C to override functions in B.
> Allowing overriding is just a error prone as not allowing it, if it's
> not explicit. The problem here is clarity: in both the D&E proposal and
> yours it's not self-evident from the syntax whether overriding is
> allowed or not.

Confirmed.


> Second: allowing overring makes the implementation of your proposal
> quite problematic. An object of class C can never be a B, no matter
> what, because there's no sub-object of type B in the layout of C.

I would like C& to be B&.

> So consider this case:
>
> struct B
> {
>   virtual void f();
> };
>
> struct C : B&
> {
>   C(B& b) : B(b) {}
>   virtual void f();  // assuming it overrides B::f
> };
>
> void foo(B& b)
> {
>   b->f();
> }
>
> int main()
> {
>   B b;
>   C c(b);
>   foo(c);  // Problem here!
> }
>
> To call foo(c), c need to be converted into a B&. You can't just pass a
> reference to b to foo()! That's because a reference to b holds a vtable
> of class B that refers to B::f. And you can't modify b either, tweaking
> its vtable so that it uses C::f, because foo(b) shall still call B::f.

Bad news, thank you :)

Looks like I have to inherit from special kind of reference that allows
us to modify the vtable of the underlying object: rvalue reference?

  struct B
  {
    virtual void f();
  };

  struct C : B&&
  {
    C(B&& b) : B(b) {}
    virtual void f();  // overrides B::f
  };

  void foo(B& b)
  {
    b->f();
  }

  int main()
  {
    B b;
    C c(std::move(b)); // modifies the vtable of b
    foo(c);
  }

With the std::move, the type modification is slightly more visible.
I'm not too familiar with the rvalue reference binding rules, so I
need some suggestion.

BTW, I can yield the "inheriting from lvalue reference" syntax to
non-overriding inheritance, i.e. for smart references, as long as
there is a demand for them.

Not sure, is the syntax looks clear now, or not yet.


--
Andrei Polushin

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]