C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Downcasting without Sidecasting

From: Phil Endecott <std_proposals_list_at_[hidden]>
Date: Tue, 09 Jul 2024 15:53:39 +0100
Arthur O'Dwyer wrote:
> That's an upcast, not a downcast.

Sorry for my confused post yesterday. I got muddled while trying to map your
cats onto my already-half-forgotten original problem.

Here is a longer example that is closer to the code I was originally struggling with:

//////////

#include <cassert>
#include <iostream>


struct V { virtual ~V() = default; };
struct D { void foo() { std::cout << "foo"; } };

struct CV: V {};
struct DV: V, D {};

void foo(std::string_view msg, auto& v)
{
  std::cout << msg << ' ' << typeid(decltype(v)).name() << ' ';
  auto dp = dynamic_cast<D*>(&v);
  if (dp) dp->foo();
  std::cout << '\n';
}
// (Also consider the case where the arg is V& v, not auto& v.)

void test1()
{
  CV cv;
  DV dv;

  foo("cv", cv); // does nothing.
  foo("dv", dv); // calls D::foo.
}


struct Composed {
  CV cv;
  DV dv;
};

struct Inherited:
  CV,
  DV
{};

void test2()
{
  Composed composed;
  Inherited inherited;

  foo("composed.cv", composed.cv); // does nothing.
  foo("composed.dv", composed.dv); // calls D::foo.

  foo("inherited CV", static_cast<CV&>(inherited)); // Unexpectedly calls D::foo.
  foo("inherited DV", static_cast<DV&>(inherited)); // Calls D::foo.
}


int main()
{
  test1();
  test2();
}

//////////

I solved the original problem by replacing some inheritance with composition,
and that focused my attention on how base class objects compare with
member objects. Member objects know nothing about their siblings; base
class objects seemingly cannot forget their siblings.

The consequence is that a class designer needs to consider how his class might
be inherited from (or, make it 'final'). The way the class is used leaks into the
class itself.

Maybe what I need is a way to cast a "BASE that is actually a DERIVED" to
a "BASE that has forgotten its most derived type". I suspect this is difficult.

Anyway: as written above, my example can be solved without any run-time casting
like this:

void foo(std::string_view msg, auto& v)
{
  std::cout << msg << ' ' << typeid(decltype(v)).name() << ' ';
  if constexpr (std::is_base_of_v<D,std::remove_reference_t<decltype(v)>>) {
    auto dp = static_cast<D*>(&v);
    dp->foo();
  }
  std::cout << '\n';
}

but that doesn't work if I have V& v rather than auto& v as the argument.
In this case, I need a cast that will let me convert a V& that is a DV& to its
sibling D&, but not convert a V& that is a CV& that is an Inherited& to the D&
in its "cousin" DV&! That really is getting complicated. It seems that rather
than my original ask to distinguish up/down/side cases, I really needed to
distinguish different degrees of separation in sidecasts!


Regards, Phil.

Received on 2024-07-09 14:53:42