proposal for more explicit vtable control

293 views
Skip to first unread message

csch...@gmx.de

unread,
Jul 9, 2013, 6:34:07 PM7/9/13
to std-pr...@isocpp.org
Hallo list,
I searched for similar existing proposals but found none, so I posting it here.
The proposed features seems so basic to me that I think it must have been
proposed before, in one way or another. But in any case, here it goes:

In addition to the "= 0" initializer to indicate pure virtual functions, the
proposal allows to initialize a virtual function with the address of another
function, like so:

struct foo
{
   
virtual void bar();
   
virtual void baz() = &bar;
};

This shall mean, that the default implementation for baz() is bar(), unless this

behavior is overridden in a derived class.  The envisioned canonical
implementation is of course, that the address of function bar is put into both
slots of foo's vtable. In the absence of this feature, the workarounds are
either code duplication (same implementation for bar and baz) or an additional
function call (baz calls bar).

I feel that neither workaround should be necessary and this is a capability gap in C++.
I am motivated by the possibilities that are available to pure C programmers when 
designing a scheme for a polymorphic object hierarchy (for example, PyObject in CPython). 
There, it is perfectly possible to assign the same function pointer into different slots
of what is the equivalent of the vtable. It is also possible to put data
elements there, thereby creating the equivalent of virtual static data members
(which I am strongly in favor of). 

Feedback and comments welcome.

Klaim - Joël Lamotte

unread,
Jul 10, 2013, 7:03:48 AM7/10/13
to std-pr...@isocpp.org
Some questions, I'm just curious:

1. are there important differences with the following?

struct foo
{
     virtual void bar();
     virtual void baz() { bar(); } // this is inlined AFAIK, isn't it?
};

or maybe you're suggesting that there are more complex cases where 
it is not appropriate or maintanable to do it this way?

2. Do you have real use cases where you need such features?

Joel Lamotte

Ville Voutilainen

unread,
Jul 10, 2013, 7:17:38 AM7/10/13
to std-pr...@isocpp.org
On 10 July 2013 14:03, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
Some questions, I'm just curious:

1. are there important differences with the following?

struct foo
{
     virtual void bar();
     virtual void baz() { bar(); } // this is inlined AFAIK, isn't it?
};

Inlined? It's a virtual call.


But, for the alternative:

struct foo
{
    virtual void bar();
    virtual void baz();
};

// in a separate TU
void foo::bar() {common_impl();}
void foo::baz() {common_impl();}

In the TU of these definitions, common_impl(); can be inlined. So why would we make this
proposed change, when this work-around doesn't add extra function calls and causes a tiny
amount of code duplication?

DeadMG

unread,
Jul 10, 2013, 7:42:49 AM7/10/13
to std-pr...@isocpp.org


On Wednesday, July 10, 2013 12:17:38 PM UTC+1, Ville Voutilainen wrote:



On 10 July 2013 14:03, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
Some questions, I'm just curious:

1. are there important differences with the following?

struct foo
{
     virtual void bar();
     virtual void baz() { bar(); } // this is inlined AFAIK, isn't it?
};

Inlined? It's a virtual call.

You could qualify the call to foo::bar() directly. Frankly, I don't see how even if it wasn't inlined, one function call that a compiler which really wanted to could optimize out is worth a language feature. 

Ville Voutilainen

unread,
Jul 10, 2013, 7:46:31 AM7/10/13
to std-pr...@isocpp.org
The remainder of my message shows an alternative approach that avoids any function call via inlining of
a non-virtual and at the same time avoids/minimizes code duplication, so I don't disagree with you - I find
the motivation for this feature weak.

Klaim - Joël Lamotte

unread,
Jul 10, 2013, 8:23:29 AM7/10/13
to std-pr...@isocpp.org

On Wed, Jul 10, 2013 at 1:17 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
Inlined? It's a virtual call.

Ah yes, I was thinking without the potential inheritance because there is none in the example, sorry.

Jeffrey Yasskin

unread,
Jul 10, 2013, 1:43:18 PM7/10/13
to std-pr...@isocpp.org
On Wed, Jul 10, 2013 at 4:17 AM, Ville Voutilainen
<ville.vo...@gmail.com> wrote:
>
>
>
> On 10 July 2013 14:03, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
>>
>> Some questions, I'm just curious:
>>
>> 1. are there important differences with the following?
>>
>> struct foo
>> {
>> virtual void bar();
>> virtual void baz() { foo::bar(); } // this is inlined AFAIK, isn't it?
>> };
>
>
> But, for the alternative:
>
> struct foo
> {
> virtual void bar();
> virtual void baz();
> };
>
> // in a separate TU
> void foo::bar() {common_impl();}
> void foo::baz() {common_impl();}
>
> In the TU of these definitions, common_impl(); can be inlined. So why would
> we make this
> proposed change, when this work-around doesn't add extra function calls and
> causes a tiny
> amount of code duplication?

Both of these cost a small amount of code size with current compilers.
The proposed syntax would make it fairly obvious that no extra
function is emitted. However, I believe compilers could optimize
either of the suggested alternatives into a shared vtable entry, and
if the code size improvement is important to someone, they should bug
their compiler vendor to do that. If something in the language
prohibits that optimization, that would be worth fixing.

Jeffrey

Ville Voutilainen

unread,
Jul 10, 2013, 1:54:36 PM7/10/13
to std-pr...@isocpp.org
On 10 July 2013 20:43, Jeffrey Yasskin <jyas...@google.com> wrote:
Both of these cost a small amount of code size with current compilers.
The proposed syntax would make it fairly obvious that no extra
function is emitted. However, I believe compilers could optimize
either of the suggested alternatives into a shared vtable entry, and
if the code size improvement is important to someone, they should bug
their compiler vendor to do that. If something in the language
prohibits that optimization, that would be worth fixing.



I'm not quite convinced it would be worth fixing, to be honest.

Thiago Macieira

unread,
Jul 10, 2013, 4:22:45 PM7/10/13
to std-pr...@isocpp.org
I don't see the point either. If the ABI in question allows one function to be
replaced with another entirely, then it's just an optimisation matter. Please
talk to your compiler people.

It might not be worth saving 16 bytes or less.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
signature.asc

csch...@gmx.de

unread,
Jul 10, 2013, 6:30:15 PM7/10/13
to std-pr...@isocpp.org
Hi Joel, here is one such use case

class ScriptObject
{
   
virtual void OpCopyAssign( const ScriptObject * );
   
virtual void OpMoveAssign( ScriptObject * ) = &CopyAssign;
};

class ScriptNumber : public ScriptObject
{
   
// defines only OpCopyAssign
}

class ScriptString : public ScriptObject
{
   
// defines both
};



It would start to get interesting (and really useful) when the feature was defined to be *covariant*.
Then the compiler would, as long as a derived class does not define its own version of OpMoveAssign, use whatever version of OpCopyAssign it has (derived or overridden).
Thus, you'll get the equivalent behavior of that

class ScriptObject
{
    
virtual void OpCopyAssign( const ScriptObject * );
    
virtual void OpMoveAssign( ScriptObject *other ) { OpCopyAssign( other ); } // virtual call
};

without the overhead of a 2nd virtual call.
Of course, if compilers would be smart enough to detect this configuration and optimize the vtable entries along the way, that'd be great too. But the ability to specify in the language that this behavior is explicitly wanted is more conceptually pure.

Nevin Liber

unread,
Jul 10, 2013, 6:38:15 PM7/10/13
to std-pr...@isocpp.org
On 9 July 2013 17:34, <csch...@gmx.de> wrote:

struct foo
{
   
virtual void bar();
   
virtual void baz() = &bar;
};

This shall mean, that the default implementation for baz() is bar(), unless this

behavior is overridden in a derived class. 

What is the intended output of the following program:

struct foo
{
    virtual void bar() { cout << "foo::bar" << endl; }
    virtual void baz() = &bar();
};

struct goo : foo
{
    void bar() override { cout << "goo::bar" << endl; }
};

struct hoo : goo
{
    void baz() override { cout << "hoo::baz" << endl; }
};

void print(foo const& f)
{
    cout << "bar: "; f.bar();
    cout << "baz: "; f.baz();
    cout << endl;
}

int main()
{
    print(foo());
    print(goo());
    print(hoo());
}
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Thiago Macieira

unread,
Jul 10, 2013, 8:51:34 PM7/10/13
to std-pr...@isocpp.org
On quarta-feira, 10 de julho de 2013 15.30.15, csch...@gmx.de wrote:
> Hi Joel, here is one such use case
>
> class ScriptObject
> {
> virtual void OpCopyAssign( const ScriptObject * );
> virtual void OpMoveAssign( ScriptObject * ) = &CopyAssign;
> };
>
> class ScriptNumber : public ScriptObject
> {
> // defines only OpCopyAssign
> }
>
> class ScriptString : public ScriptObject
> {
> // defines both
> };

I don't see why you need explicit control over the vtable. What you want can
be easily achieved by the code you wrote below.

In most compilers today and architectures, that code will be one single
instruction. The compiler could optimise the two functions to be one and the
same by aliasing their symbol names, though. Maybe you want to talk to your
compiler vendor.

I don't think this violates anything in the standard, since you can't get the
non-virtual address of the function anyway.

> It would start to get interesting (and really useful) when the feature was
> defined to be *covariant*.
> Then the compiler would, as long as a derived class does not define its own
> version of OpMoveAssign, use whatever version of OpCopyAssign it has
> (derived or overridden).
> Thus, you'll get the equivalent behavior of that
>
> class ScriptObject
> {
>
> virtual void OpCopyAssign( const ScriptObject * );
> virtual void OpMoveAssign( ScriptObject *other ) { OpCopyAssign( other
>
> ); } // virtual call
> };
>
> without the overhead of a 2nd virtual call.

That's only possible if the passing of the arguments is compatible without a
thunk. That is entirely ABI-specific and we can't have a feature in the
standard that depends on the ABI.

Maybe the above case is trivial, but think of the case if OpCopyAssign had a
returned value. Or of covariant return types.

> Of course, if compilers would be smart enough to detect this configuration
> and optimize the vtable entries along the way, that'd be great too. But the
> ability to specify in the language that this behavior is explicitly wanted
> is more conceptually pure.

In my opinion, this is assuming too much about how compilers implement virtual
calls in the first place.
signature.asc

Róbert Dávid

unread,
Jul 11, 2013, 3:49:29 PM7/11/13
to std-pr...@isocpp.org

If people's answer will be same or different to this question, it will show if it is consistent of not.
Here is my take:

Foo has two entries in the vtable, bar and baz. Both points to the function that prints foo::bar. So far:
bar: foo::bar
baz
: foo::bar
Goo has the same two entries, overrides bar, but doesn't change baz:
bar: goo::bar
baz
: foo::bar
Hoo keeps Goo's entry for bar, but overrides baz:
bar: goo::bar
baz
: hoo::baz
I guess the key question was what happens at goo::baz..

To be honest, I find the feature interesting, and even relatively easy to implement, but I don't really see high value of the feature. I would like to see a better example, where the code is more simple and elegant using this proposed syntax, but is hard to implement currently.

Regards, Robert

Ville Voutilainen

unread,
Jul 11, 2013, 4:14:43 PM7/11/13
to std-pr...@isocpp.org
I would expect the same result.

Michael Price - Dev

unread,
Jul 11, 2013, 9:13:03 PM7/11/13
to std-pr...@isocpp.org
This is quite similar to an idea that I've been mulling over for more than a year. Function aliasing.

Currently you can achieve something like this with function pointers, but they have certain limitations.

Here's an example of one use I was envisioning:

template <typename T>
void func(int myInt, const char* myStr, bool myBool)
{
T::something(myInt, myStr, myBool);
}

struct thing_i_dont_control
{
static int doIt(int, const char*, bool);
};

// essentially a concept map
struct map_it
{
using something = thing_i_dont_control::doIt;
};

func<map_it>(1, "a", true);

This would be even better if it allowed for parameter reordering and providing defaults.

