Permit virtual function returns to be any type implicitly convertible.

265 views
Skip to first unread message

DeadMG

unread,
Jan 7, 2013, 9:38:24 AM1/7/13
to std-pr...@isocpp.org
Often, we see classes which return objects of other classes from their virtual functions in mirrored hierarchies, such as factories. This is fine- for a while.

struct Base {
    virtual ~Base() {}
};
struct Derived : Base {
    virtual ~Derived() {}
};
struct BaseFactory {
    virtual Base* Create() = 0;
};
struct DerivedFactory : BaseFactory {
    Derived* Create() { return new Derived(); }
};

But, of course, a responsible C++ user will observe that the Create method is, in fact, horrendously unsafe through it's use of manual memory management. The problem is that this can't be fixed by changing the Create method to use an appropriate smart pointer, as virtual function returns are strictly limited to only covariant return types. I propose that it should be changed to be any return type which can be implicitly converted to the original return type. This should permit the use of smart pointers in this situation- amongst others.

Ville Voutilainen

unread,
Jan 7, 2013, 2:05:08 PM1/7/13
to std-pr...@isocpp.org
The problem I see with this proposal is that it would seem to increase
the cost of (some)
virtual calls, since a c++ implementation would then be required to
perform a user-specified
conversion at runtime, and there may be ABI implications, too.

DeadMG

unread,
Jan 7, 2013, 2:06:37 PM1/7/13
to std-pr...@isocpp.org
The only cost increase would be the cost of the conversion the user requested- hardly an undue penalty.

As for ABI, I don't think it should have any implications there. All the existing code still behaves identically to how it does today.

Ville Voutilainen

unread,
Jan 7, 2013, 2:10:19 PM1/7/13
to std-pr...@isocpp.org
On 7 January 2013 21:06, DeadMG <wolfei...@gmail.com> wrote:
> The only cost increase would be the cost of the conversion the user
> requested- hardly an undue penalty.

Well, perhaps it would be a good idea to have an implementation and measure
whether the penalty is undue or not.

> As for ABI, I don't think it should have any implications there. All the
> existing code still behaves identically to how it does today.

I don't see how you can add support for a runtime operation which may
or may not need
to invoke a user-specified conversion without any ABI changes.

DeadMG

unread,
Jan 7, 2013, 2:13:58 PM1/7/13
to std-pr...@isocpp.org
Well, perhaps it would be a good idea to have an implementation and measure 
whether the penalty is undue or not. 

What, you want to have a user-defined conversion without paying for it? 

Well, in a common implementation with vtables, then changing the body of the function pointed to by the vtable won't change the signature or calling convention or any such thing. Because that's all you'd be doing- changing the body of some compiler-generated functions. Or to be more accurate, requiring the compiler to generate some functions that are now illegal. That's all.

Felipe Magno de Almeida

unread,
Jan 7, 2013, 2:16:42 PM1/7/13
to std-pr...@isocpp.org
Wouldn't you need to have various functions for each possible conversion
in the hierarchy?

> --

Regards,
--
Felipe Magno de Almeida

Ville Voutilainen

unread,
Jan 7, 2013, 2:17:22 PM1/7/13
to std-pr...@isocpp.org
On 7 January 2013 21:13, DeadMG <wolfei...@gmail.com> wrote:
>> Well, perhaps it would be a good idea to have an implementation and
>> measure
>> whether the penalty is undue or not.
> What, you want to have a user-defined conversion without paying for it?

With the current rules, I can control where and when that cost gets
incurred - and that's
when I perform a downcast from a Base* or a smart_ptr<Base>.

> Well, in a common implementation with vtables, then changing the body of the
> function pointed to by the vtable won't change the signature or calling
> convention or any such thing. Because that's all you'd be doing- changing
> the body of some compiler-generated functions. Or to be more accurate,
> requiring the compiler to generate some functions that are now illegal.
> That's all.

I don't quite see how it's that simple. The call site, which is not
statically determined,
performs the upcast if the call was from an entry point where the
return type is Base*
instead of Derived*. The function body just returns a Derived*, and
the caller converts
if necessary. I don't see how it's different with this change, the
function body would
return what it's declared to return and the call site must decide
whether to do a conversion
or not, and then do the conversion if need be.

DeadMG

unread,
Jan 7, 2013, 2:20:15 PM1/7/13
to std-pr...@isocpp.org
You wouldn't need any more functions than you do now if you return a raw pointer. You need a small thunk to fix up "this", call the derived function, and fix down the return argument. All I'm proposing is that "fix down" can be more flexible than before. It would not require changing ABIs or generating more functions than now or changing the implementation of inheritance.

It's just the same as before, except where the compiler previously generated code to convert a pointer from Derived* to Base*, it might now generate code to perform some user-defined implicit conversion.

