C++ Logo

std-proposals

Advanced search

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

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Thu, 23 Feb 2023 00:52:17 +0000
To give an example where this would be useful, let's say that I want
to use a 'std::binary_semaphore' as though it were a Lockable. The
following code works fine with every C++ compiler:

struct Adaptor : binary_semaphore {
    void lock (void) noexcept(false) { this->acquire(); }
    void unlock(void) noexcept(false) { this->release(); }
};

std::binary_semaphore some_global_object;

int Func(int &b)
{
    std::lock_guard<Adaptor> myguard{ *(Adaptor*)&some_global_object };

    return b + 7;
}

Officially this is UB, but it works fine. What if the Standard were to
explicitly allow it by using 'static_cast' as follows?

   std::lock_guard<Adaptor> myguard{
static_cast<Adaptor&>(some_global_object) };

The rule could be as follows: You can use 'static_cast' to cast from a
Base class to a Derived class unless:
(a) The Derived class has additional member objects
(b) The Base class has no virtual functions and is therefore not
polymorphic, but the Derived class has at least one virtual function
and is therefore polymorphic

If neither A nor B is violated, then it's harmless to cast from Base
to Derived, and so how about we explicitly write in the Standard that
this is allowed?

The alternative to this would be to do:

struct Adaptor {
    std::binary_semaphore &bs;
    Adaptor(std::binary_semaphore &arg) : bs(arg) {}
    void lock (void) noexcept(false) { bs.acquire(); }
    void unlock(void) noexcept(false) { bs.release(); }
};

int Func(int &b)
{
    Adaptor a(some_global_object);
    std::lock_guard<Adaptor> myguard{ a };

    return b + 7;
}

but of course this means we need to create a separate object of the
type 'Adaptor' and it can't be a temporary either -- as the following
doesn't compile:

int Func(int &b)
{
    std::lock_guard<Adaptor> myguard{ Adaptor(some_global_object) };
// can't bind L-value ref to temporary

    return b + 7;
}

In my example at the top, casting from std::binary_semaphore& to
Adaptor& is harmless, and so if it's harmless then why not allow it if
we can make good use of it?

Furthermore we could add a class to <type_traits> so that we can check
at compile-time if the cast is valid:

    namespace std {
        template<class Base, class Derived>
        struct is_downcast_valid {
            static bool constexpr value =
__compiler_support_needed(std::binary_semaphore,Adaptor);
        };
    }

Received on 2023-02-23 00:52:29