Weird use of `?:` in `typeid` code
Solution 1:
I think it is an optimisation! A little known and rarely (you could say "never") used feature of typeid
is that a null dereference of the argument of typeid
throws an exception instead of the usual UB.
What? Are you serious? Are you drunk?
Indeed. Yes. No.
int *p = 0; *p; // UB typeid (*p); // throws
Yes, this is ugly, even by the C++ standard of language ugliness.
OTOH, this does not work anywhere inside the argument of typeid
, so adding any clutter will cancel this "feature":
int *p = 0; typeid(1 ? *p : *p); // UB typeid(identity(*p)); // UB
For the record: I am not claiming in this message that automatic checking by the compiler that a pointer is not null before doing a dereference is necessarily a crazy thing. I am only saying that doing this check when the dereference is the immediate argument of typeid
, and not elsewhere, is totally crazy. (Maybe is was a prank inserted in some draft, and never removed.)
For the record: I am not claiming in the previous "For the record" that it makes sense for the compiler to insert automatic checks that a pointer is not null, and to to throw an exception (as in Java) when a null is dereferenced: in general, throwing an exception on a null dereference is absurd. This is a programming error so an exception will not help. An assertion failure is called for.
Solution 2:
The only effect I can see is that 1 ? X : X
gives you X
as an rvalue instead of plain X
which would be an lvalue. This can matter to typeid()
for things like arrays (decaying to pointers) but I don't think it would matter if Derived
is known to be a class. Perhaps it was copied from someplace where the rvalue-ness did matter? That would support the comment about "cargo cult programming"
Regarding the comment below I did a test and sure enough typeid(array) == typeid(1 ? array : array)
, so in a sense I'm wrong, but my misunderstanding could still match the misunderstanding that lead to the original code!
Solution 3:
This behaviour is covered by [expr.typeid]/2 (N3936):
When
typeid
is applied to a glvalue expression whose type is a polymorphic class type, the result refers to astd::type_info
object representing the type of the most derived object (that is, the dynamic type) to which the glvalue refers. If the glvalue expression is obtained by applying the unary*
operator to a pointer and the pointer is a null pointer value, thetypeid
expression throws an exception of a type that would match a handler of typestd::bad_typeid
exception.
The expression 1 ? *p : *p
is always an lvalue. This is because *p
is an lvalue, and [expr.cond]/4 says that if the second and third operand to the ternary operator have the same type and value category, then the result of the operator has that type and value category also.
Therefore, 1 ? *m_basePtr : *m_basePtr
is an lvalue with type Base
. Since Base
has a virtual destructor, it is a polymorphic class type.
Therefore, this code is indeed an example of "When typeid
is applied to a glvalue expression whose type is a polymorphic class type" .
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 via the ternary operator. Therefore the standard does not require that an exception be thrown if m_basePtr
is null.
The behaviour in the case that m_basePtr
is null would be covered by the more general rules about dereferencing a null pointer (which are a bit murky in C++ actually but for practical purposes we'll assume that it causes undefined behaviour here).
Finally: why would someone write this? I think curiousguy's answer is the most plausible suggestion so far: with this construct, the compiler does not have to insert a null pointer test and code to generate an exception, so it is a micro-optimization.
Presumably the programmer is either happy enough that this will never be called with a null pointer, or happy to rely on a particular implementation's handling of null pointer dereference.