7

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?

Victor Dyachenko
  • 1,363
  • 8
  • 18
  • 4
    You're using a type-erased container and fetching its stored value, and you're worried about the performance of a comparison to `nullptr`? – Nicol Bolas Jul 27 '16 at 15:59
  • 1
    Yes. I worry about every avoidable overhead. And it is just ugly interface – Victor Dyachenko Jul 27 '16 at 16:05
  • 1
    There are versions of those functions that take references as arguments. They just throw on failure (instead of returning `nullptr`). – Barry Jul 27 '16 at 16:17
  • Yes, I now. But they are different sort of actions. They are assertions not requests. – Victor Dyachenko Jul 27 '16 at 16:19
  • "*They are assertions not requests.*" Consider the reason why you would make such a request. I'm fairly sure that a `variant` visitor would have superior performance to any code that uses a series of `get_if` calls. As for `any`, code which tests several alternatives as you do will have terrible performance regardless (not to mention the general ugliness of reading it). In short, you're *already* on the sub-optimal path; who cares if it's slightly less optimal than it could be? – Nicol Bolas Jul 27 '16 at 16:37
  • 1
    @Nicol Makes sense. But it isn't answer to my question – Victor Dyachenko Jul 27 '16 at 17:19
  • @VictorDyachenko: Your question makes no real sense. You are asking *WHY* the designers decided to create pointer versions of these functions. If you really want to know, ask the designers! But why not have them? The reference versions throw an exception if the cast fails, and exceptions are not always desired. So you have a choice - use the reference versions if you want exceptions, or use the pointer versions otherwise. References are usually implemented as pointers at the machine layer, there is no real performance difference one way or the other, but exceptions have overhead of their own. – Remy Lebeau Jul 27 '16 at 19:14
  • Please don't worry about performance like this until you use a profiler. Your processor most likely spends a most of a time elsewhere. You are only wasting your time which could be used to optimize parts of code where a performance optimization would bring noticeable results (most likely not here). – Marian Spanik Jul 27 '16 at 20:06

1 Answers1

6

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524