why doesn't std::any_cast support implicit conversion?
std::any_cast
is specified in terms of typeid
. To quote cppreference on this:
Throws
std::bad_any_cast
if thetypeid
of the requestedValueType
does not match that of the contents of operand.
Since typeid
doesn't allow the implementation to "figure out" an implicit conversion is possible, there's no way (to my knowledge) that any_cast
can know it's possible either.
To put it otherwise, the type erasure provided by std::any
relies on information available only at run-time. And that information is not quite as rich as the information the compiler has for figuring out conversions. That's the cost of type erasure in C++17.
To do what you want you'd need full code reflection and reification. That means every detail of every type would have to be saved to every binary (and every signature of every function on every type! And every template anywhere!), and when you ask to convert from an any to a type X you'd pass the data about X into the any, which would contain enough information about the type it contained to basically attempt to compile a conversion to X and fail or not.
There are languages that can do this; every binary ships with IR bytecode (or raw source) and an interpreter/compiler. These languages tend to be 2x or more slower than C++ at most tasks and have significantly larger memory footprints. It may be possible to have those features without that cost, but nobody has that language that I know of.
C++ doesn't have this ability. Instead, it forgets almost all facts about types during compilation. For any, it remembers a typeid which it can be used to get an exact match, and how to convert its storage to said exact match.
std::any
has to be implemented with type-erasure. That's because it can store any type and can't be a template. There is just no other functionality in C++ to achieve this at the moment.
What that means is that std::any
will store a type-erased pointer, void*
and std::any_cast
will convert that pointer to the specified type and that's it. It just does a sanity check using typeid
before to check whether you the type you cast it to is the one stored into the any.
Allowing implicit conversions would be impossible using the current implementation. Think about it (ignore the typeid
check for now).
std::any_cast<long>(a);
a
stores an int
and not a long
. How should std::any
know that? It can just cast its void*
to the type specified, dereference it and return it. Casting a pointer from one type to another is a strict aliasing violation and results in UB, so that's a bad idea.
std::any
would have to store the actual type of the object stored in it, which is not possible. You can't store types in C++ right now. It could maintain a list of types along with their respective typeid
s and switch over them to get the current type and perform the implicit conversion. But there is no way to do that for every single type that you are going to use. User defined types wouldn't work anyways, and you'd have to rely on things such as macros to "register" your type and generate the appropriate switch case for it1.
Maybe something like this:
template<typename T>
T any_cast(const any &Any) {
const auto Typeid = Any.typeid();
if (Typeid == typeid(int))
return *static_cast<int *>(Any.ptr());
else if (Typeid == typeid(long))
return *static_cast<long *>(Any.ptr());
// and so on. Add your macro magic here.
// What should happen if a type is not registered?
}
Is this a good solution? No, by far. The switch is costly, and C++'s mantra is "you don't pay for what you don't use" so no, there is no way of achieving this currently. The approach is also "hacky" and very brittle (what happens if you forget to register a type). In short, the possible benefits of doing something like this is not worth the trouble in the first place.
is there a workaround to allow an implicit conversion (in case the exact type that std::any holds is unknown)?
Yes, implement std::any
(or a comparable type) and std::any_cast
yourself using the macro register approach mentioned above1. I won't recommend it though. If you don't and can't know what type std::any
stores and need to access it, you have a possible design flaw.
1: Don't actually know if this is possible, I'm not that good in macro (ab)use. You can also hard-code your types in for your custom implementation or use a separate tool for it.