C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Allow downcasting at compile time if Derived has no extra member objects

From: Ville Voutilainen <ville.voutilainen_at_[hidden]>
Date: Thu, 23 Feb 2023 16:08:18 +0200
On Thu, 23 Feb 2023 at 14:30, Frederick Virchanza Gotham via
Std-Proposals <std-proposals_at_[hidden]> wrote:
> There are some ideas that you can jump straight into, and immediately
> float among the community to get people thinking, although perhaps
> instead of just allowing 'static_cast' in the way I described, maybe
> there should be a new keyword such as 'interface' or 'facade',
> something like:
>
> namespace std {
> class binary_semaphore { /* stuff goes here */ };
>
> interface lockable_binary_semaphore : binary_semaphore {
> void lock (void) noexcept(false) { return this->acquire(); }
> void unlock(void) noexcept(false) { return this->release(); }
> };
> }
>
> If the new function has the same signature as the old function, then
> maybe allow the following shorthand:
>
> interface lockable_binary_semaphore : binary_semaphore {
> lock = acquire;
> unlock = release;
> };
>
> And then in the code we could do:
>
> extern std::binary_semaphore g_sem;
>
> int main(void)
> {
> std::lock_guard<lockable_binary_semaphore>( g_sem ); //
> implicit conversion from binary_semaphore& to
> lockable_binary_semaphore&
> }

Getting warmer. The idea is interesting in the sense that it is in
fact useful to be able to legally
convert a base class pointerlikething into a derived class
pointerlikething. I can see it being useful
for static polymorphism scenarios, where the derived classes hold no
data, just behavior.

None of your earlier examples need that, they can be done in other ways.

Now then, onto the adventure into this: static_cast is indeed bad; for
this conversion, we actually want
to guarantee that the derived class is empty. It's thus better to do
it with something else than static_cast,
like a new kind of cast named "empty_class_downcast" or whatever.

Now, let's step back. We think we need to do this, because we can't
apply the usual technique used with
such hierarchies, which would be just to pass objects of the derived
type by value, because the base subobject
isn't movable, as it contains a mutex or something else that can't move.

We then think we need to resort to pointers instead, and then use
either new amended semantics of static_cast,
or a new cast, so that dereferencing the resulting pointer is
well-defined even though an object of the pointed type
doesn't really exist.

So, we can work around this by defining almost exactly the hierarchy
we had before, except omitting the immovable
base from it, and using a base that is also just empty, and then
writing either a member function or a free function
that converts our immovable type to whichever _value_ of a derived
handle type that refers to the immovable object quite like a pointer
would.

And now we have gone full circle, we can pass those handles by value,
we can convert the immovable type to them, and everything
works just like it would've worked with the enhanced static_cast
(which is a bad choice, since it can't be distinguished from
actually risky static_casts) or the new cast (which is burdensome
because it's a new language facility, or a new stdlib facility
with magical powers).

And we no longer need a new language facility, you just need to define
that hierarchy and write that conversion function.
It's all type-safe, and not dangerous at all - have the derived
handles be constructible from a pointer/reference to your immovable
class, construct them from it, and you can do it with a single
conversion template. If you want to save space, save that
pointer/reference
to the base class of the hierarchy. You can write a "downcast"
function that takes that base, extracts a pointer/reference to the
immovable
from it, and constructs your new derived handle.

Problem solved, user-facing syntax is almost exactly what it was in
your original idea, and no language/stdlib changes are needed.

Received on 2023-02-23 14:08:31