The question is : what is the defining characteristic/s of RTTI?
Pure RTTI seems to be exactly equivalent to 'dynamic_downcast', like,
say, the Eiffel assignment attempt. But what about that is it that
makes it "Run Time" (which we've had such a hard time pinning down),
and what about that is it that makes it "Type Information" (ditto)?
That's a bit subtle, and I agree with Tom Usenet that that is probably
why we have not managed to agree.
As I see it:
"Run Time" --> Only _knowable_ at run time, i.e. potentially
changing over time for the same code snippet.
"Type Info" --> An _explicit representation_ of a type.
I think you all agree with the "Run Time" definition, and probably
associate "potentially changing" in the context of exception handling
with "changing catch statement" depending on how a 'throw' is reached.
The "Type Info" is a bit harder, but here's an example, namely the
'throw'/'catch' compatibility matrix revisited. This matrix is in its most
basic form indexed by 'throw' statements and 'catch' statements. But in
typical code the number of thrown types tends to be very small, a handful
or so, and likewise for the number of caught types. And so all rows
representing 'throw' statements of the same type can essentially be
collapsed into one common row, plus an external vector that maps 'throw'
statements to rows. And ditto for columns indexed by 'catch' statements.
In this optimization a single row that represents a set of 'throw'
statements of the same type is in effect an identification of that type
in the sense that there is a one-to-one mapping between the relevant
types and corresponding rows. But it is not an explicit representation,
for given the (index to) a row one cannot obtain any information about
the type except via additional otherwise unnecessary stored associations,
nor can the row be used for anything but 'catch'/'throw' compatibility
checking. And the row is not "only knowable at run-time"; it can be (and
must, in order to practically useful, be) constructed at compile time.
For columns the situation is more complex, for a column index is only
available when a stack frame with a containing 'catch' has been found.
So isn't this column index then both an explicit representation of a
type (the 'catch' type), and only knowable at run-time? Yes to the last,
but no to the first: it's not an explicit representation, it cannot be
used to determine anything about the type involved (except via additional
and otherwise unnecessary information).
In short, all that's necessary for C++ exception handling is _implicit_
type representation that cannot be used for any other purpose. And so
it's not surprising that C++ EH cannot emulate 'dynamic_cast'. Or that
the functionality of 'dynamic_cast', or basic RTTI, is not required.
The end result of this analysis-by-example is that the concept of RTTI
hinges crucially on _explicit_ type representation.
No, you've shown that exception handling *doesn't* hinge on explicit
type representation. This is also true of dynamic_cast. Here's a
rewrite of your example:
The "Type Info" is a bit harder, but here's an example, namely the
'constructor call'/'dynamic_cast' compatibility matrix revisited. This
matrix is in its most basic form indexed by 'constructor call'
statements and 'dynamic_cast' statements. In typical code the number
of created types tends to be quite large, but the number of types
dynamically casted to tends to be small. And so all rows representing
'constructor call' statements of the same type can essentially be
collapsed into one common row, plus an external vector that maps
'constructor call' statements to rows. And ditto for columns indexed
by 'dynamic_cast' statements.
In this optimization a single row that represents a set of
'constructor call' statements of the same type is in effect an
identification of that type in the sense that there is a one-to-one
mapping between the relevant types and corresponding rows. But it is
not an explicit representation, for given the (index to) a row one
cannot obtain any information about the type except via additional
otherwise unnecessary stored associations, nor can the row be used for
anything but 'dynamic_cast' compatibility checking.
For columns the situation is simple. The column index is known for a
particular 'dynamic_cast' statement at compile time. For rows the
situation is more complex. The row index is only found when the object
begin passed to the 'dynamic_cast' statement is known, and this is
only knowable in general at runtime. So isn't this row index then both
an explicit representation of a type (the 'constructor call' type),
and only knowable at run-time? Yes to the last, but no to the first:
it's not an explicit representation, it cannot be used to determine
anything about the type involved (except via additional and otherwise
unnecessary information).
In short, all that's necessary for C++ dynamic_cast is _implicit_
type representation that cannot be used for any other purpose.
Some very thinking-
circuit-activating books have been written about just that little
crucial but hard-to-pin-down difference. Hofstadter's "Gödel, Escher,
Bach" comes to mind, as well as Scott Fahlman's "NETL", not to mention
Wolfram's "A new kind of physics". It's no wonder that we find it hard
to agree on the dividing line...
Your argument is specious, since the fact that exception handling does
not require RTTI can be simply extended to an argument that
dynamic_cast doesn't require RTTI. Either your definition of RTTI is
wrong, or dynamic_cast doesn't require RTTI.
However, neither of these arguments change the fact that matching
catch blocks and performing dynamic_casts both conceptually involve
taking a type, and checking it to see whether it is derived from
another type, and performing this check at runtime.
If your position is simply that you choose not to call that RTTI, we
can agree to disagree.
Tom