14. Casting

Reasonable men do not change the world.

– G.B.Shaw

Major and minor extensions — the need for run-time type information (RTTI) — dynamic_cast — syntax — which types support RTTI — casting from a virtual base — uses and misuses of RTTI — typeid() — class type_info — extended type information — a simple object I/O system — rejected alternatives new casts — static_castreinterpret_castconst_cast — using new-style casts.

14.1 Major Extensions

Templates (§15), exceptions (§16), run-type type information (§14.2), and name-spaces (§17) are often referred to as major extensions. What makes them major -whether seen as extensions or as integral features of C++ – is that they affect the way programs can be organized. Since C++ was created primarily to allow new ways of organizing programs rather than simply to provide more convenient ways of expressing traditional designs, the major features are the ones that matter.

Minor features are therefore considered minor because they don’t affect the overall structure of a program. They don’t affect design. They are not minor because they involve only a few lines of manual text to define or require only a few lines of compiler code to implement. In fact, some major features are easier to describe and implement than some minor features.

Naturally, not every feature fits neatly into the simple minor and major categories. For example, nested functions could be seen as either minor or major depending on how important you consider their use in expressing iteration. However, my policy over the years has been to work hard on a few major extensions while trying to minimize minor extension. Curiously enough, the volume of interest and public debate is often inversely proportional to the importance of a feature. The reason is that it is much easier to have a firm opinion on a minor feature than on a major one; minor features fit directly into the current state of affairs, whereas major ones – by definition – do not.

Because support for building libraries and for composing software out of semi-independent parts is a key aim of C++, the major extensions relate to that: templates, exception handling, run-time type identification, and namespaces. Of these, templates and exception handling were part of my view of what C++ should be even in the pre-Release-1.0 days (§2.9.2 and §3.15). Run-time type identification was considered even in the first draft of C++ (§3.5), but postponed in the hope that it would prove unnecessary. Namespaces is the only major extension beyond the original conception of C++, yet even it is a response to a problem that I unsuccessfully tried to solve in the first version of C++ (§3.12).

14.2 Run-Time Type Information

In many ways, the discussion about mechanisms for run-time determination of the type of an object resembled the discussions about multiple inheritance (§12.6). Multiple inheritance was perceived as the first major extension to the original C++ definition. Run-Time Type Information, often called RTTI, was the first major extension beyond the features mandated for the standardization process and presented in the ARM.

Again, a new style of programming was being directly supported by C++. Again some

– Declared the support unnecessary

– Declared the new style inherently evil (“against the spirit of C++”)

– Deemed it too expensive

– Thought it too complicated and confusing

– Saw it as the beginning of an avalanche of new features

In addition, RTTI attracted criticism related to the C/C++ casting mechanism in general. For example, many dislike that (old-style) casts can be used to bypass the access control for private base classes and can cast away const. These criticisms are well founded and important; they are discussed in §14.3.

Again, I defended the new feature on the grounds that it was important to some, harmless to people who didn’t use it, that if we didn’t support it directly people would simply simulate it, and that it was easy to implement. To support the last claim, I produced an experimental implementation in two mornings. This makes RTTI at least two orders of magnitude simpler than exceptions and templates, and more than an order of magnitude simpler than multiple inheritance.

The original impetus for adding facilities for determining the type of an object at run time to C++ came from Dmitry Lenkov [Lenkov,1991]. Dmitry in turn built on experience from major C++ libraries such as Interviews [Linton, 1987], the NIH library [Gorlen,1990], and ET++ [Weinand,1988]. The dossier mechanism [Inter-rante,1990] was also available for examination.

The RTTI mechanisms provided by libraries are mutually incompatible, so they become a barrier to the use of more than one library. Also, all require considerable foresight from base class designers. Consequently, a language-supported mechanism was needed.

I got involved in the detailed design for such mechanisms as the coauthor with Dmitry of the original proposal to the ANSI/ISO committee and as the main person responsible for the refinement of the proposal in the committee [Stroustrup,1992]. The proposal was first presented to the committee at the London meeting in July 1991 and accepted at the Portland, Oregon meeting in March 1993.

The run-time type information mechanism consists of three parts:

– An operator, dynamic_cast, for obtaining a pointer to an object of a derived class given a pointer to a base class of that object. The operator dynamic_cast delivers that pointer only if the object pointed to really is of the specified derived class; otherwise it returns 0.

– An operator, typeid, for identifying the exact type of an object given a pointer to a base class.

– A structure, type_info, acting as a hook for further run-time information associated with a type.

To conserve space, the RTTI discussion is almost completely restricted to pointers.

14.2.1 The Problem

Assume that a library supplies class dialog_box and that its interfaces are expressed in terms of dialog_boxes. I, however, use both dialog_boxes and my own dbox_w_strs:

class dialog_box : public window { // library class
    // ...
public:
    virtual int ask();
    // ...
};

class dbox_w_str : public dialog_box { // my class
    // ...
public:
    int ask();
    virtual char* get_string();
    // ...
};

So, when the system/library hands me a pointer to a dialog_box, how can I know whether it is one of my dbox_w_strs?

Note that I can’t modify the library to know my dbox_w_str class. Even if I could, I wouldn’t, because then I would have to worry about dbox_w_strs in new versions of the library and about errors I might have introduced into the “standard” library.

14.2.2 The dynamic_cast Operator

A naive solution would be to find the type of the object pointed to and compare that to my dbox_w_str class:

void my_fct(dialog_box* bp)
{
    if (typeid(*bp) == typeid(dbox_w_str)) { // naive

        dbox_w_str * dbp = (dbox_w_str*)bp;

         // use dbp
    }
    else {

        // treat *bp as a ''plain'' dialog box
    }
}

Given the name of a type as the operand, the typeid() operator returns an object that identifies it. Given an expression operand, typeid() returns an object that identifies the type of the object that the expression denotes. In particular, typeid (*bp) returns an object that allows the programmer to ask questions about the type of the object pointed to by bp. In this case, we asked if that type was identical to the type dbox_w_str.

This is the simplest question to ask, but it is typically not the right question. The reason to ask is to see if some detail of a derived class can be safely used. To use it, we need to obtain a pointer to the derived class. In the example, we used a cast on the line following the test. Further, we typically are not interested in the exact type of the object pointed to, but only in whether we can safely perform that cast. This question can be asked directly using the dynamic_cast operator:

void my_fct(dialog_box* bp)
{
    if (dbox_w_str* dbp = dynamic_cast<dbox_w_str*>(bp)) {
        
        // use dbp
    }
    else {

        // treat *pb as a ''plain'' dialog box
    }
}

The dynamic_cast<T*> (p) operator converts its operand p to the desired type T* if *p really is a T or a class derived from T; otherwise, the value of dynamic_cast<T*> (p) is 0.

There are several advantages to merging the test and the cast into a single operation:

– A dynamic cast makes it impossible to mismatch the test and the cast.

– By using the information available in the type-information objects, it is possible to cast to types that are not fully defined in the scope of the cast.

– By using the information available in the type information objects, it is often possible to cast from a virtual base class to a derived class (§14.2.2.3).

– A static cast can’t give the correct result in all cases (§ 14.3.2.1).

The dynamic_cast operator serves the majority of needs I have encountered. I consider dynamic_cast to be the most important part of the RTTI mechanism and the construct users should focus on.

dynamic_cast operator can also be used to cast to a reference type. In case of failure, a cast to a reference throws a bad_cast exception. For example:

void my_fct(dialog_box& b)
{
    dbox_w_str& db = dynamic_cast<dbox_w_str&>(b);

    // use db
}

I use a reference cast when I want an assumption about a reference type checked and consider it a failure for my assumption to be wrong. If, instead, I want to select among plausible alternatives, I use a pointer cast and test the result.

I don’t recall exactly when I settled on a run-time-checked cast as my preferred way of dealing with run-time type checking should direct language support become necessary. The idea was first suggested to me by someone at Xerox PARC during a visit there in 1984 or 1985. The suggestion was to have ordinary casts do checking. As mentioned in §14.2.2.1, this variant has problems with overhead and compatibility, but I saw that some form of casts would help minimize the misuses that a switch-on-type mechanism, such as Simula’s INSPECT, makes so tempting.

14.2.2.1 Syntax

The discussion of what the dynamic_cast operator should look like reflected both pure syntactic concerns and concerns about the nature of conversions.

Casts are one of the most error-prone facilities in C++. They are also one of the ugliest syntactically. Naturally, I considered if it would be possible to

[1] Eliminate casts.

[2] Make casts safe.

[3] Provide a cast syntax that makes it obvious that an unsafe operation is used.

[4] Provide alternatives to casting and discourage the use of casts.

Basically, dynamic_cast reflects the conclusion that a combination of [3] and [4] seems feasible, whereas [1] and [2] are not.

Considering [1], we observed that no language supporting systems programming has completely eliminated the possibility of casting and that even effective support for numeric work requires some form of type conversion. Thus, the aim must be to minimize the use of casts and make them as well behaved as possible. Starting from that premise, Dmitry and I devised a proposal that unified dynamic and static casts using the old-style cast syntax. This seemed a good idea, but upon closer examination several problems were uncovered:

[1] Dynamic casts and ordinary unchecked casts are fundamentally different operations. Dynamic casts look into objects to produce a result and may fail with a run-time indication of that failure. Ordinary casts perform an operation that is determined exclusively by the types involved and doesn’t depend on the value of the object involved (except for occasional checking for null pointers). An ordinary cast doesn’t fail; it simply produces a new value. Using the cast syntax for both dynamic and static casts led to confusion about what a given cast expression really did.

[2] If dynamic casts are not syntactically distinguished, it is not possible to find them easily (grep for them, to use Unix-speak).

[3] If dynamic casts are not syntactically distinguished, it is not possible for the compiler to reject unsuitable uses of dynamic casts; it must simply perform whatever kind of cast can be done for the types involved. If distinguished, it can be an error to attempt a dynamic cast for objects that don’t support runtime checking.

[4] The meaning of programs using ordinary casts would change if run-time checking were applied wherever feasible. Examples are casts to undefined classes and casts within multiple inheritance hierarchies (§14.3.2). We did not manage to convince ourselves that this change would leave the meaning of all reasonable programs unchanged.

[5] The cost of checking would be incurred even for old programs that already carefully checked that casts were viable using other means.

[6] The suggested way of “turning off checking,” casting to and from void*, wouldn’t be perfectly reliable because the meaning would be changed in some cases. These cases might be perverted, but because understanding of the code would be required, the process of turning off checking would be manual and error-prone. We are also against techniques that would add yet more uncheck-able casts to programs.

[7] Making some casts “safe” would make casting more respectable; yet the long-term aim is to decrease the use of all casts (including dynamic casts).

After much discussion, we found this formulation: “Would our ideal language have more than one notation for type conversion?” For a language that distinguishes fundamentally different operations syntactically the answer is yes. Consequently, we abandoned the attempt to “hijack” the old cast syntax.

We considered if it would be possible to deprecate the old cast syntax in favor of something like:

Checked<T*>(p); // run-time checked conversion of p to a T*
Unchecked<T*>(p); // unchecked conversion of p to a T*

This would eventually make all conversions obvious, thus eliminating problems arising from traditional casts being hard to spot in C and C++ programs. It would also give all casts a common syntactic pattern based on the <T*> template notation for types (§15). This line of thought led to the alternative cast syntax presented in §14.3.

Not everyone likes the template syntax, though, and not everyone who likes the template syntax likes its use for cast operators. Consequently, we discussed and experimented with alternatives.

The (?T*) p notation was popular for some time because it mirrors the traditional cast syntax (T*) p. Others disliked it for exactly that reason, and many considered (?T*) p “far too cryptic.” Worse, I discovered a critical flaw. The most common mistake when using (?T*) p was to forget the ?. What was meant to be a relatively safe and checked conversion becomes a completely different, unchecked, and unsafe operation. For example:

if (dbox_w_string* p = (dbox_w_string*)q) // dynamic cast
{
    // *q is a dbox_w_string
}

Oops! Forgetting the ? and thus turning the comment into a lie was found to be uncomfortably common. Note that we cannot grep for occurrences of old-style casts to protect against this kind of mistake, and those of us with a strong C background are most prone to make the mistake and to miss it when reading the code.

Among the other alternatives considered, the notation

(virtual T*)p

was the most promising. It is relatively easy to spot for humans and for tools, the word virtual indicates the logical connection to classes with virtual functions (polymorphic types), and the general syntactic pattern is that of a traditional cast. However, it too was considered “too cryptic” by many and attracted the hostility of people who disliked the old cast syntax. Personally, I weakly agreed with that criticism, feeling that the dynamic_cast syntax simply fit better into C++ (as many who had significant experience using templates did). I also considered it an advantage that dynamic_cast provided a cleaner syntactic pattern that might eventually be used as an alternative to the old casts (§14.3).

14.2.2.2 When can we use Dynamic Casts?

The introduction of run-time type identification separates objects into two categories:

[1] Ones that have run-time type information associated so that their type can be determined (almost) independently of context.

[2] Those that haven’t.

Why? We cannot impose the burden of being able to identify an object’s type at runtime on built-in types such as int and double without unacceptable costs in runtime, space, and layout-compatibility problems. A similar argument applies to simple class objects and C-style structs. Consequently, from an implementation point of view, the first acceptable dividing line is between objects of classes with virtual functions and classes without. The former can easily provide run-time type information, the latter cannot.

Further, a class with virtual functions is often called a polymorphic class, and polymorphic classes are the only ones that can be safely manipulated through a base class. By “safely,” I here mean that the language provides guarantees that objects are used only according to their defined type. Naturally, individual programmers can in specific cases demonstrate that manipulations of a non-polymorphic class don’t violate the type system.

From a programming point of view, it therefore seems natural to provide run-time type identification for polymorphic types only: They are exactly the ones for which C++ supports manipulation through a base class. Supporting RTTI for a non-polymorphic type would simply provide support for switch-on-type-field programming. Naturally the language should not make that style impossible, but I saw no need to complicate the language solely to accommodate it.

Experience shows that providing RTTI for polymorphic types (only) works acceptably. However, people can get confused about which objects are polymorphic and thus about whether a dynamic cast can be used or not. Fortunately, the compiler will catch the errors when the programmer guesses wrong. I looked long and hard for an acceptable way of explicitly saying “this class supports RTTI (whether it has virtual functions or not),” but didn’t find one that was worth the effort of introducing it.

14.2.2.3 Casting from Virtual Bases

The introduction of the dynamic_cast operator allowed us to provide a way to circumvent an old problem. It is not possible to cast from a virtual base class to a derived class using an ordinary cast. The reason for the restriction is that there is insufficient information available in an object to implement a cast from a virtual base to one of the classes derived from it; see §12.4.1.

However, the information needed to provide run-time type identification includes the information needed to implement the dynamic cast from a polymorphic virtual base. Therefore, the restriction against casting from a virtual base need not apply to dynamic casts from polymorphic virtual base classes:

class B { /* ... */ virtual void f(); };
class V { /* ... */ virtual void g(); };
class X { /* no virtual functions */ };

class D: public B, public virtual V, public virtual X {
    // ...
};
void g(D& d)
{
       B* pb = &d;
       D* p1 = (D*)pb;               // ok, unchecked
       D* p2 = dynamic_cast<D*>(pb); // ok, run-time checked

       V* pv = &d;
       D* p3 = (D*)pv; // error: cannot cast from virtual base
       D* p4 = dynamic_cast<D*>(pv); // ok, run-time checked

       X* px = &d;
       D* p5 = (D*)px; // error: cannot cast from virtual base
       D* p6 = dynamic_cast<D*>(px); // error: can't cast from
                                     // non-polymorphic type
}

Naturally, such a cast can only be performed when the derived class can be unambiguously determined.

14.2.3 Uses and Misuses of RTTI

One should use explicit run-time type information only when one has to. Static (compile-time) checking is safer, implies less overhead, and – where applicable – leads to better-structured programs. For example, RTTI can be used to write thinly disguised switch statements:

    // misuse of run-time type information:

void rotate(const Shape& r)
{
    if (typeid(r) == typeid(Circle)) {
        // do nothing
    }
    else if (typeid(r) == typeid(Triangle)) {
        // rotate triangle
    }
    else if (typeid(r) == typeid(Square)) {
        // rotate square
    }
    // ...
}

I have heard this style described as providing “the syntactic elegance of C combined with the run-time efficiency of Smalltalk,” but that is really too kind. The real problem is that this code does not handle classes derived from the ones mentioned correctly and needs to be modified whenever a new class is added to the program.

Such code is usually best avoided through the use of virtual functions. It was my experience with Simula code written this way that caused facilities for run-time type identification to be left out of C++ in the first place (§3.5).

For many people trained in languages such as C, Pascal, Modula-2, Ada, etc., there is an almost irresistible urge to organize software as a set of switch statements. This urge should most often be resisted. Please note that even though the standards committee approved a RTTI mechanism for C++, we did not support it with a type-switch statement (such as Simula’s INSPECT statement). I still don’t consider a type switch a model of software organization worth supporting directly. The examples where it is appropriate are far fewer than most programmers believe at first – and by the time a programmer has second thoughts, the reorganization needed most likely will involve too much work to be done.

Many examples of proper use of RTTI arise where some service code is expressed in terms of one class and a user wants to add functionality through derivation. The dialog_box example from §14.2.1 is an example of this. If the user is willing and able to modify the definitions of the library classes, say dialog_box, then the use of RTTI can be avoided; if not, it is needed. Even if the user is willing to modify the base classes, such modification may have its own problems. For example, it may be necessary to introduce dummy implementations of virtual functions such as get_string() in classes for which the virtual functions are not needed or not meaningful. This problem is discussed in some detail in [2nd,§13.13.6] under the heading of “Fat Interfaces.” A use of RTTI to implement a simple object I/O system can be found in §14.2.7.

For people with a background in languages that rely heavily on dynamic type checking, such as Smalltalk, it is tempting to use RTTI in conjunction with overly general types. For example:

    // misuse of run-time type information:

class Object { /* ... */ };

class Container : public Object {
public:
    void put(Object*);
    Object* get();
    // ...
};

class Ship : public Object { /* ... */ };

Ship* f(Ship* ps, Container* c)
{
    c->put(ps);
    // ...
    Object* p = c->get();
    if (Ship* q = dynamic_cast<Ship*>(p)) // run-time check
        return q;

    // do something else (typically, error handling)
}

Here, class Object is an unnecessary implementation artifact. It is overly general because it does not correspond to an abstraction in the application domain and forces the application programmer to operate at a lower level of abstraction. Problems of this kind are often better solved by using container templates holding only a single kind of pointer:

template<class T> class Container {
public:
    void put(T*);
    T* get();
    // ...
};

Ship* f(Ship* ps, Container<Ship>* c)
{
    c->put(ps);
    // ...
    return c->get();
}

Combined with the use of virtual functions, this technique handles most cases.

14.2.4 Why Provide a “Dangerous Feature?”

So, if I confidently predict misuses of RTTI, why did I design the mechanism and work for its adoption?

Good programs are achieved through good education, good design, adequate testing, etc., not by providing language features that supposedly can be used only in “the right way.” Every useful feature can be misused, so the question is not whether a feature can be misused (it can), or whether it will be misused (it will). The questions are whether the good uses are sufficiently critical to warrant the effort of providing a feature, whether the effort of simulating a feature using other language features is manageable, and whether the misuses can be kept to a reasonable level by proper education.

Having considered RTTI for some time, I became convinced that we faced a classical standardization problem:

– Most major libraries provide a RTTI feature.

– Most provide it in a form that requires significant and error-prone user cooperation for it to work correctly.

– All provide it in incompatible ways.

– Most provide it in a non-general way.

– Most present it as a “neat feature” that users ought to try rather than a dangerous mechanism to be used as a last resort.

– In every major library there seem to be (only) a few cases where RTTI is critical in the sense that without it there would be a facility that the library couldn’t offer or could only offer by imposing a significant burden on users and implementers.

By providing standard RTTI, we can overcome one barrier to the use of libraries from different sources (see §8.2.2). We can provide a coherent view of the use of RTTI and try to make it as safe as possible and provide warnings against misuse.

Finally, it has been a guideline in the design of C++ that when all is said and done the programmer must be trusted. What good can be done is more important than what mistakes might be made. C++ programmers are supposed to be adults and need only minimal “nannyism.”

Not everyone is convinced, though. Some, notably Jim Waldo, argue strongly that RTTI is needed so infrequently and the misconceptions that are the roots of misuses of RTTI are so widespread that the net effect of RTTI must be detrimental to C++. Only time will tell for sure. The greatest danger from misuse comes from programmers who consider themselves so expert that they see no need to consult a C++ textbook before starting to use C++ (§7.2).

14.2.5 The typeid() Operator

I had hoped that the dynamic_cast operator would serve all common needs so that no further RTTI features needed to be presented to users. However, most other people I discussed the issue with disagreed and pointed to two further needs:

[1] A need to find the exact type of an object; that is, being told that an object is of class X, rather than just that it is of class X or some class derived from class X the way dynamic_cast does.

[2] Using the exact type of an object as a gateway to information describing properties of that type.

Finding the exact type of an object is sometimes referred to as type identity, so I named the operator yielding it typeid.

The reason people want to know the exact type of an object is usually that they want to perform some standard service on the whole object. Ideally, such services are presented as a virtual function so that the exact type needn’t be known, but when – for some reason – no such function is available, finding the exact type and then performing the operation is necessary. People have designed object I/O and database systems working this way. In those cases, no common interface can be assumed for every object manipulated so the detour through the exact type becomes necessary. Another, much simpler, use has been to obtain the name of a class for diagnostic output:

cout << typeid(*p).name();

The typeid() operator is used explicitly to gain access to information about types at run time; typeid() is a built-in operator. Had it been a function, its declaration would have looked something like this:

class type_info;
const type_info& typeid (<i>type-name</i>) ; // pseudo declaration
const type_info& typeid (<i>expression</i>) ; // pseudo declaration

That is, typeid() returns a reference to an unknown type called type_info. Given a type-name as its operand, typeid() returns a reference to a type_info that represents the type-name. Given an expression as its operand, typeid() returns a reference to a type_info that represents the type of the object denoted by the expression.

The reason typeid() returns a reference to type_info rather than a pointer is that we wanted to disable the usual pointer operators such as == and ++ on the result of typeid(). For example, it is not clear that every implementation will be able to guarantee uniqueness of type identification objects. This implies that comparing typeid()s can’t simply be defined as comparing pointers to type_info objects. With typeid() returning a type_info&, there is no problem defining == to cope with possible duplication of type_info objects for a single type.

14.2.5.1 Class type_info

Class type_info is defined in the standard header file <type_info.h>, which needs to be included for the result of typeid() to be used. The exact definition of class type_info is implementation dependent, but it is a polymorphic type that supplies comparisons and an operation that returns the name of the type represented:

class type_info {
  // implementation-dependent representation

private:
  type_info(const type_info&);             // users can't
  type_info& operator=(const type_info&);  // copy type_info

public:
  virtual ~type_info();                    // is polymorphic

  int operator==(const type_info&) const;  // can be compared
  int operator!=(const type_info&) const;
  int before(const type_info&) const;      // ordering

  const char* name() const;                // name of type
};

More detailed information can be supplied and accessed as described below. However, because of the great diversity of the “more detailed information” desired by different people and because of the desire for minimal space overhead by others, the services offered by type_info are deliberately minimal.

The before() function is intended to allow type_infos to be sorted so that they can be accessed through hash tables, etc. There is no relation between the relationships defined by before and inheritance relationships (see §14.2.8.3). Further, there is no guarantee that before() yields the same results in different programs or different runs of the same programs. In this, before() resembles the address-of operator.

14.2.5.2 Extended Type Information

Sometimes knowing the exact type of an object is simply the first step to acquiring and using more detailed information about that type.

Consider how an implementation or a tool could make information about types available to users at run time. Say we have a tool that generates a table of My_type_info objects. The preferred way of presenting this to the user is to provide an associative array (map, dictionary) of typenames and such tables. To get such a member table for a type, a user would write:

#include <type_info.h>

extern Map<My_type_info, const char*> my_type_table;

void f(B* p)
{
    My_type_info& mi = my_type_table[typeid(*p).name()];
    // use mi
}

Someone else might prefer to index tables directly with typeids rather than requiring the user to use the name() string:

extern Map<Your_type_info,type_info*> your_type_table;

void g(B* p)
{
    Your_type_info& yi = your_type_table[&typeid(*p)];
    // use yi
}

This way of associating typeids with information allows several people or tools to associate different information to types without interfering with each other:

Image

This is most important because the likelihood that someone can come up with a set of information that satisfies all users is zero. In particular, any set of information that would satisfy most users would be so large that it would be unacceptable overhead for users who need only minimal run-time type information.

An implementation may choose to provide additional implementation-specific type information. Such system-provided extended type information could be accessed through an associative array exactly as user-provided extended type information is. Alternatively, the extended type information could be presented as a class Extended_type_info derived from class type_info:

Image

A dynamic_cast can then be used to determine if a particular kind of extended type information is available:

#include <type_info.h>

typedef Extended_type_info Eti;

void f(Sometype* p)
{
    if (Eti* p = dynamic_cast<Eti*>(&typeid(*p))) {
        // ...
    }
}

What “extended” information might a tool or an implementation make available to a user? Basically any information that a compiler can provide and that some program might want to take advantage of at run time. For example:

– Object layouts for object I/O and/or debugging.

– Pointers to functions creating and copying objects.

– Tables of functions together with their symbolic names for calls from interpreter code.

– Lists of all objects of a given type.

– References to source code for the member function.

– Online documentation for the class.

The reason such things are supported through libraries, possibly standard libraries, is that there are too many needs, too many potentially implementation-specific details, and too much information to support every use in the language itself. Also, some of these uses subvert the static checking provided by the language. Others impose costs in run time and space that I do not feel appropriate for a language feature.

14.2.6 Object Layout Model

Here is a plausible memory layout for an object with a virtual function table and type information object:

Image

The dashed arrow represents an offset that allows the start of the complete object to be found given only a pointer to a polymorphic sub-object. It is equivalent to the offset (delta) used in the implementation of virtual functions (§12.4).

For each type with virtual functions, an object of type type_info is generated. These objects need not be unique. However, a good implementation will generate unique type_info objects wherever possible and only generate type_info objects for types where some form of run-time type information is actually used. An easy implementation simply places the type_info object for a class right next to its vtbl.

Cfront-based implementations and implementations that borrowed Cfront’s virtual table layout can be updated to support RTTI without even requiring recompilation of old code. The reason is that I considered providing RTTI at the time I implemented Release 2.0 and left two empty words at the start of each vtbl to allow such an extension. At the time, I didn’t add RTTI because I wasn’t certain that it was needed or – assuming that it was needed – exactly how the facility should be presented to users. As an experiment, I implemented a simple version in which every object of a class with virtual functions could be made to print out its name. Having done that, I was satisfied that I knew how to add RTTI should it ever become necessary – and removed the feature.

14.2.7 An Example: Simple Object I/O

Let me present a sketch of how a user might use RTTI together with a simple object I/O system and outline how such an object I/O system might be implemented. Users want to read objects from a stream, determine that they are of the expected types, and then use them. For example:

void user()
{
    // open file assumed to hold shapes, and
    // attach ss as an istream for that file
    // ...

    io_obj* p = get_obj(ss); // read object from stream

    if (Shape* sp = dynamic_cast<Shape*>(p)) {
        sp->draw(); // use the Shape
        // ...
    }
    else {
        // oops: non-shape in Shape file
    }
}

The function user() deals with shapes exclusively through the abstract class Shape and can therefore use every kind of shape. The use of dynamic_cast is essential because the object I/O system can deal with many other kinds of objects and the user may accidentally have opened a file containing perfectly good objects of classes that this user has never heard of.

This object I/O system assumes that every object read or written is of a class derived from io_obj. Class io_obj must be a polymorphic type to allow us to use dynamic_cast. For example:

class io_obj { // polymorphic
    virtual io_obj* clone();
};

The critical function in the object I/O system is get_obj() that reads data from an istream and creates class objects based on that data. Let me assume that the data representing an object on an input stream is prefixed by a string identifying the object’s class. The job of get_obj() is to read that string prefix and call a function capable of creating and initializing an object of the right class. For example:

typedef io_obj* (*PF)(istream&);

Map<String,PF> io_map; // maps strings to creation functions

io_obj* get_obj(istream& s)
{
    String str;
    if (get_word(s, str) == 0) // read initial word into str
        throw no_class;

PF f = io_map[str]; // lookup 'str' to get function
if (f == 0) throw unknown_class; // no match for vstr'
io_obj* p = f(s); // construct object from stream
if (debug) cout << typeid(*p).name() << ' ';
}

The Map called io_map is an associative array that holds pairs of name strings and functions that can construct objects of the class with that name. The Map type is one of the most useful and efficient data structures in any language. One of the first widely used implementations of the idea in C++ was written by Andrew Koenig [Koenig, 1988]; see also [2nd,§8.8].

Note the use of typeid() for debugging purposes. In this particular design, that is the only use of RTTI in the implementation.

We could, of course, define class Shape the usual way except deriving it from io_obj as required by user ():

class Shape : public io_obj {
    // ...
};

However, it would be more interesting (and in many cases more realistic) to use some previously defined Shape class hierarchy unchanged:

class iocircle : public Circle, public io_obj {
public:
    iocircle* clone() // override io_obj::clone()
        { return new iocircle(*this); }

    iocircle(istream&); // initialize from input stream

    static iocircle* new_circle(istream& s)
    {
        return new iocircle(s);
    }
    // ...
} ;

The iocircle (istream&) constructor initializes an object with data from its istream argument. The new_circle function is the one put into the io_map to make the class known to the object I/O system. For example:

io_map["iocircle"]=&iocircle::new_circle;

Other shapes are constructed in the same way:

class iotriangle : public Triangle, public io_obj {
    // ...
};

If the provision of the object I/O scaffolding becomes tedious, a template might be used:

template<class T>
class io : public T, public io_obj {
public:
    io* clone() // override io_obj::clone()
        { return new io(*this); }

    io(istream&); // initialize from input stream

    static io* new_io(istream& s)
    {
        return new io(s);
    }
    // ...
};

Given this, we could define iocircle like this:

typedef io<Circle> iocircle;

We would still have to define io<Circle>::io(istream&) explicitly, though, because it needs to know about the details of Circle.

This simple object I/O system may not do everything anyone ever wanted, but it almost fits on a single page, and the key mechanisms have many uses. In general, these techniques can be used to invoke a function based on a string supplied by a user.

14.2.8 Alternatives Considered

The RTTI mechanism provided is an “onion design.” As you peel off the layers of the mechanism you find more powerful facilities – which if badly used might make you cry.

The basic notion of the RTTI mechanisms described here is that for maximal ease of programming and to minimize implementation dependencies, we should minimize the use of RTTI:

[1] Preferably, we should use no run-time type information at all and rely exclusively on static (compile-time) checking.

[2] If that is not possible, we should use only dynamic casts. In that case, we don’t even have to know the exact name of the object’s type and don’t need to include any header files related to RTTI.

[3] If we must, we can compare typeid() s, but to do that, we need to know the exact name of at least some of the types involved. It is assumed that “ordinary users” will never need to examine run-time type information further.

[4] Finally, if we absolutely do need more information about a type – say, because we are trying to implement a debugger, a database system, or some other form of object I/O system – we can use operations on typeids to obtain more detailed information.

This approach of providing a series of facilities of increasing involvement with runtime properties of classes contrasts to the approach of providing a class giving a single standard view of the run-time type properties of classes – a meta-class. The C++ approach encourages greater reliance on the (safer and more efficient) static type system, has a smaller minimal cost (in time and comprehensibility) to users, and is also more general because of the possibility of providing multiple views of a class by providing more detailed type information.

Several alternatives to this “onion approach” were considered.

14.2.8.1 Meta-Objects

The onion approach differs from the approaches taken by Smalltalk and CLOS. Such systems have also repeatedly been proposed for C++. In such systems, type_info is replaced by a “meta-object” capable of accepting – at run time – requests to perform any operation that can be requested of an object in the language. In essence, having a meta-object mechanism embeds an interpreter for the complete language in the run-time environment. I saw that as a danger to the basic efficiency of the language, a potential way of subverting the protection mechanisms, and at odds with the basic notions of design and documentation relating to static type checking.

These objections to basing C++ on a notion of meta-objects doesn’t mean that meta-objects can’t be useful. They most certainly can be, and the notion of extended type information opens the door for people who really need those techniques to supply them through libraries. I did, however, reject the idea of burdening every C++ user with those mechanisms, and I cannot recommend those design and implementation techniques for general C++ programming; see [2nd,§12] for details.

14.2.8.2 The Type-inquiry Operator

To many people, an operator that answers the question “is *pb of class D or a class derived from D?” seems more natural than dynamic_cast that performs a cast if and only if the answer to that question is affirmative. That is, they want to write code like this:

void my_fct(dialog_box* bbp)
{

    if (dbp->isKindOf(dbox_w_str)) {

        dbox_w_str* dbsp = (dbox_w_str*)dbp;

        // use dbsp
    }
    else {

        // treat *dbp as a ''plain'' dialog box
    }
}

There are several problems with this. The most serious is that the cast will, in some cases, not give the desired result (see §14.3.2.1). This is an example of how difficult it can be to import notions from a different language. Smalltalk provides isKindOf for type inquiry, but Smalltalk doesn’t need the subsequent cast and therefore cannot suffer any problems relating to it. However, importing the isKindOf idea into C++ would cause both technical and stylistic problems.

In fact, stylistic arguments had settled the issue for me in favor of some form of conditional cast before I discovered those technical “killer” arguments against type-inquiry operators along the line of isKindOf. Separating the test and the type conversion causes verbosity and makes it possible to mismatch the test and the cast.

14.2.8.3 Type Relations

Several people suggested defining <, <=, etc., on type_info objects to express relationships in a class hierarchy. That is easy, but too cute. It also suffers from problems with an explicit type comparison operation as described in §14.2.8.2. We need a cast in any event so we can just as well use a dynamic cast.

14.2.8.4 Multi-methods

A more promising use of RTTI would be to support “multi-methods,” that is, the ability to select a virtual function based on more than one object. Such a language facility would be a boon to writers of code that deals with binary operations on diverse objects; see §13.8. It appears that the type_info objects could easily hold the information necessary to accomplish this. This makes multi-methods more of a possible further extension than an alternative to the approach we adopted.

I made no such proposal, however, because I could not clearly grasp the implications of such a change and did not want to propose a major new extension without experience in the context of C++.

14.2.8.5 Unconstrained Methods

Given RTTI, one can support “unconstrained methods;” that is, one could hold enough information in the type_info object for a class to check at run time whether a given function was supported or not. Thus, one could support Smalltalk-style run-time-checked function calls. However, I felt no need for that and considered that extension as contrary to my effort to encourage efficient and type-safe programming. The dynamic cast enables a check-and-call strategy:

if (D* pd = dynamic_cast<D*>(pb)) { // is *pb a D?
    pd->dfct(); // call D function
    // ...
}

rather than the call-and-have-the-call-check strategy of Smalltalk:

pb->dfct(); // hope pb points to something that
            // has a dfct; handle failed calls
            // somewhere (else)

The check-and-call strategy provides more static checking (we know at compile time that dfct is defined for class D), doesn’t impose an overhead on the vast majority of calls that don’t need the check, and provides a visible clue that something beyond the ordinary is going on.

14.2.8.6 Checked Initialization

We also considered checking assignments and/or initialization in a way similar to what is done in languages such as Beta and Eiffel. For example:

void f(B* pb)
{
    D* pd1 = pb; // error: type mismatch
    D* pd2 ?= pb; // ok, check if is *pb a D at run time

    pd1 = pb; // error: type mismatch
    pd2 ?= pb; // ok, check if is *pb a D at run time
}

However, I thought the ? was too hard to spot in real code and too error-prone because it wouldn’t be combined with a test. Also, it sometimes requires the introduction of an otherwise unnecessary named variable. The alternative, allowing ? = only in conditions seemed very attractive:

void f(B* pb)
{
    D* pd1 ?= pb;       // error: unchecked
                        // conditional initialization

    if (D* pd2 ?= pb) { // ok: checked
                        // conditional initialization
        // ...
    }
}

However, you would have to distinguish between cases where exceptions are thrown in the case of failure and cases where 0 is returned. Also, a ? = operator would lack the odium attached to ugly casts and would therefore encourage misuse.

By allowing declarations in conditions (§3.11.5.2), I made it possible to use dynamic_cast in the style suggested by this alternative:

void f(B* pb)
{
    if (D* pd2 = dynamic_cast<D*>(pb)) { // ok: checked
        // ...
    }
}

14.3 A New Cast Notation

Syntactically and semantically, casts are one of the ugliest features of C and C++. This has led to a continuous search for alternatives to casts: Function declarations enabling implicit conversion of arguments (§2.6), templates (§14.2.3), and the relaxation of the overriding rules for virtual functions (§13.7) each remove the need for some casts. The dynamic_cast operator (§14.2.2), on the other hand, provides a safer alternative to old-style casts for a specific usage. This led to a complementary approach to try to factor out the logically separate uses of casting and support them with operators similar to dynamic_cast:

static_cast<T>(e)      // reasonably well-behaved casts.
reinterpret_cast<T>(e) // casts yielding values that must
                       // be cast back to be used safely.
const_cast<T>(e)       // casting away const.

This section can be read as an analysis of the problems with old-style casts or as a synthesis of a new feature. It is both. The definition of these operators owes much to the efforts of the extensions working group where strong opinions both for and against have been heard. Dag Brück, Jerry Schwarz, and Andrew Koenig have made particularly constructive contributions. These new cast operators were accepted at the San Jose meeting in November of 1993.

To conserve space the discussion is almost completely restricted to the most difficult case: pointers. The treatment of arithmetic types, pointers to members, references, etc., is left as an exercise for the reader.

14.3.1 The Problem

The C and C++ cast is a sledgehammer: (T) expr will – with very few exceptions – yield a value of type T based in some way on the value of expr. Maybe a simple reinterpretation of the bits of expr is involved, maybe an arithmetic narrowing or widening is involved, maybe some address arithmetic is done to navigate a class hierarchy, maybe the result is implementation dependent, maybe a const or volatile attribute is removed, etc. It is not possible for a reader to determine what the writer intended from an isolated cast expression. For example:

const X* pc = new X;
// ...
pv = (Y*) pc;

Did the programmer intend to obtain a pointer to a type unrelated to X? Cast away the const attribute? Both? Was the intent to gain access to a base class Y of X? The possibilities for confusion are endless.

Further, an apparently innocent change to a declaration can quietly change the meaning of an expression dramatically. For example:

class X : public A, public B { /* ... */ };

void f(X* px)
{
    ((B*)px)->g();   // call B's g
    px->B::g();      // a more explicit, better, way
}

Change the definition of X so that B no longer is a base class and the meaning of (B*) px changes completely without giving the compiler any chance to diagnose a problem.

Apart from the semantic problems with old-style casts, the notation is unfortunate. The notation is close to minimal and uses only parentheses – the most overused syntactic construct in C. Consequently, casts are hard for humans to spot in a program and also hard to search for using simple tools such as grep. The cast syntax is also a major source of C++ parser complexity.

To sum up, old-style casts:

[1] Are a problem for understanding: they provide a single notation for several weakly related operations.

[2] Are error-prone: almost every type combination has some legal interpretation.

[3] Are hard to spot in code and hard to search for with simple tools.

[4] Complicate the C and C++ grammar.

The new cast operators represent a classification of the old cast’s functionality. To have a chance of wide acceptance in the user population, they have to be able to perform any operation that the old casts can do. Otherwise, a reason for future use of old-style casts would have been provided. I have found only one exception: an old-style cast can cast from a derived class to its private base class. There is no reason for this operation; it is dangerous and useless. There is no mechanism for granting oneself access to the complete private representation of an object – and none is needed. The fact that an old-style cast can be used to gain access to the part of a representation that is a private base is an unfortunate historical accident. For example:

class D : public A, private B {
private:
    int m;
    // ...
};

void f(D* pd) // f() is not a member or a friend of D
{
    B* pb1 = (B*)pd;              // gain access to D's
                                  // private base B.
                                  // Yuck!
    B* pb2 = static_cast<B*>(pd); // error: can't access
                                  // private. Fine!
}

Except by manipulating pd as a pointer to raw memory, there is no way for f() to get to D::m. Thus, the new cast operators close a loophole in the access rules and provide a greater degree of consistency.

The long names and the template-like syntax of the new casts put off some people. That may be all for the better because one of the purposes of the new casts is to remind people that casting is a hazardous business and to emphasize that there are different kinds of danger involved in the use of the different operators. In my experience, the strongest dislike is expressed by people who use C++ mostly as a dialect of C and think they need to cast frequently. Also, people who haven’t yet made serious use of templates find the notation odd. The dislike for the template-like notation wears off as people gain experience with templates.

14.3.2 The static_cast Operator

The static_cast<T> (e) notation is meant to replace (T) e for conversions such as Base* to Derived*. Such conversions are not always safe but frequent and well-defined even in the absence of a run-time check. For example:

class B { /* ... */ };

class D : public B { /* ... */ };

void f(B* pb, D* pd)
{
    D* pd2 = static_cast<D*>(pb); // what we used
                                  // to call (D*)pb.
    B* pb2 = static_cast<B*>(pd); // safe conversion
    // ...
}

One way of thinking of static_cast is as the explicit inverse operation to the implicit conversions. Except that static_cast respects constness, it can do S->T provided T->S can be done implicitly. This implies that in most cases the result of static_cast can be used without further casting. In this, it differs from reinterpret_cast14.3.3).

In addition, conversions that may be performed implicitly – such as standard conversions and user-defined conversions – are invoked by static_cast.

In contrast to dynainic_cast, no run-time check is required for the static_cast conversion of pb. The object pointed to by pb might not point to a D in which case uses of *pd2 are undefined and probably disastrous.

In contrast to the old-style cast, pointer and reference types must be complete; that is, trying to use static_cast to convert to or from a pointer to a type for which the declaration hasn’t been seen is an error. For example:

class X;  // X is an incomplete type
class Y;  // Y is an incomplete type

void f(X* px)
{
    Y* p = (Y*)px;  // allowed, dangerous
    p = static_cast<Y*>(px);  // error:
                              // X and Y undefined
}

This eliminates yet another source of errors. If you need to cast incomplete types, use reinterprete_cast14.3.3) to make it clear that you are not trying to do hierarchy navigation, or use dynamic_cast14.2.2).

14.3.2.1 Static Casts and Dynamic Casts

The effect of both dynamic_cast and static_cast on pointers to classes is navigation in a class hierarchy. However, static_cast relies exclusively on static information (and can therefore be fooled). Consider:

class B { /* ... */ };

class D : public B { /* ... */ };

void f(B* pb)
{
    D* pd1 = dynamic_cast<D*>(pb);
    D* pd2 = static_cast<D*>(pb);
}

If pb really points to a D, then pd1 and pd2 get the same value. So do they if pb= = 0. However, if pb points to a B (only) then dynamic_cast will know enough to return 0, whereas static_cast must rely on the programmer’s assertion that pb points to a D and returns a pointer to the supposed D object. Worse, consider:

class D1 : public D { /* ... */ };
class D2 : public B { /* ... */ };
class X : public D1, public D2 { /* ... */ };

void g()
{
    D2* pd2 = new X;
    f(Pd2);
}

Here, g() will call f() with a B that is not a sub-object of a D. Consequently, dynamic_cast will correctly find the sibling sub-object of type D, whereas static_cast will return a pointer to some inappropriate sub-object of the X. I think it was Martin O’Riordan who first brought this phenomenon to my attention.

14.3.3 The reinterpret_cast Operator

The reinterpret_cast<T> (e) notation is meant to replace (T) e for conversions, such as char* to int* and Some_class* to Unrelated_class*, that are inherently unsafe and often implementation dependent. Basically, reinterpret_cast returns a value that is a crude reinterpretation of its argument. For example:

class S;
class T;

void f(int* pi, char* pc, S* ps, T* pt, int i)
{
    S* ps2 = reinterpret_cast<S*>(pi);
    S* ps3 = reinterpret_cast<S*>(pt);
    char* pc2 = reinterpret_cast<char*> (pt);
    int* pi2 = reinterpret_cast<int*>(pc);
    int i2 = reinterpret_cast<int>(pc);
    int* pi3 = reinterpret_cast<int*>(i);
}

The reinterpret_cast operator allows any pointer to be converted into any other pointer type and also any integral type to be converted into any pointer type and vice versa. Essentially, all of these conversions are unsafe, implementation dependent, or both. Unless the desired conversion is inherently low-level and unsafe, the programmer should use one of the other casts.

Unlike static_cast, the result of a reinterpret_cast can’t safely be used for anything except being cast back to its original type. Other uses are at best non-portable. This is why pointer-to-function and pointer-to-member conversions are reinterpret_casts rather than static_casts. For example:

void thump(char* p) { *p = 'x'; }

typedef void (*PF)(const char*);
PF pf;

void g(const char* pc)
{

    thump(pc); // error: bad argument type

    pf = &thump; // error

    pf = static_cast<PF>(&thump); // error!

    pf = reinterpret_cast<PF>(&thump); // ok: on your
                                       // head be it
pf(pc); // not guaranteed to work!
}

Clearly, getting pf to point to thump is dangerous because doing so fools the type system and allows the address of a const to be passed to something that tries to modify it. That is why we must use a cast and in particular why the “nasty” reinterpret_cast must be used. It comes as a surprise to many, though, that a call through pf to thump is still not guaranteed to work (in C++ exactly as in C). The reason is that an implementation is allowed to use different calling sequences for different function types. In particular, there are good reasons why an implementation would use different calling sequences for const and non-const arguments.

Note that reinterprete_cast does not do class hierarchy navigation. For example:

class A { /* ... */ };
class B { /* ... */ };
class D : public A, public B { /* ... */ };

void f(B* pb)
{
    D* pd1 = reinterpret_cast<D*>(pb);
    D* pd2 = static_cast<D*>(pb);
}

Here, pd1 and pd2 will typically get different values. In a call

f(new D);

pd2 will point to the start of the D object passed, whereas pd1 will point to the start of D’s B sub-object.

The reinterpret_cast<T> (arg) operation is almost as bad as (T)arg. However, reinterpret_cast is more visible, never performs class hierarchy navigation, does not cast away const, and the other casts provide alternatives; reinterpret_cast is an operation for performing low-level and usually implementation-dependent conversions – only.

14.3.4 The const_cast Operator

The thorniest issue in finding a replacement for old-style casts was finding an acceptable treatment of const. The ideal is to ensure that “constness” is never quietly removed. For this reason reinterpret_cast, dynamic_cast, and static_cast were conceived as respecting constness; that is, they can’t be used to “cast away const.”

The const_cast<T> (e) notation is meant to replace (T)e for conversions used to gain access to data specified const or volatile. For example:

extern "C" char* strchr(char*, char);

inline const char* strchr(const char* p, char c)
{
    return strchr(const_cast<char*>(p), char c);
}

In const_cast<T> (e), the type argument T must be identical to the type of the argument e except for const and volatile modifiers. The result is identical to e except that its type is T.

Note that the result of casting away const from an object originally defined const is undefined (§13.3).

14.3.4.1 Problems with const Protection

There are, unfortunately, subtleties in the type system that open loopholes in the protection against implicit violation of “constness.” Consider:

const char cc = 'a';
const char* pcc = &cc;
const char** ppcc = &pcc;
void* pv = ppcc; // no cast needed:
                 // ppcc isn't a const, it only points to one,
                 // but const vanished!
char** ppc = (char**)pv; // points to pcc

void f()
{
    **ppc = 'x' ; // Zap!
}

However, having void* unsafe can be considered acceptable because everybody knows – or at least ought to know – that casts from void* are inherently tricky.

Such examples become interesting when you start building classes that can contain a variety of pointer types (for example, to minimize generated code, see §15.5).

Unions and the use of the ellipsis to suppress type checking of function arguments provide other loopholes in the protection against implicit violation of “constness.” However, I prefer a system that leaves a few loopholes to one that provides no protection at all. As with void*, programmers should know that unions and unchecked function arguments are inherently dangerous, should be avoided wherever possible, and should be handled with special care when actually needed.

14.3.5 Impact of the New-style Casts

The new-style casts are part of a continuing effort to eliminate holes in the C++ type system. The aim is to minimize and localize unsafe and error-prone programming practices. Here, I discuss how to cope with related problem areas (old-style casts, implicit narrowing conversions, and function style conversions) and how to convert existing code to use the new cast operators.

14.3.5.1 Old-style Casts

I intended the new-style casts as a complete replacement for the (T) e notation. I proposed to deprecate (T) e; that is, for the committee to give users warning that the (T) e notation would most likely not be part of a future revision of the C++ standard. I saw a direct parallel between this and the introduction of C++-style function prototypes in the ANSI/ISO C standard together with the deprecation of unchecked calls. However, that idea didn’t gain a majority, so that cleanup of C++ will probably never happen.

What is more important, though, is that the new cast operators provide individual programmers and organizations with an opportunity to avoid the insecurities of old-style casts when type safety is more important than backward compatibility with C. The new casts can further be supported by compiler warnings for the use of old-style casts.

The new cast operators provide an evolution path to a safer, yet no less efficient, style of programming. This is likely to increase in importance over the years as the general quality of code improves and tools assuming type safety come into widespread use – in C++ and in other languages.

14.3.5.2 Implicit Narrowing Conversions

The idea of minimizing violations of the static type system and making such violations as obvious as possible is fundamental to the work on new casts. Naturally, I also reconsidered the possibility of eliminating implicit narrowing conversions such as long to int and double to char2.6.1). Unfortunately, a general ban is not just infeasible, it would also be counterproductive. The main problem is that arithmetic can overflow:

void f(char c, short s, int i)
{
    C++; // result might not fit in a char
    s++; // result might not fit in a short
    i++; // might overflow
}

If we prohibited implicit narrowing, c++ and s++ would become illegal because chars and shorts are promoted to ints before arithmetic operations are performed. Requiring explicit casts for narrowing conversions would require a rewrite:

void f(char c, short s, int i)
{
    c = static_cast<char> (c + 1);
    s = static_cast<short> (s + 1);
    i + +;
}

I don’t see any hope for imposing such a notational burden without a corresponding major benefit. And where is the benefit? Littering the code with explicit casts will not improve code clarity and won’t even reduce the number of errors because people would add the casts thoughtlessly. The i++ isn’t safe, either, because of the possibility of overflow. Adding the casts might even be counterproductive because an implementation might by default catch overflow at run time, and the explicit use of a cast would suppress such a check. A better way would be to define dynamic_cast to perform a run-time check on the value of a numeric operand. That way users who consider checking important could then use dynamic_cast where their experience tells them that the check is actually worthwhile. Alternatively, people can just write a function that checks and use that. For example (§15.6.2):

template<class V, class U> V narrow(U u)
{
    V v = u;
    if (v!=u) throw bad_narrowing;
    return v;
}

Even though a ban on narrowing conversions is infeasible and would require a thorough overhaul of the rules governing arithmetic operations, there are still quite a few conversions that an implementation could warn against with a good degree of confidence: floating type to integral type, long to short, long to int, and long to char. Cfront always did that. In my experience, other potentially narrowing conversions such as int to float and int to char are harmless too often for warnings to be accepted by users.

14.3.5.3 The Constructor Call Notation

C++ supports the constructor notation T (v) as a synonym for the old-style cast notation (T) v. A better solution would be to redefine T (v) as a synonym for valid object construction as in initializations such as

T val (v) ;

(that we don’t have a good name for). Such a change would require a transition because – like the suggested deprecation of (T) v – this breaks existing code. Like the deprecation of the (T) e notation, this failed to gain acceptance in the committee. Fortunately, people who want to use an explicit form of otherwise implicit conversions, say for disambiguation, can write a class template to do so (§15.6.2).

14.3.5.4 Using the New Casts

Can the new casts be used without understanding the subtleties presented here? Can code using old-style casts be converted to use the new-style casts without programmers getting bogged down in language law? For the new casts to become widely preferred over old-style casts, the answer to both questions must be yes.

A simple conversion strategy is to use static_cast in all cases and see what the compiler says. If the compiler doesn’t like static_cast in some case, then that case is worth examining. If the problem is a const violation, look to see if the result of the cast does lead to an actual violation; if not, const_cast should be used. If the problem is incomplete types, pointer to functions, or casting between unrelated pointer types, try to determine that the resulting pointer is actually cast back again before use. If the problem is something like a pointer to int conversion one ought to think harder about what should be going on. If such a cast can’t be eliminated, reinterpret_cast will do exactly what an old-style cast would do in such cases.

In most cases this analysis and its resulting elimination of old-style casts can be done by a not-too-complicated program. In all cases, it would be better if the cast – new or old – could be eliminated.

The standards committee is still discussing naming conventions for standard library classes. I have picked the names I consider the most likely outcome of these discussions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset