Strange use of `?:` Code in `typeid`

In one of the projects I'm working on, I see this code

struct Base { virtual ~Base() { } }; struct ClassX { bool isHoldingDerivedObj() const { return typeid(1 ? *m_basePtr : *m_basePtr) == typeid(Derived); } Base *m_basePtr; }; 

I have never seen typeid . Why is he doing this weird dance with ?: Instead of just doing typeid(*m_basePtr) ? Could there be some reason? Base is a polymorphic class (with virtual destructor).

EDIT: Elsewhere in this code, I see this, and it looks equivalent to "redundant"

 template<typename T> T &nonnull(T &t) { return t; } struct ClassY { bool isHoldingDerivedObj() const { return typeid(nonnull(*m_basePtr)) == typeid(Derived); } Base *m_basePtr; }; 
+56
c ++ conditional-operator micro-optimization
Jul 22. 2018-11-21T00:
source share
4 answers

I think this is optimization ! A little known and rarely (you could say never) typeid function was that null dereferencing the typeid argument typeid an exception instead of regular UB.

What? Are you serious? You're drunk?

Really. Yes. No.

 int * p = 0;
 * p;  // UB
 typeid (* p);  // throws

Yes, it's ugly, even by the C ++ standard for language ugliness.

OTOH, this does not work anywhere inside the typeid argument, so adding any clutter overrides this "function":

 int * p = 0;
 typeid (1? * p: * p);  // UB
 typeid (identity (* p));  // UB

For the record: I am not claiming in this post that automatically checking by the compiler that the pointer is not null before doing dereferencing is crazy thing. I am just saying what to do this check when dereferencing is a direct argument to typeid , and not elsewhere , absolutely crazy. (Perhaps it was a joke inserted in some draft and never deleted.)

For the record: I do not state in the previous Record that it makes sense for the compiler to insert automatic checks that the pointer is not null and to throw an exception (as in Java) when null is dereferenced: in general, throwing an exception to null dereference is absurd . This is a programming error, so an exception will not help. An approval error is called.

+46
Sep 26 '11 at 23:33
source share

The only effect that I see is that 1 ? X : X 1 ? X : X gives you X as an rvalue instead of plain X , which will be an lvalue. This might make a difference for typeid() for things like arrays (decaying to pointers), but I don't think it would be important if Derived is known as a class. Maybe it was copied from somewhere, where does the value matter? This would support the comment on "programming cult goods."

As for the comment below, I did a test and of course typeid(array) == typeid(1 ? array : array) , so in a sense I am wrong, but my misunderstanding may still correspond to the misunderstandings that lead to the source code!

+5
Jul 22 '11 at 20:59
source share

This behavior extends to [expr.typeid] / 2 (N3936):

When typeid is applied to a glvalue expression whose type is the type of a polymorphic class, the result refers to the std::type_info object representing the type of the derived object itself (that is, the dynamic type) to which the value of glvalue refers. If the glvalue expression is obtained by applying the unary operator * to the pointer, and the pointer is the value of the null pointer, the typeid expression typeid a type exception that will correspond to the exception handler of the std::bad_typeid .

Expression 1 ? *p : *p 1 ? *p : *p always an lvalue. This is because *p is an lvalue, and [expr.cond] / 4 says that if the second and third operands for the ternary operator have the same type and category of values, then the result of the operator has this type category and values ​​as well.

Therefore, 1 ? *m_basePtr : *m_basePtr 1 ? *m_basePtr : *m_basePtr is an lvalue of type Base . Since Base has a virtual destructor, it is a polymorphic type of class.

Therefore, this code is really an example of "When typeid is applied to a glvalue expression whose type is a polymorphic type of a class."




Now we can read the rest of the above quote. The glvalue expression was not "obtained by applying the unary operator * to a pointer" - it was obtained using the ternary operator. Therefore, the standard does not require an exception to be thrown if m_basePtr is null.

The behavior when m_basePtr is null will be covered by the more general null pointer dereferencing rules (which are actually a bit muddy in C ++, but for practical purposes, we assume that it causes undefined behavior here).




Finally: why would anyone write this? I think the curiousguy answer is the most plausible assumption: with this construct, the compiler does not need to insert a null pointer and code to throw an exception, so this is micro-optimization.

Presumably, the programmer is either pleased that this will never be called using a null pointer, or relies on specific processing to implement a null pointer dereferencing implementation.

+3
Jan 24 '15 at 0:04
source share

I suspect some kind of compiler was for a simple case

 typeid(*m_basePtr) 

returns typeid (Base) always, regardless of the type of runtime. But turning it into an expression / temporary / rvalue did the RTTI compiler.

Question: which compiler, when, etc. I think GCC had problems with typeid at an early stage, but it's a vague memory.

0
Aug 6 2018-11-11T00:
source share



All Articles