For std::any
and std::variant
we have functions to request the object about current contained value, that return nullptr
if the request cannot be satisfied (just like dynamic_cast
does):
template<class ValueType>
const ValueType *any_cast(const any *operand);
template<class ValueType>
ValueType *any_cast(any *operand);
and
template <class T, class... Types>
std::add_pointer_t<T> get_if(variant<Types...> *pv);
template <class T, class... Types>
std::add_pointer_t<const T> get_if(const variant<Types...> *pv);
Both take pointer as an argument. Why? It is not efficient. The implementation has every time to check if the argument is not nullptr
. Does nullptr
argument makes any sense at all?
This functions can be class members or take a reference as an argument (may be with slightly different names). What are reasons for suboptimal design like this? Just mimic dynamic_cast
interface?
std::any. The class any describes a type-safe container for single values of any copy constructible type. 1) An object of class any stores an instance of any type that satisfies the constructor requirements or is empty, and this is referred to as the state of the class any object.
Empty variants are also ill-formed (std::variant<std::monostate> can be used instead). A variant is permitted to hold the same type more than once, and to hold differently cv-qualified versions of the same type.
std::any uses Small Buffer Optimization, so it will not dynamically allocate memory for simple types like ints, doubles… but for larger types it will use extra new . std::any might be considered 'heavy', but offers a lot of flexibility and type-safety.
Performance of std::any Also, invoking a std::any_cast to retrieve the value is quite slow compared to std::variant . The Boost equivalent of std::any , boost::any , provides a fast version of std::any_cast called boost::any_cast_unsafe which can be utilized if you know which type is contained.
To keep the function name down to one of them, and make it work like the built in _cast
operators, any_cast
works with either references or pointers.
The pointer versions take a pointer, and if it contains what you ask for, returns a pointer to the element. Otherwise it returns nullptr.
The reference versions take a reference, and throws if it does not contain what you ask for.
They used the pointer-ness of the argument to distinguish between those two options, matching how dynamic_cast<T&>(x)
and dynamic_cast<T*>(&x)
work.
It is going to be easy to inline. Inline checks against null of a pointer-to-automatic-storage object are easy to optimize to be "not null", as there are no conformant ways for an automatic storage object's address to be nullptr.
So in almost every case in release I would expect zero overhead from the check-if-nullptr. The one exception is where the code has a pointer-to-any (or variant), it has some proof that this pointer-to-any is not null that the compiler is unlikely to know, and it then passes that to any_cast
. If they had not used the pointer-to-indicate-pointer-return-type trick, one could "risk" UB with the any_cast_to_ptr(any&)
and unconditionally dereferencing the pointer.
The real API missing is the dangerous_any_cast
, which simply does UB if the type does not match, because nonlocally proven knowledge about the state of the any
seems more likely than nonlocally proven knowledge about the nullity of a pointer-to-any.
Such cases are rare.
As for get_if
vs get
, I do not know why there is no get_if(variant<???>&)
overload.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With