Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comma operator with typeid

I was studying A Generic Non-intrusive Smart Pointer Implementation. I have some confusion in section 4. One statement is

the expression supplied as the argument to the typeid operator is only evaluated if the result type is an lvalue of polymorphic class type.

And associated example code is:

template<typename T>
  void* startOfObject(T* p) {
  void* q=static_cast<void*>(p);
  typeid(q=dynamic_cast<void*>(p),*p); // This line
  return q;
}

AFAIU, it means q=dynamic_cast<void*>(p) will be evaluated if the result type is an lvalue of polymorphic class type. The result means the result of evaluating dynamic_cast<void*>(p) (I guess), so the dynamic_cast has to be applied in any case. The articles states (as I understand) that if p is not polymorphic then dynamic_cast will not be applied, but why? Before applying it, how can it be known whether the result is polymorphic or not? It will be helpful if someone describes in details how the full statement will be executed.

Another statement is

There is also a problem if p is NULL – the typeid will throw a std::bad cast.

The problem I see is with de-referencing if p is NULL, not with typeid (although it may throw bad_typeid, but that is not because of casting). dynamic_cast will return a NULL pointer of type void* if p is NULL, and typeid should be able to deduce the type information. Is that a typo, or am I missing something?

like image 896
Rakib Avatar asked Jun 06 '14 08:06

Rakib


People also ask

What does typeid return C++?

The typeid operator returns an lvalue of type const std::type_info that represents the type of expression expr. You must include the standard template library header <typeinfo> to use the typeid operator.

What is Typeid?

The typeid operator allows the type of an object to be determined at run time. The result of typeid is a const type_info& . The value is a reference to a type_info object that represents either the type-id or the type of the expression, depending on which form of typeid is used.

Is Typeid slow?

typeid() can be much faster than other type checks if all the stars are aligned, or it can be extremely slow. typeid() requires the compiler to determine if a pointer is null, so if you already know your pointer is non-null, it's more efficient to use typeid() on a reference instead.


3 Answers

It's a fancy trick to write essentially the following code

if (T is polymorphic)
  return dynamic_cast<void*>(p);
else
  return static_cast<void*>(p);

The trick used is that typeid(expr) is evaluated in one of two ways. If the compiler determines that expr has a non-polymorphic type, it goes ahead and uses its static type. But if expr has a dynamic type, it evaluates expr at runtime. The assignment before the comma operator is therefore evaluatad if and only if *p after the comma is polymorphic.

The null case is complex for that reason. If T is not polymorphic, then typeid(*p) is replaced by the compiler at compile time, and the runtime null pointer doesn't matter at all. If T is polymorphic, special handling of null pointer dereferences applies, and that special handling states that a std::bad_typeid exception is thrown.

like image 143
MSalters Avatar answered Sep 28 '22 10:09

MSalters


The comma operator has left-to-right associativity and is evaluated from left to right. That means the result of the expression q=dynamic_cast<void*>(p),*p is *p. So the dynamic cast is only evaluated if the type of *p is polymorphic.

Regarding the NULL problem the standard states:

When typeid is applied to an lvalue expression whose type is a polymorphic class type (10.3), the result refers to a type_info object representing the type of the most derived object (1.8) (that is, the dynamic type) to which the lvalue refers. If the lvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10), the typeid expression throws the bad_typeid exception (18.5.3).

like image 26
jasal Avatar answered Sep 28 '22 11:09

jasal


Explanation

Since C++ is a statically typed language the type of every expression is known at compile-time, even in cases that involves polymorphic behavior.

Whether a certain class is polymorphic or not is known at the time of compilation, this property will and cannot be changed between program executions.

If the compiler sees that the expr in typeid(expr) will not yield a value of polymorphic class type, it will simply make expr "unevaluated", which is equivalent of not executing it during run-time.


! ! !

It is important to note that the expr must still be valid, we cannot use typeid to potentially ignore a ill-formed expression; a compiler diagnostic must still be issued if the expression is ill-formed.

Just because the expr will be "unevaluated" does not mean that we can have it contain an ill-formed sub-expression.

struct A { }; // not polymorphic

...

A * ptr = ...;
typeid (dynamic_cast<void*> (ptr), *ptr); // ill-formed

Since ptr is not a pointer to a polymorphic class we cannot use it in dynamic_cast<T> where T = void*, as specified in [expr.dynamic.cast]p6.


Dereferencing a potential nullptr?

This will make typeid throw an exception, so it will not yield undefined behavior but one should be prepared to handle an exception if one uses such implementation.

5.2.8p2 Type identification [expr.typeid]

(...) If the glvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10), the typeid expression throws an exception (15.1) of a type that would match a handler of type std::bad_typeid exception (18.8.3)


Clarification

It means q=dynamic_cast(p) will be evaluated if the result type is an lvalue of polymorphic class type. The result means the result of evaluating dynamic_cast(p) (I guess).

No, what the author is saying is that if the resulting type of the entire expression is a polymorphic class type, than the expression will be evaluated.

Note: The "entire expression" refers to the ... in typeid(...), in your case; q=dynamic_cast<void*>(p),*p.


The Comma Operator

The comma operator will take two operands expr1 and expr2, it will start off by evaluating expr1 and then discard this value, after which it will evaluate expr2 and yield the value of this expression.

  • How does the comma operator work?

Putting it together

This means that the resulting type of using a comma operator is as if it only consisted of the right-hand-side expression, and in the following line the compiler will check so that the result of expr2 is a type which is a polymorphic class.

typeid (expr1, expr2)

typeid (q=dynamic_cast<void*>(p), *p)

// expr1 = q=dynamic_cast<void*>(p)
// expr2 = *p

Note: In C++03 the resulting type had to be a polymorphic lvalue, in C++11 this has been changed to glvalues of polymorphic class type.

like image 38
Filip Roséen - refp Avatar answered Sep 28 '22 12:09

Filip Roséen - refp