hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.51k stars 244 forks source link

[SUGGESTION] Solution for "auto* pDerived = base2derived(pBase);" #411

Closed mutluit closed 1 year ago

mutluit commented 1 year ago

In current C++ I'm missing this feature (unless I overlooked something):

auto* pDerived = base2derived(pBase);

pDerived will point either to a derived object, or nullptr if pBase is not the address of a derived.

Is this possible in this new language project?

JohelEGP commented 1 year ago

I think that's a dynamic_cast.

JohelEGP commented 1 year ago

Indeed: https://cpp2.godbolt.org/z/EszaoKh1P.

base: type = {
  f: (virtual this) = { }
}
derived: type = {
  this: base;
}
main: () = {
  b: base = ();
  [[assert: dynamic_cast<* derived>(b&) == nullptr]]
  d: derived = ();
  (inout db: base = d) {
    [[assert: dynamic_cast<* derived>(db&) != nullptr]]
  }
}
mutluit commented 1 year ago

I think that's a dynamic_cast.

I tried the following using dynamic_cast, but IMO it's not the correct solution. I think there ought to be a better and simpler method, as suggested above.

I have a base class Base_t and multiple derived classes from it (like Dog_t and Cat_t, and so on). I collect the base addresses of these objects in a vector. By using this vector I then need to access the original objects. Not just some virtual functions of the objects, but the complete original object. How best to do this?

The following solution is IMO not that satisfactory.

Is there a better way, maybe in newer C++ standards like c++20? Something like: auto* pDerived = helper(pBase); // pDerived will be a ptr to the correct derived object, or nullptr in case of err

class Base_t { ... class Dog_t : public Base_t { ... class Cat_t : public Base_t { ... ...

enum Type_t { Unknown, Base, Dog, Cat };

struct Identity_t { Type_t Type; union { Base_t pBase; Dog_t pDog; Cat_t* pCat; };

Identity_t() : Type(Unknown), pBase(nullptr) {}

};

Identity_t Identity(Base_t pBase) { Identity_t I; if ((I.pDog = dynamic_cast<Dog_t>(pBase))) { I.Type = Dog; return I; } if ((I.pCat = dynamic_cast<Cat_t*>(pBase))) { I.Type = Cat; return I; } return I; }

// And using it like this: // p is the address of a derived object, ie. Dog_t or Cat_t

auto I = Identity(p); if (I.Type == Dog) { // use I.pDog to access it auto& X = I.pDog; X.print(); } else if (I.Type == Cat) { // use I.pCat to access it auto& X = I.pCat; X.print(); }

... // end of code

realgdman commented 1 year ago

I think also it can be expressed with 'as' / 'is' new syntax Like in https://github.com/hsutter/cppfront/issues/411#issuecomment-1533490786 changing to

    b: base = ();
    [[assert: !(b is derived)]]
    d: derived = ();
    (inout db: base = d) {
        [[assert: (db is derived)]]
    }
JohelEGP commented 1 year ago

I think that's a dynamic_cast.

I tried the following using dynamic_cast, but IMO it's not the correct solution. I think there ought to be a better and simpler method, as suggested above.

Ah, so your suggestion is to reimplement dynamic_cast. I think that's out of scope, and definitely unwanted. If you were to follow the issue template, you'd find that https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines actually has plenty of guidelines that argue in favor of dynamic_cast, and I think counter reimplementations.

mutluit commented 1 year ago

It's in fact about extending the language so that it allows that a function can also return auto* or auto&, ie.

auto* func() { // pDog and pCat are different types if (condition) return pDog; return pCat; }

realgdman commented 1 year ago

Have you considered std::variant

JohelEGP commented 1 year ago

Or https://github.com/ldionne/dyno.

realgdman commented 1 year ago

Also look at this example, which shows direction where cpp2 (and hopefully cpp1) is aiming. That folder with regression tests also contain many useful examples.

https://github.com/hsutter/cppfront/blob/main/regression-tests/pure2-inspect-expression-in-generic-function-multiple-types.cpp2