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 the typeid of the requested ValueType 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 typeids 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.

Tags:

C++

C++17