Nevin Liber

unread,
Jan 7, 2013, 2:46:44 PM1/7/13
to std-pr...@isocpp.org
On 7 January 2013 13:20, DeadMG <wolfei...@gmail.com> wrote:
You wouldn't need any more functions than you do now if you return a raw pointer. You need a small thunk to fix up "this", call the derived function, and fix down the return argument. All I'm proposing is that "fix down" can be more flexible than before.

Is this purely an extension, or is there a cost (code/space/speed) to a hierarchy that only has the C++11 covariant return types?

Also, what happens in the case:

struct RA {};
struct RB { operator RA() const; };
struct RC { operator RB() const; };

struct VA { virtual RA clone() const; };
struct VB : VA { virtual RB clone() const; };
struct VC : VA { virtual RC clone() const; };

int main() { RA ra(VC().clone()); }

Is the expectation that the object stored in ra is the result of:

static_cast<RA>(static_cast<RB>(VC().VC::clone()))

It's just the same as before, except where the compiler previously generated code to convert a pointer from Derived* to Base*, it might now generate code to perform some user-defined implicit conversion.

In the cases where that cost is free now (on typical implementations), is it still free?
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

DeadMG

unread,
Jan 7, 2013, 2:58:46 PM1/7/13
to std-pr...@isocpp.org
Yes. If you don't request any conversion, then none has to be performed.

You might expect a call to a user-defined conversion function (which is presumably used in at least one other place) to consume slightly more code than a pointer change (which is presumably just one subtraction). But for one, that doesn't violate "Pay for what you use", because you only pay the cost if you use it, and for two, the cost is no more than using that implicit conversion in some other place- for example, in the body of the derived function itself, which is what's typically done now.

For your proposed case, then VC::clone() is invoked, and then overload resolution kicks in from there. I'm not sure that in this specific instance, that can actually compile, because it would involve two user-defined implicit conversions and I think the Standard allows only one. However, you do raise an interesting point, which is that the compiler may end up invoking more than one implicit conversion (for example if clone() were called on an instance of VC which was referred to by a pointer of static type VA), which is new.

Christof Meerwald

unread,
Jan 7, 2013, 3:42:45 PM1/7/13
to std-pr...@isocpp.org
On Mon, Jan 07, 2013 at 09:17:22PM +0200, Ville Voutilainen wrote:
> I don't quite see how it's that simple. The call site, which is not
> statically determined,
> performs the upcast if the call was from an entry point where the
> return type is Base*
> instead of Derived*. The function body just returns a Derived*, and
> the caller converts
> if necessary. I don't see how it's different with this change, the
> function body would
> return what it's declared to return and the call site must decide
> whether to do a conversion
> or not, and then do the conversion if need be.

Consider the following C++11 example:

struct A
{
virtual void g();
};

struct B
{
virtual B *f();
};

struct C : A, B
{
virtual C *f()
{
return this;
}
};

void bar(B *b)
{
B *ptr = b->f();
}

void foo()
{
C c;
bar(&c);
}

When using gcc, the vtable entry for f actually points to a compiler
generated thunk that performs the return type conversion from C* to
B*. And I guess the proposal is to allow compiler generated thunks to
perform user-defined conversions (and not just derived-to-base
conversions).


Christof

--

http://cmeerw.org sip:cmeerw at cmeerw.org
mailto:cmeerw at cmeerw.org xmpp:cmeerw at cmeerw.org

Ville Voutilainen

unread,
Jan 7, 2013, 3:50:43 PM1/7/13
to std-pr...@isocpp.org
On 7 January 2013 22:42, Christof Meerwald <cme...@cmeerw.org> wrote:
> When using gcc, the vtable entry for f actually points to a compiler
> generated thunk that performs the return type conversion from C* to
> B*. And I guess the proposal is to allow compiler generated thunks to
> perform user-defined conversions (and not just derived-to-base
> conversions).

Thank you. That would indicate that there's no additional performance problem.

I like the semantics of the proposal, they certainly seem useful. If
there weren't any
implementation concerns (and I'm not claiming there are, at least for
gcc, according
to your explanation), I would wholeheartedly want this change. So the
remaining question
is, why are the current rules restricted to covariant types?

DeadMG

unread,
Jan 7, 2013, 4:19:35 PM1/7/13
to std-pr...@isocpp.org
Presumably, the original authors did not want to permit most implicit conversions, did want to permit covariance, but did not foresee the problems with smart pointers. Ultimately, something like

struct Base {
    virtual int f() = 0;
};
struct Derived : Base {
    virtual float f();
};

probably isn't the smartest move in the world. But as the price of permitting strongly-typed smart pointer returns, it's probably worth it.
Reply all
Reply to author
Forward
0 new messages