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?
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.
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.
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.
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.
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).
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
.
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 typestd::bad_typeid
exception (18.8.3)
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.
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.
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