The examples provided so far have been "simple" functions. When you get functions that take five or six parameters that need to be forwarded, it gets a bit verbose (and I'd argue error prone).

I also have a gut feeling that this type of technique can be used for some interesting dependency injection and mocking scenarios, but I haven't fully developed those ideas.

True, my thoughts have nothing to do with virtual functions, but I believe the ideas presented thus far are similar enough to broaden the discussion.

And apologies for the poor formatting, this was from my mobile.

Vicente J. Botet Escriba

unread,
Jul 12, 2013, 1:37:42 AM7/12/13
to std-pr...@isocpp.org
Le 11/07/13 22:14, Ville Voutilainen a écrit :
Not me. I would expect that goo::baz vtable entry is &goo::bar as the declaration


  virtual void baz() = &bar();

is a shortcut for

  virtual void baz() { bar() } 

Otherwise I don't see the interest.

Vicente

Ville Voutilainen

unread,
Jul 12, 2013, 6:16:40 AM7/12/13
to std-pr...@isocpp.org
On 12 July 2013 08:37, Vicente J. Botet Escriba <vicent...@wanadoo.fr> wrote:
Not me. I would expect that goo::baz vtable entry is &goo::bar as the declaration


  virtual void baz() = &bar();

is a shortcut for

  virtual void baz() { bar() } 

Otherwise I don't see the interest.



If that's the shortcut you expect baz() = &bar();  to mean, then goo::baz() should
override that behavior to not invoke bar() at all. Perhaps you expect the "shortcut"
to be different.

Róbert Dávid

unread,
Jul 12, 2013, 1:31:33 PM7/12/13
to std-pr...@isocpp.org

If you interpret it as that, this features has absolute zero value - it is even LONGER to write than what it was shortcut to ("=&x;" vs. "{x}").

However, if you interpret is as defining a vtable entry that points to the very same function, that is different, it might be useful. But as I said, still needs a better usecase.

Regards, Robert

Ville Voutilainen

unread,
Jul 12, 2013, 2:55:17 PM7/12/13
to std-pr...@isocpp.org
On 12 July 2013 20:31, Róbert Dávid <lrd...@gmail.com> wrote:
Not me. I would expect that goo::baz vtable entry is &goo::bar as the declaration

  virtual void baz() = &bar();

is a shortcut for

  virtual void baz() { bar() } 

Otherwise I don't see the interest.

Vicente

If you interpret it as that, this features has absolute zero value - it is even LONGER to write than what it was shortcut to ("=&x;" vs. "{x}").

However, if you interpret is as defining a vtable entry that points to the very same function, that is different, it might be useful. But as I said, still needs a better usecase.


The whole idea in this proposal is that virtual void baz() = &bar(); is almost but not quite equivalent
to a baz that calls bar, because the "slot" of baz has the same function pointer implementation-wise
as bar does. Even so, when a derived class overrides baz, it overrides baz, not bar. The override
of bar is up to discussion; the original vtable is so that baz/bar point to the base implementation
of bar. If bar is overridden, should the vtable entry for baz now point to that override? It makes
rather little sense not to, since otherwise calls to bar would call the new override but calls to
baz would still invoke the base bar, as was my and Robert's thinking in our replies. I think it needs to be so
that print(goo); prints
goo::bar
goo::bar

I don't know how to implement that. I'm not sure there's reserved space in a vtable to record the
information that a vtable entry "aliases" another and when that "another" is overridden, the alias
should update too.

csch...@gmx.de

unread,
Jul 16, 2013, 5:57:21 PM7/16/13
to std-pr...@isocpp.org

The proposal really only useful if goo is going to print

bar: goo:bar
baz: goo:bar

so that the declaration "= &bar" is covariant, i.e. the vtable entry of baz co-varies with the vtable entry of bar, until the link is broken with with a manual override of baz. 
Therein lies the utility of the proposal, otherwise it would just be syntactic sugar for a qualified, non-virtual call.

In the case of above, the vtable of class "goo" already has an entry for baz, so there is no problem in regard to missing vtable slot.

Think about header files that are published as part of an SDK interface, in anticipation that 3rd party programmers are going to derive from it. With this proposal, it would be possible for the interface author to specify that "baz = &bar", and if a user does not wish to override baz, he gets the reasonable default behavior (i.e. a call to baz will call his *overridden* version of bar) with out the penalty of a double dispatch. This is IMHO better than just having a documentation that says "but you also must override baz" etc.

Nevin Liber

unread,
Jul 16, 2013, 6:14:05 PM7/16/13
to std-pr...@isocpp.org
On 16 July 2013 16:57, <csch...@gmx.de> wrote:
Think about header files that are published as part of an SDK interface, in anticipation that 3rd party programmers are going to derive from it. With this proposal, it would be possible for the interface author to specify that "baz = &bar", and if a user does not wish to override baz, he gets the reasonable default behavior (i.e. a call to baz will call his *overridden* version of bar) with out the penalty of a double dispatch.

Only if they have the exact same calling signatures.  That makes it of very limited use.

Combined with two reasonable behaviors, neither of which is obvious from the syntax, IMHO this feature isn't worth the cost in added complexity to the language.  -1.

Jeffrey Yasskin

unread,
Jul 16, 2013, 6:16:31 PM7/16/13
to std-pr...@isocpp.org
I agree. -1 to the feature.
Reply all
Reply to author
Forward
0 new messages