C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Downcasting without Sidecasting

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Mon, 8 Jul 2024 10:11:50 -0400
My CppCon 2017 talk "dynamic_cast from scratch"
<https://www.youtube.com/watch?v=QzJL-8WbpuU> covers this whole general
area, including
- a graphical/visual notation for talking about base class hierarchies,
including virtual bases, and
- a proof-of-concept "faster dynamic_cast" that does everything
dynamic_cast does, but measurably faster than the Itanium-ABI runtime.

To Phil's original question "how do I make a dynamic_cast that casts *only*
down, not sideways?"
After diagramming the situation in my head (using the notation from my
talk), I'm like 90% sure that this approach necessarily works —
https://godbolt.org/z/cazrbPEx3
I'm 99% sure it works if all the relationships involved are public. My 10%
unsurety comes from the scenarios involving non-public inheritance
relationships.
Oh, and it definitely doesn't work if the target type itself is two cats —
e.g. this can't be used as-is to downcast `Cat` to `SiameseCat`. But that
gives a nice hard compile-time error, rather than giving the wrong answer
at runtime.

struct Cat { virtual ~Cat() = default; };
struct LeftCat : Cat {};
struct RightCat : Cat {};
struct SiameseCat : LeftCat, RightCat {};

LeftCat *downcast_to_leftcat(Cat *c) {
  if (LeftCat *lc = dynamic_cast<LeftCat*>(c)) {
    if (static_cast<Cat*>(lc) == c) {
      return lc; // this was definitely a pure downcast
    }
  }
  return nullptr;
}

int main() {
  SiameseCat sc;
  assert(downcast_to_leftcat((LeftCat*)&sc) != nullptr);
  assert(downcast_to_leftcat((RightCat*)&sc) == nullptr);
}


On Thu, Jul 4, 2024 at 3:34 PM Chris Ryan via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> Funny that you should be asking about that now. At this very moment I am
> writing a talk I will be giving at a couple conferences this fall, that
> includes this exact subject.
>
> The sidecast offset is specifically needed with multiple inheritance
> because all the bases (except the first base) are NOT at the same "this"
> address as the derived class. So when you cast from the derived to one of
> the bases the pointer returned points specifically at the base object
> address. If you change the type but not the address you will be pointing
> at the wrong location (address of the first base class) using the wrong
> type (but not the type of the first base class). The cast computes the
> offset itself, however when you call via the virtual mechanism, the v-table
> points to a multiple inheritance thunking function that adjusts the "this"
> pointer address before calling the actual function to the start address of
> the base object.
>
> https://godbolt.org/z/sPTxe3sYP (mostly 64-bit)
> https://tinyurl.com/sizecastpdf (slideware presumes 32bit)
>
> Chris++;
>
>
> On Thu, Jul 4, 2024 at 10:53 AM Phil Endecott via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>>
>> dynamic_cast will both downcast and sidecast. (And upcast, etc.)
>>
>> Is there some trick that can be used to constrain it to do one but not
>> the other?
>> If not, should the language provide additional casts that are more
>> constrained
>> in what they do? (I.e. downcast<T>(expr), upcast<T>(expr),
>> sidecast<T>(expr).)
>> Would that be feasible with how the current ABIs work?
>>
>> I ask because, as you can guess, I've just hit an issue where I was
>> getting
>> unexpected sidecasting where code was only expecting downcasting. My
>> most derived class has multiple inheritance (but not virtual
>> inheritance). I
>> invoke some function on one of its base classes, and that unexpectedly
>> behaves differently because it can cast itself to a sibling base class.
>>
>> Thoughts anyone?
>>
>

Received on 2024-07-08 14:12:03