Date: Thu, 23 Feb 2023 20:14:19 +0000
On Thu, 2023-02-23 at 16:08 +0200, Ville Voutilainen via Std-Proposals
wrote:
> 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.
>
I know you are not proposing this, but just for the record: Such an
empty_class_downcast would still subvert aliasing. While compilers
still have to treat a base pointer and the derived pointer as
potentially aliasing, they can still assume that a derived pointer and
an other derived pointer of a different type can't alias. Example:
struct Base {
int i;
};
struct Derived1 : Base {};
struct Derived2 : Base {};
void foo(Derived1* ptr1, Derived2* ptr2) {
ptr1->i += ptr2->i;
ptr1->i += ptr2->i;
/* compilers currently are allowed to transform this into:
ptr1->i += 2*ptr2->i;
*/
}
gcc seems to do this transformation:
https://godbolt.org/z/M9q5EMacE
So if you have a pointer to Base, it is still unsafe to "empty class
downcast" it to Derived1, as the actual complete type might be derived
from Derived2 and this might subvert aliasing down the line.
> 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.
wrote:
> 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.
>
I know you are not proposing this, but just for the record: Such an
empty_class_downcast would still subvert aliasing. While compilers
still have to treat a base pointer and the derived pointer as
potentially aliasing, they can still assume that a derived pointer and
an other derived pointer of a different type can't alias. Example:
struct Base {
int i;
};
struct Derived1 : Base {};
struct Derived2 : Base {};
void foo(Derived1* ptr1, Derived2* ptr2) {
ptr1->i += ptr2->i;
ptr1->i += ptr2->i;
/* compilers currently are allowed to transform this into:
ptr1->i += 2*ptr2->i;
*/
}
gcc seems to do this transformation:
https://godbolt.org/z/M9q5EMacE
So if you have a pointer to Base, it is still unsafe to "empty class
downcast" it to Derived1, as the actual complete type might be derived
from Derived2 and this might subvert aliasing down the line.
> 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 20:14:24