Skip to content

refactor: simpler type check in Acts::Any#4993

Draft
timadye wants to merge 8 commits intoacts-project:mainfrom
timadye:adye-any1
Draft

refactor: simpler type check in Acts::Any#4993
timadye wants to merge 8 commits intoacts-project:mainfrom
timadye:adye-any1

Conversation

@timadye
Copy link
Copy Markdown
Contributor

@timadye timadye commented Jan 21, 2026

A simpler alternative to #4968.

I think this is equivalent, in that both use a unique static variable in a member function templated on the contained type. This PR just replaces the hashed type name with a pointer to Handler.

Also adds a missing include.

@paulgessinger

@github-actions github-actions bot added the Component - Core Affects the Core module label Jan 21, 2026
@github-actions github-actions bot added this to the next milestone Jan 21, 2026
@timadye timadye changed the title simpler type check in Acts::Any refactor: simpler type check in Acts::Any Jan 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 21, 2026

📊: Physics performance monitoring for 6814905

Full contents

physmon summary

@sonarqubecloud
Copy link
Copy Markdown

@paulgessinger
Copy link
Copy Markdown
Member

paulgessinger commented Jan 21, 2026

Thanks @timadye.

I think this is exactly what we're had in Acts::Any before my change to the type hash in #4828.

This exactly exhibits the problem: the pointers will point at different addresses in different shared libraries and the check then returns false, identifying them as separate types.

The pointer comparison in the assignment operator is likely a bug I think.

@timadye
Copy link
Copy Markdown
Contributor Author

timadye commented Jan 21, 2026

Sorry, I didn't see that you'd tried this before.

I think I see how yours might work. The pattern is the same. In each case you are relying on a static variable inside a template <typename T> static function only being initialised once. That seems to work across compilation units, but not across shared libraries.

In that case, you rely on typeid(T).name() being the same between different shared libraries. This clearly won't work with the original method (and my repeat of it here), where we compare addresses.

However, according to cppreference, the only things guaranteed to match are comparing std::typeinfo objects (returned by typeid(T)), their ti.hash_code()s, and std::type_index(ti).

libstdc++'s std::any compares typeid(T)s. Actually it compares addresses first, so will not throw if either

  1. the address of a static function inside a template <typename T> static struct {} matches, or
  2. typeid(T) matches, with the typeinfo of the contained type being returned by function templated on that type.

I think it does both for speed, and so it mostly works even if RTTI isn't available.

I think we could do something similar by caching a pointer to the std::typeinfo and dereferencing before comparing. Or cache the hash_code or std::type_index.

I can try that...

@paulgessinger
Copy link
Copy Markdown
Member

We just need to make sure that this then actually works, also in @wdconinc's case. I fear that all of the guarantees around typeid, type_index or .hash_code() don't actually work in practice in the cases we need them to.

dynamic_cast is also guaranteed to work across libraries, I believe, but in our experience, it often doesn't work.

@timadye
Copy link
Copy Markdown
Contributor Author

timadye commented Jan 27, 2026

I tried to reproduce @wdconinc's issue with https://godbolt.org/z/ev74rP8GY . This has a main() program that passes an Acts::Any to a function in a shared library and uses any.as<T>() to access the value. Or the other way round, with the Acts::Any returned to the main(). I tried with T as a built-in type and my own structs. I tried with main() compiled with GCC and shared library compiled with Clang, and vice versa. I tried different versions of the compilers.

In all cases, it worked fine if the types were the same, and correctly threw an exception if they were different. Maybe I missed some combination, but I tried lots! I have so-far only tried with my implementation here, but expect it would work just as well with #4968.

@paulgessinger
Copy link
Copy Markdown
Member

paulgessinger commented Jan 27, 2026

@timadye That's interesting. I know @asalzburger had originally reported the issue with just using the pointer, but I remember that was in the depths of the python bindings and specifically on macOS and AppleClang. I'm not sure if he would be able to reproduce it at this point.

@github-actions github-actions bot added the Stale label Feb 27, 2026
@timadye
Copy link
Copy Markdown
Contributor Author

timadye commented Mar 3, 2026

The updated Acts::Any test still works in https://godbolt.org/z/oEhdb1GKj .

@github-actions github-actions bot removed the Stale label Mar 4, 2026
@timadye timadye marked this pull request as ready for review March 4, 2026 10:41
@timadye
Copy link
Copy Markdown
Contributor Author

timadye commented Apr 8, 2026

Aha! I get a std::bad_any_cast if I pass a simple templated class object in a Acts::Any from main into a shared library: https://godbolt.org/z/sEM8eTrEo . Going the other way works fine, no exception.

@timadye
Copy link
Copy Markdown
Contributor Author

timadye commented Apr 8, 2026

And it shows the same behaviour with std::any: https://godbolt.org/z/jT5oT1qr1

@paulgessinger
Copy link
Copy Markdown
Member

@timadye can you unpack for me what this means in terms of conclusion?

@timadye
Copy link
Copy Markdown
Contributor Author

timadye commented Apr 8, 2026

Sorry, those two examples were actually working as expected. I was asking for a different class out of Acts::Any or std::any. Here are the three examples working with x86_64/gcc15.2 as expected (now with debugging enabled):

  1. https://godbolt.org/z/M57MWcPnj (Acts::Any from main)
  2. https://godbolt.org/z/Ed35s4EaY (Acts::Any from refactor: simpler type check in Acts::Any #4993)
  3. https://godbolt.org/z/YqjcKvb1W (std::any)

Let me keep hunting...

@timadye timadye marked this pull request as draft April 8, 2026 19:58
@github-actions github-actions bot added the Component - Examples Affects the Examples module label Apr 8, 2026
@timadye
Copy link
Copy Markdown
Contributor Author

timadye commented Apr 8, 2026

I have now tried three types to store in the Acts::Any (from main or #4993) or std::any:

  1. S (a simple struct)
  2. S<int>
  3. S<D>, where D is a simple struct

In all cases, it works correctly with no std::bad_any_cast thrown. Tested with gcc 15.2, clang 19.1, and a mix of the two (one for main(), the other for the .so). I also tested construct in main() and use in .so, and vice versa. I probably missed some combinations, but I tried lots of them.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 8, 2026

@timadye
Copy link
Copy Markdown
Contributor Author

timadye commented Apr 9, 2026

I updated to the latest main. Interestingly, the linux_ubuntu_python_wheel test gives one Failed to execute Algorithm "PythonTrackFinder": RuntimeError: bad any_cast.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Component - Core Affects the Core module Component - Examples Affects the Examples module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants