Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[Proposal] Inheriting from reference

2 views
Skip to first unread message

Andrei Polushin

unread,
Nov 4, 2006, 8:27:45 PM11/4/06
to
Hi,

Many years ago, there was a long-term discussion about overloading
operator.() for smart references. Neither idea was accepted, however.
Now let me propose a compromise solution for that old problem. Note
that I do not propose overloading operator.(), but suggest a more
idiomatic way to deal with the problem.

For the background information and problem statement, refer to

[1] B.Stroustrup, Design and Evolution of C++,
11.5.2 Smart references

[2] B.Stroustrup, Design and Evolution of C++,
12.7 Delegation

[3] Discussions in comp.std.c++
(search for "smart reference")


Use case
========

Imagine we have a GUI system that creates objects of type Window:

class Window {
Window();
public:
void f();
// ...
};

Window& createWindow(); // factory function

Window w& = createWindow(); // ok

We need to inherit from Window to create Widget objects, but Window
is not inheritable: it has private constructor:

class Widget : public Window { // error: cannot inherit
// ...
};

We actually need to inherit, because we need our Widget to have all
that thousands of Window member functions.

We can try to define the Widget as smart pointer, but what if we need
a smart pointer to the Widget itself?

class Widget : auto_ptr<Window> {
public:
Widget() : auto_ptr(&createWindow()) {}
void g();
}

auto_ptr<Widget> p(new Widget());
p->g(); // ok
p->get()->f(); // very, very, very ugly

Probably, we need a smart reference with overloaded operator.():

class Widget {
Window& w;
public:
Widget() : w(createWindow()) {}
Window& operator.() { return w; }
// ...
};

But we are not allowed to overload operator.() for many severe reasons.

Now recall a virtual inheritance that may help:

class Widget : virtual public Window {
public:
Widget()
: Window(createWindow()) // copied by value
{}
// ...
};

Now it's copied by value, but we know that virtual base class is
*referred* from derived class using some /interesting mechanism/
(which I will not describe here). In fact, we have a not-so-smart
reference from derived (Widget) to base (Window). So why do we need
to copy Window by value? I want to initialize that reference instead.

So I propose an explicit


Syntax
======

class Widget : public Window& { // *derive from reference*
public:
Widget()
: Window(createWindow()) // initialize reference
{}
// ...
};

This solves the problem and does not create more problems.

- We must initialize reference, so it is always initialized;
- We cannot rebind reference, but probably we don't need that;
- There is no ambiguity in derived class members' access, as
it was with overloaded operator.()

The behavior for inheritance from reference is otherwise identical
to usual inheritance, and the behavior of resulting "smart reference"
is identical to usual reference, so I expect the idea will fit well
into language design.

Could it be accepted? I'm waiting for your comments.


--
Andrei Polushin

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

Andrei Polushin

unread,
Nov 6, 2006, 12:02:40 AM11/6/06
to
Andrei Polushin wrote:
> So I propose an explicit
>
> Syntax
> ======
>
> class Widget : public Window& { // *derive from reference*
> public:
> Widget()
> : Window(createWindow()) // initialize reference
> {}
> // ...
> };
>
> This solves the problem and does not create more problems.
>
> - We must initialize reference, so it is always initialized;
> - We cannot rebind reference, but probably we don't need that;
> - There is no ambiguity in derived class members' access, as
> it was with overloaded operator.()
>
> The behavior for inheritance from reference is otherwise identical
> to usual inheritance, and the behavior of resulting "smart reference"
> is identical to usual reference, so I expect the idea will fit well
> into language design.

Re-reading D&E 12.7, I've noticed a relevant question: what if the
base class contains virtual functions - should they be overridden in
class derived by reference? I see several alternate solutions:

1. Derived does never override functions from Base,
no diagnostics required (harmful and inconsistent);

2. Derived is unable to override functions from Base,
any attempt to override will fail to compile (inconvenient,
but better than nothing; compatible with other alternatives);

3. Derived can override members of Base that expects it.
Base class is marked as target for "derivation by reference"
and each instance of derived class contains a copy of VTable
suitable for dispatching its virtual calls (memory consuming,
complicated);

4. Derived can override members of Base.
An effective *dynamic type* of Derived is created at runtime
from the combination of actual Base1 type and Derived type.
This involves the runtime copying of Base1::VTable with
subsequent overriding its relevant entries by those from
Derived::VTable. The dynamic type is created once for each pair
of <Base1,Derived> and cached by the runtime. (Most viable and
preferable, but requires additional memory allocation).

The last variant is like a prototype-based inheritance available in
some other languages (JavaScript, Python 2.2, etc.). Despite its
benefits, it will require additional memory for each dynamic type.

Some alternatives for the memory allocation:

1. use ::operator new();
2. use std::allocator();
3. use some specific allocator (implementation-defined);
4. any of the above, and provide a limited static memory area
for each case where the dynamic type could be created.

Probably, the allocation issue is completely implementation-defined.
I prefer the use of specific allocator and the limited static memory
area preallocated in advance, if possible to predict its size.

Alberto Ganesh Barbati

unread,
Nov 8, 2006, 7:09:35 PM11/8/06
to
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. 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. In practice, the only difference from a normal T&
is that std::delegate<T> is a class type that you can inherit from. I'm
not a compiler writer, but it might not be too difficult to implement.
In fact, if it weren't for data members it's quite simple:

1) provide an (explicit?) constructor that takes a T&
2) provide a trivial dtor
3) class will be non-copyable, so declare but not define private copy
ctor and assignment operator
4) provide a implicit conversion operator to T&
5) for each public non-static member function (whether virtual or
non-virtual) in T provide an inline wrapper function that perform
delegation. Notice that virtual functions in T will become non-virtual
in std::delegate<T>: this is intentional.
6) for each public non-static member function template provide a
suitable wrapper similar to point 5.

Except for point 3, all members of std::delegate<T> are declared public.

Notice that non-public members are not imported. Alternatively, we could
allow protected members to be imported as protected, but it might be
seen as a violation of the access specifier (actually, std::delegate<T>
and T are unrelated classes, to std::delegate<T> should not have access
to non-public members).

About data membera, we have two choices:

1) disallow completely: i.e. if T has public non-static data members,
std::delegate<T> is ill-formed;

2) allow them: in that case if d is an instance of std::delegate<T> and
x a non-static public data member of T then the compiler shall interpret
d.x as d.t.x where t is the reference to a T object stored in d.

Number 1 seems too draconic, but number 2 seems more difficult to implement.

Just my 2 eurocent,

Ganesh

PS: of course, the name std::reference<T> could also be used.

Andrei Polushin

unread,
Nov 9, 2006, 12:26:22 AM11/9/06
to
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

Alberto Ganesh Barbati

unread,
Nov 9, 2006, 12:19:33 PM11/9/06
to
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

Andrei Polushin

unread,
Nov 9, 2006, 5:25:00 PM11/9/06
to
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

loufoque

unread,
Nov 19, 2006, 1:13:34 PM11/19/06
to
Andrei Polushin wrote:
>
> Syntax
> ======
>
> class Widget : public Window& { // *derive from reference*
> public:
> Widget()
> : Window(createWindow()) // initialize reference
> {}
> // ...
> };
>
> This solves the problem and does not create more problems.
>
> - We must initialize reference, so it is always initialized;
> - We cannot rebind reference, but probably we don't need that;
> - There is no ambiguity in derived class members' access, as
> it was with overloaded operator.()
>
> The behavior for inheritance from reference is otherwise identical
> to usual inheritance, and the behavior of resulting "smart reference"
> is identical to usual reference, so I expect the idea will fit well
> into language design.
>
> Could it be accepted? I'm waiting for your comments.

That seems interesting because since Widget inherits from a reference to
Window, then functions taking references to Window can take references
to Widget instead.

This allows to forward operator overloading for the underlying type etc.

Seungbeom Kim

unread,
Nov 19, 2006, 7:41:37 PM11/19/06
to
loufoque wrote:
> Andrei Polushin wrote:
>>
>> Syntax
>> ======
>>
>> class Widget : public Window& { // *derive from reference*
>> public:
>> Widget()
>> : Window(createWindow()) // initialize reference
>> {}
>> // ...
>> };
>
> That seems interesting because since Widget inherits from a reference to
> Window, then functions taking references to Window can take references
> to Widget instead.

Even if Widget is derived from Window (and not a reference to Window),
functions taking Window& can take Widget&.

struct Window { };
struct Widget : Window { };
void foo(Window&);
void bar(Widget& w) { foo(w); }

This is perfectly valid.

--
Seungbeom Kim

Mathias Gaunard

unread,
Nov 20, 2006, 11:32:45 AM11/20/06
to
Seungbeom Kim wrote:

> Even if Widget is derived from Window (and not a reference to Window)

In that case Widget is not a smart reference. Which was the point, here.

Smart references need to be convertible to normal references, which
seems to be quite clearly allowed by this inheritance-like proposal.

0 new messages