Skip to content

Conversation

@nytelytee
Copy link
Contributor

Allows you to repeatedly apply static_cast in the order the casts are applied in.

So, geode::cast::chain_cast<X, Y, Z>(obj) is equivalent to static_cast<Z>(static_cast<Y>(static_cast<X>(obj))).

Additionally, rarely useful, but geode::cast::chain_cast<>(obj) just returns obj.

@nytelytee
Copy link
Contributor Author

nytelytee commented Sep 11, 2025

Perhaps the second overload (the empty case, chain_cast<>(something)) should be removed? It is the only overload that doesn't act in the exact same way as applying static_cast repeatedly (ideally there is nothing to apply there, so it should always act literally as if the function call just doesn't exist, but I don't think it is possible to do that). It calls more constructors compared to just passing a value directly without any casts (which is what it's meant to symbolize). It also technically returns a different type.

I only added it so that if someone is doing template metaprogramming, and they try to expand a pack into a chain_cast for whatever reason (this is literally never going to happen), it would still work even if the pack is empty, but the unexpected calls on extra constructors compared to just passing the value directly could be unintuitive.

Given all of this, I will just remove it.

@nytelytee
Copy link
Contributor Author

nytelytee commented Sep 11, 2025

I also wrote a modify_cast, which is a concrete solution for the original problem that made chain_cast come up, namely casting a child of Base into a Modify<Derived, Base> class.

The example for chain_cast mentions a case like this:
chain_cast<GJBaseGameLayer*, MyGJBGL*>(PlayLayer::get());

The same could be done with modify_cast like so:
modify_cast<MyGJBGL*>(PlayLayer::get());

The implementation is a bit hacky, I was not sure how I would fetch the base class from a modify class; I ended up using the m_fields attribute, since that allowed me to specialize on FieldIntermediate. It also only works on pointers, and explicitly checks for pointers; I am not sure if anyone would ever be working with anything other than pointers when it comes to nodes, so...

Implementation is as follows:

namespace internal {

  // extract the base from the modify class, by checking its m_fields member;
  // the m_fields member is a FieldIntermediate<Derived, Base>, which gives us access to
  // Base when specialized; this is the only way i could figure out to extract the base
  // it looks pretty hacky to me, but i don't know a better way
  // type will also be void if there is no m_fields, or m_fields is not FieldIntermediate<Derived, Base>
  template<typename T, typename = void>
  struct extract_modify_base { using type = void; };

  template<typename T>
  struct extract_modify_base<T, std::void_t<decltype(T::m_fields)>> {
  private:
    template<typename U>
    struct extract_base { using type = void; };

    template<typename Derived, typename Base>
    struct extract_base<FieldIntermediate<Derived, Base>> { using type = Base; };
  public:
    using type = typename extract_base<decltype(T::m_fields)>::type;
  };
  
  template<typename T>
  using extract_modify_base_t = typename extract_modify_base<T>::type;

}

template<typename Target, typename Original>
constexpr Target modify_cast(Original original) {

  using TargetBase = ::internal::extract_modify_base_t<std::remove_pointer_t<Target>>;

  static_assert(std::is_pointer_v<Target> && !std::is_pointer_v<std::remove_pointer_t<Target>>, "Target class has to be a single pointer.");
  static_assert(std::is_pointer_v<Original> && !std::is_pointer_v<std::remove_pointer_t<Original>>, "Original class has to be a single pointer.");
  static_assert(
      (
          requires { std::remove_pointer_t<Target>::m_fields; !std::is_void_v<TargetBase>; } &&
          // satisfies the above, but does not inherit from Modify<TargetBase, std::remove_pointer_t<Target>>
          // someone tried to spoof a modify class???? why would you do that
          std::is_base_of_v<geode::Modify<std::remove_pointer_t<Target>, TargetBase>, std::remove_pointer_t<Target>>
      ),
      "The target class has to be a Modify class."
  );
  static_assert(
    !std::is_void_v<TargetBase> && requires { static_cast<TargetBase*>(original); },
    "The original class has to be castable to the class the modify class is modifying."
  );

  return static_cast<Target>(static_cast<TargetBase*>(original));
}

I am not going to add it to this PR until someone notifies me whether it's OK or not, since it may be out of scope for the PR.

@altalk23
Copy link
Member

altalk23 commented Sep 27, 2025

modify_cast is fine, but id say instead of chain_cast just use dynamic cast/typeinfo cast instead cause thats its point, can merge if you implement it based on that, also using PascalCase for every type, modify_cast is fine though, and make sure that the namespace name is fitting, by going through other internal namespaces in headers

@nytelytee nytelytee changed the title Add chain_cast into geode::cast Add modify_cast into geode::cast Sep 28, 2025
@nytelytee
Copy link
Contributor Author

I removed chain_cast and added modify_cast instead; it does not use typeinfo_cast, however, just static_cast. I could not figure out a way to get both behaviors other than splitting it into 2 functions, since it is not as easy as just checking if original is a base of the base class. E.g. if you're casting original from GJBaseGameLayer* into HookedPlayLayer*, just checking if GJBaseGameLayer is a base of PlayLayer would succeed, and it would use static_cast; this, however, would not be valid if original is not actually a PlayLayer*. Checking the base the other way around (which is effectively using typeinfo_cast for downcasting, and using static_cast for upcasting) would force the use of typeinfo_cast even when you know the type and want to avoid a typeinfo check. The only solution I see (that doesn't force the use of typeinfo_cast for performance reasons) is having 2 variants of modify_cast, e.g. static_modify_cast and dynamic_modify_cast/typeinfo_modify_cast, which use static_cast/typeinfo_cast respectively.

Sidenote, I think that even if you could always correctly determine which cast to use at compile time just based on the types alone, I think that it would be unintuitive, as people would have to track whether their modify_cast call uses typeinfo_cast or static_cast, if they care about speed.

All of this said, I haven't made 2 separate versions because I don't know if it would be approved or not, so I just kept it at the original implementation for now.

@dankmeme01 dankmeme01 requested a review from altalk23 September 30, 2025 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants