为何std::any_cast不支持隐式转换?转换可行时仍抛bad_any_cast异常?
Great question! Let's break this down step by step to understand the design choices behind std::any_cast, why it throws even when implicit conversion seems possible, and how to handle implicit conversions when you don't know the exact stored type.
The Core Design of std::any
First, let's recall what std::any is for: it's a type-safe container for storing a single value of any type. Its biggest priority is type safety—it ensures you don't accidentally treat a value as the wrong type, which could lead to crashes, undefined behavior, or subtle bugs.
std::any_cast is the tool for retrieving values from std::any, and it's intentionally strict. It only succeeds if the type you request exactly matches the type stored in the std::any (checked via typeid). Implicit conversions are not part of its job because they introduce ambiguity and risk:
- An implicit
double→intconversion could silently lose precision. - An
int→boolconversion might not do what you expect (any non-zero int becomestrue). - Allowing implicit conversions would blur the line between "retrieving the exact type I stored" and "converting to a related type"—two distinct operations.
Why Implicit Conversion Doesn't Fix the bad_any_cast Exception
Take your example:
std::any a = 10; // Stores an int auto b = std::any_cast<long>(a); // Throws std::bad_any_cast
Even though int can be implicitly converted to long, std::any_cast doesn't care. It checks if the stored type (int) is identical to the requested type (long). Since they're different types (different typeid values), it throws immediately.
This isn't an oversight—it's by design. std::any_cast is meant to answer the question: "Is this std::any holding exactly a long?" If not, it fails. Conversion logic is left to you, the developer, because you know what's safe for your use case.
Workarounds for Implicit Conversion with std::any
If you need to handle implicit conversions when you don't know the exact stored type, here are a few practical approaches:
1. Build a Flexible Conversion Wrapper
Create a template function that tries to extract common types and convert them to your target type. This uses std::is_convertible_v to check if a conversion is possible, and falls back through potential types:
#include <any> #include <stdexcept> #include <type_traits> template <typename Target> Target any_cast_convert(const std::any& a) { // First, try exact match if (a.type() == typeid(Target)) { return std::any_cast<Target>(a); } // Try converting from common types (extend this list as needed) if constexpr (std::is_convertible_v<int, Target>) { try { return static_cast<Target>(std::any_cast<int>(a)); } catch (const std::bad_any_cast&) {} } if constexpr (std::is_convertible_v<double, Target>) { try { return static_cast<Target>(std::any_cast<double>(a)); } catch (const std::bad_any_cast&) {} } if constexpr (std::is_convertible_v<std::string, Target>) { try { return static_cast<Target>(std::any_cast<std::string>(a)); } catch (const std::bad_any_cast&) {} } // If none of the above work, throw throw std::bad_any_cast(); } // Usage example int main() { std::any a = 10; long b = any_cast_convert<long>(a); // Successfully gets 10L std::any c = 3.14; int d = any_cast_convert<int>(c); // Gets 3 (implicit double→int conversion) return 0; }
2. Use std::variant If You Know Possible Types
If you can list all possible types your container might hold, std::variant is a better fit than std::any. It lets you use std::visit to handle conversions cleanly:
#include <variant> #include <iostream> int main() { std::variant<int, double, std::string> v = 10; long b = std::visit([](auto&& val) -> long { return static_cast<long>(val); // Implicit conversion happens here }, v); std::cout << b << std::endl; // Outputs 10 return 0; }
std::variant is more type-safe than std::any in this scenario because it knows exactly what types it can hold, so you don't have to guess or handle unexpected types.
3. Manual Dynamic Type Checking
For simple cases, you can directly check the stored type with typeid and convert manually:
#include <any> #include <typeinfo> #include <stdexcept> long get_long_from_any(const std::any& a) { if (a.type() == typeid(int)) { return static_cast<long>(std::any_cast<int>(a)); } else if (a.type() == typeid(long)) { return std::any_cast<long>(a); } else if (a.type() == typeid(double)) { return static_cast<long>(std::any_cast<double>(a)); } else { throw std::bad_any_cast(); } }
This is straightforward but less scalable—you'll need to add a new if clause for every type you want to support.
内容的提问来源于stack exchange,提问作者Timo

