<<

Draft for OOPSLA 2012

Open and Efficient Type Switch for ++

Yuriy Solodkyy Gabriel Dos Reis Bjarne Stroustrup Texas A&M University Texas, USA {yuriys,gdr,bs}@cse.tamu.edu

Abstract – allow for independent extensions, modular type-checking Selecting operations based on the run-time type of an object and dynamic linking. On the other, in order to be accepted is key to many object-oriented and for production code, the implementation of such a construct techniques. We present a technique for implementing open must equal or outperform all known workarounds. However, and efficient type-switching for hierarchical extensible data existing approaches to case analysis on hierarchical exten- types. The technique is general and copes well with C++ sible data types are either efficient or open, but not both. multiple inheritance. Truly open approaches rely on expensive class-membership To simplify experimentation and gain realistic prefor- testing combined with decision trees []. Efficient approaches mance using production-quality compilers and tool chains, rely on sealing either the class hierarchy or the set of func- we implement our type swich constructs as an ISO C++11 li- tions, which loses extensibility [9, 18, 44, 51]. Consider a brary. Our -only implementation provides concise no- simple expression language: tation and outperforms the visitor design pattern, commonly exp ∶∶= val S exp + exp S exp − exp S exp ∗ exp S exp~exp used for type-casing scenarios in object-oriented programs. For many uses, it equals or outperforms equivalent code in In an object-oriented language without direct support for languages with built-in type-switching constructs, such as algebraic data types, the type representing an expression-tree OCaml and Haskell. The type-switching code is easier to in the language will typically be encoded as an abstract base use and is more expressive than hand-coded visitors. The li- class, listing the (sealed set of) allowed virtual functions, brary is non-intrusive and circumvents most of extensibility with derived classes representing variants: restrictions typical of visitor design pattern. It was motivated struct Expr { virtual int eval () = 0; }; by applications involving large, typed, abstract syntax trees. struct Value : Expr { ⋯ int eval (); int value; }; Categories and Subject Descriptors D[1]: 5; D [3]: 3 struct Plus : Expr { ⋯ Expr& e1; Expr& e2; }; General Terms Languages, Design A simple evaluator for this language can be implemented with the aid of a virtual function eval() declared in the base Keywords Type Switch, Typecase, Visitor Design Pattern, class Expr. The approach is intrusive however, as we will Memoization, C++ have to modify the base class every time we would like to add a function. Instead we offer an external introspection of 1. Introduction objects with case analysis: Algebraic data types as seen in functional languages are int eval (const Expr& e) closed and their variants are disjoint, which allows for ef- { ficient implementation of case analysis on such types. Data Match(e) types in object-oriented languages are extensible and hierar- Case(const Value& x) return x.value; chical (implying that variants are not necessarily disjoint). Case(const Plus& x) return eval (x. e1) + eval(x. e2); To be general, an type-switching construct must be open Case(const Minus& x) return eval(x.e1) − eval (x. e2); Case(const Times& x) return eval(x.e1) * eval (x. e2); Case(const Divide& x) return eval(x. e1) / eval (x. e2); EndMatch } The syntax is provided without any external tool support or additional definitions. Instead we rely on a few C++11 fea- [Copyright notice will appear here once ’preprint’ option is removed.] tures [24], meta-programming, and macros. It runs

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 1 2012/4/13 about as fast as OCaml and Haskell equivalents (§4.3), and, This is the first technique for efficiently handling type depending on the usage scenario, compiler and underlying switching in the presence of general multiple inheritance. hardware, comes close or outperforms the handcrafted C++ Being a library, our solution is that it can be used with any code based on the visitor design pattern (§4). ISO C++11 compiler (e.g., Microsoft, GNU, or ) with- The ideas and the library presented here were motivated out requiring the installation of any additional tools or pre- by our unsatisfactory experiences working with various C++ processors. The solution sets a new threshold for acceptable front-ends and program analysis frameworks [1, 32, 39]. The performance, brevity, clarity and usefulness of open type- problem was not in the frameworks per se, but in the fact that switching in C++. we had to use the visitor design pattern [18] to inspect, tra- verse, and elaborate abstract syntax trees to target languages. 2. Overview We found visitors unsuitable to express our application logic directly, surprisingly hard to teach students, and often slower A heterarchy is a partially ordered set (H, <∶) where H than hand-crafted workaround techniques. We found users is a set of classes and <∶ is reflexive, transitive and anti- relying on dynamic casts in many places, often nested, to an- symmetric subtyping relation on H. We are going to use swer simple structural questions. That is, the users preferred terms class and type interchangibly as the exact distinction shorter, cleaner, and more direct code to visitors. The conse- is not important for this discussion. Given two types in a quential high performance cost was usually not discovered subtyping relation D <∶ B, the type D is said to be a subtype until later, when it was hard to remedy. or a derived class of B, which in turn is said to be a supertype or a base class of D. When the transitive reduction <∶d of <∶ 1.1 Summary is a function, H is usually referred to as hierarchy to indicate This paper makes the following contributions: single inheritance. Subtyping effectively makes objects belong to multiple • A technique for implementing open and efficient type types. We call by the most-derived type the type used to switching on extensible hierarchical data types as seen create an object (before any conversions). By static type we in object-oriented languages. call the type of an object as known to the compiler based • The technique approaches the notational convenience of on its declaration as well as any supertype of it. By dynamic functional-language type-switch constructs and delivers type of an object we call any base class of the most-derived equivalent performance to those for closed cases. type. • The technique is simpler to use and outperforms the vis- itor pattern. 2.1 Type Switch • A type-switch approach that combines subtype tests and In general, type switch or typecase is a multiway branch type conversion; It delivers superior performance to ap- statement that distinguishes values based on their type. In a proaches that combine (even constant-time) subtype tests multi-paradigm like C++ that sup- with decision trees for even small class hierarchies. ports in various forms parametric, ad-hoc and subtyping • The type-switch construct handles multiple inheritance polymorphisms, such a broad definition subsumes numer- without workarounds. ous different type casing constructs studied in the litera- • A constant-time function whose equivalence kernel par- ture [21, 23, 45]. In this work we only look at type casing titions the set of objects with the same static type into scenarios based on dynamic polymorphism of C++ (nomi- equivalence classes based on inheritance path of the static native subtyping polymorphism based on inheritance), sim- type with the most-derived type. ilar to those studied by Glew [21]. It is possible to gener- In particular, our technique for efficient type switching: alize type casing (the notion, not our implementation) to static polymorphism of C++ (ad-hoc and parametric poly- • Comes close and often outperform various workaround morphisms enabled by overloading and templates) along the techniques used in practice, e.g. visitor design pattern, line of work introduced by Harper and Morrisett [23] and without sacrificing extensibility (§4). studied in the context of closed and extensible solutions by • Is open by construction (§3.2), non-intrusive, and avoids Vytiniotis et al [45], but we do not address such a generaliza- the control inversion typical for visitors. tion here. We use the term type switch instead of a broader • Works in the presence of multiple inheritance, both re- typecase to stress the run-time nature of the type analysis peated and virtual, as well as in generic code (§3.7). similar to how regular switch-statement of C++ performs • Does not require any changes to the C++ object model or case analysis of values at run time. computations at link or load time. Given an object descriptor, called subject, of static type • Can be used in object-oriented languages with object S (pointer or reference) referred to as subject type, and a models similar to C++’s. list of target types Ti associated with the branches, a type • For C++, can be implemented as a library written in ISO switch statement needs to identify a suitable clause m (or Standard C++11. absence of such) based on the most-derived type D <∶ S of

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 2 2012/4/13 the subject as well as suitable conversion that csts the subject 2.2 Expression Problem to the target type Tm. Due to multiple inheritance, types Type switching is related to a more general problem mani- Ti in general may or may not be derived from S, however, festing the differences in functional and object-oriented pro- because of the strong static type safety requirement, the type gramming styles. of applicable clause Tm will necessarily have to be one Conventional algebraic datatypes, as found in most func- of subject’s dynamic types: D <∶ Tm. A hypothetical type tional languages, allow for easy addition of new functions switch statement, not currently supported by C++, may look on existing data types. But they fall short in extending as following: data types themselves (e.g. with new constructors), which requires modifying the source code. Object-oriented lan- switch (subject) { case T1: s1; ... case Tn: sn; } guages, on the other hand, make data type extension trivial There is no need for an explicit default clause in our setting through inheritance; but the addition of new functions op- because such a clause is semantically equivalent to a case erating on these classes typically requires changes to the clause guarded by the subject type: case S: s. The only class definition. This dilemma is known as the expression semantic difference such a choice makes is in the treatment problem [10, 46]. of null-pointers, which, one may argue, should be handled by Classes differ from algebraic data types in two important the default clause. We disagree, because not distinguishing ways. Firstly, they are extensible, for new variants can be between invalid object and valid object of a known static added later by inheriting from the base class. Secondly, they but unknown dynamic type may lead to some nasty run-time are hierarchical and thus typically non-disjoint since vari- errors. ants can be inherited from other variants and form a subtyp- Similar control structures exist in many programming ing relation between themselves [21]. In contrast, variants in languages, e.g. match in Scala [35], case in Haskell [25] algebraic data types are disjoint and closed. Some functional and ML [33], typecase in Modula-3 [6] and CLOS [? ] languages e.g. ML2000 [2] and its predecessor, Moby, were (as a macro), tagcase in CLU [30], union case in Algol 68 experimenting with hierarchical extensible sum types, which and date back to at least Simula’s Inspect statement [11]. are closer to object-oriented classes then algebraic data types The statement in general can be given numerous plausible are, but, interestingly, they provided neither traditional nor semantics: efficient facilities for performing case analysis on them. Zenger and Odersky later refined the expression problem • First-fit semantics will evaluate the first statement si such in the context of independently extensible solutions [50] as a that Ti is a base class of D challenge to find an implementation technique that satisfies • Best-fit semantics will evaluate the statement correspond- the following requirements: ing to the most-derived base class Ti of D if it is unique (subject to ambiguity) • Extensibility in both dimensions: It should be possible to add new data variants, while adapting the existing opera- • Exact-fit semantics will evaluate statement si if Ti = D. tions accordingly. It should also be possible to introduce • All-fit semantics will evaluate all statements si whose new functions. guard type Ti is a subtype of D (order of execution has to be defined) • Strong static type safety: It should be impossible to apply • Any-fit semantics might choose non-deterministically one a function to a data variant, which it cannot handle. of the statements enabled by all-fit • No modification or duplication: Existing code should neither be modified nor duplicated. The list is not exhaustive and depending on a language, any • Separate compilation: Neither datatype extensions nor of these semantics can be a plausible choice. Functional addition of new functions should require re-typechecking languages, for example, often prefer first-fit semantics be- the original datatype or existing functions. No safety cause it is similar to case analysis in mathematics. Object- checks should be deferred until link or runtime. oriented languages would typically be inclined to best-fit se- • Independent extensibility: It should be possible to com- mantics due to its similarity to overload resolution and vir- bine independently developed extensions so that they can tual dispatch, however, some do opt for first-fit semantics be used jointly. to mimic the functional style: e.g. Scala [35]. Exact-fit se- While these requirements were formulated for extensible mantics can often be seen in languages supporting discrim- data type with disjoint variants, object-oriented languages inated union types: e.g. variant records in Pascal, Ada and primarily deal with hierarchical data types. We thus found it Modula-2, oneof and variant objects in CLU, unions in C important to explicitly state an additional requirement based and C++ etc. All-fit and any-fit semantics might be seen in on the Liskov substitution principle [29]: languages based on predicate dispatching [17] or guarded commands [13], where a predicate can be seen as a charac- • Substitutability: Operations expressed on more general teristic function of a type, while logical implication can be data variants should be applicable to more specific ones seen as subtyping. that are in a subtyping relation with them.

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 3 2012/4/13 We will refer to a solution that satisfies all of the above switch overcomes all the major shortcomings of the visitor requirements as open. Numerous solutions have been pro- design pattern: posed to dealing with the expression problem in both func- • non-intrusive tional [19, 31] and object-oriented camps [22, 27, 37, 49], Case analysis with an open type switch is but very few has made its way into one of the mainstream as it inspects the hierarchy externally and can be applied languages. We refer the reader to Zenger and Odersky’s orig- retroactively. • inal manuscript for a discussion of the approaches [50]. In- New variants can be accounted for in the newly written terestingly, most of the discussed object-oriented solutions code and will be seen as a base class or default in the were focusing on the visitor design pattern and its exten- existing code. • sions, which even today seems to be the most commonly The affected functions are limited to those for which the used approach to dealing with the expression problem in newly added variant is a disjoint case. • object-oriented languages. The code avoids the control inversion and the need for A lot has been written about the visitor design pattern [18, boilerplate code that visitors introduce, and is thus a more 36, 37, 49]. Its advantages include extensibility of functions, direct expression of the intent. speed, and, being a library solution. Nevertheless, the so- 2.4 C++ Specifics: Subobjects lution is intrusive, specific to hierarchy, and requires a lot of boilerplate code to be written. It also introduces control C++ supports two kinds of inheritance: non-virtual [15] inversion, and, most importantly, – hinders extensibility of (also known as replicated [40] or repeated [47]) inheritance classes. and virtual [15] (or shared [47]) inheritance. The difference between the two only arises in situations where a class indi- rectly inherits from the same base class via more than one 2.3 Open Type Switch path in the hierarchy. Different kinds of inheritance give Note that the presence of a type switch in an object-oriented raise to the notion of subobject in C++, which are then used language alone does not solve the expression problem be- to define semantics of operations like casts, virtual func- cause the existing code may have to be modified to take tion dispatch etc. We give an informal introduction to them new variants into account. Relying on default clause is not here in order to show some subtleties of the C++ inheritance considered to be an acceptable solution in this context, be- model, which must be taken into account when addressing cause often times the only reasonable default behavior is to type switching or subtype testing. raise an exception. Zenger and Odersky note that in such cases defaults will transform type errors that should man- 1. Class Hierarchy with Multiple Inheritance of A ifest statically into runtime exceptions that are thrown dy- Common Definitions: struct Z { short z1; virtual int baz(); }; namically [50]. Z struct Y { short y1; virtual int yep(); }; While we generally agree with this observation, we would struct A : Z { char a1; virtual int foo(); }; Y Repeated Inheritance: like to point out that in our experience newly added variants struct B : A { short b1; char b2; }; were more often extending an existing variant than creating A struct C : A, Y {}; struct D : B, C { short d1[3]; int yep(); }; an entirely disjoint one. In a hypotetical compiler, for ex- B C Virtual Inheritance: ample, a new kind of type expression will typically extend struct B : virtual A { short b1; char b2; }; struct C : virtual A, Y {}; a TypeExpression variant, while a new form of annotation D struct D : B, C { short d1[3]; int yep(); }; will extend an Annotation variant, thus not extending the root ASTNode directly. Due to substitutability requirement 2. Subobject Graphs such a new variant will be treated as a variant it extends in a. Repeated Inheritance b. Virtual Inheritance all the existing code. The functions that will be affected by D::B::A::Z D::C::A::Z D::A::Z its addition and thus have to be modified will be limited to functions directly analyzing the variant it extends and not D::B::A D::C::A D::C::Y D::A D::C::Y providing a default behavior. D::B D::C D::B D::C To account for this subtlety of extensible hierarchical data types, we use a term open type switch to refer to a D D type switch that satisfies all the requirements of an open solution to expression problem stated above except for the Figure 1. Multiple Inheritance in C++ no modification or duplication requirement. We loosen it to allow modification of functions for which the newly added Consider a simple class hierarchy in Figure 1(1). Class D variant becomes a disjoint (orthogonal) case not handled indirectly inherits from class A through its B and C base by default clause. We believe that the loosened requirement classes. In this case, the user may opt to keep distinct subob- allows us to express pragmatically interesting restrictions jects of class A (repeated inheritance) or a shared one (virtual that developers are willing to live with. Besides, open type inheritance) by specifying how B and C are inherited from

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 4 2012/4/13 A. The kind of inheritance is thus not a property of a given the downcast to the most-derived type is always guaranteed class, but a property of an inheritance relation between de- to succeed regardless of the source subobject, the upcast to rived and base class and it is possible to mix the two in an the target type may be ambiguous, in which case the cast will object of the most-derived type. fail. A cast from Y to B inside an object of the most-derived A class hierarchy, i.e. an inheritance graph gives rise to a type D in Figure 1(2a,2b) will be an example of a success- subobject graph, where a given class node may be replicated ful cross cast. A similar cast from Y to A inside D under when inherited repeatedly or left shared when inherited vir- repeated inheritance of (2a) will fail because of ambiguous tually. The edges in such a graph represent subobject con- upcast from D to A. taintment and are marked with whether such containtment An interesting artefact of these distinctions can be seen on is shared or exclusive. Every class C in the class hierarchy an example of casting a subobject of type Z to a subobject will have its own subobject graph representing the subobject of type A in Figure 1(2a). The subobject D::B::A::Z will of an object of the most-derived type C. Figure 1(2) shows be successfully cast to D::B::A, while the D::C::A::Z will subobject graph for class D obtained for the class hierarchy be successfully cast to D::C::A. These casts do not involve in (1) under repeated (a) and virtual (b) inheritance of class downcasting to D followed by an upcast to A, which would A by classes B and C. The shared containment is indicated be ambiguous, but instead take the dynamic type of a larger with the dashed arrows, while exclusive with the solid ones. subobject (D::B or D::C) the source subobject is contained We will use term object descriptor to mean either pointer in into account in order to resolve the ambiguity. A similar or reference to an object, which we will use interchangably cast from Y to A will fail and should Y have also been when not explicitly specified. An object descriptor of static non-virtually derived from Z, the cast from D::C::Y::Z to A type A referencing an object of the most-derived type C can would have failed. This shows that the distinction between be understood as any *::A-node in the subobject graph of crosscast and downcast is not based solely on the presence C. Rosie and Friedman call A an effective type of object, of a subtyping relation between the source and target types, while the node in the subobject graph representing it – its but also on the actual position of the source subobject in the effective subobject. Casts in such a model can be understood most-derived type’s subobject graph. as a change from one effective subobject to another. We will C++ inheritance model, presented here informally, com- use terms source subobject and target subobject to refer to plicates the semantics and the implementation of a type effective subobjects before and after the cast. Their static switch further. On one side we have to define the semantics types will be refered to as as source type and target type of a type switch when the cast between source and target respectively. C++ distinguishes 3 kinds of casts: upcasts, types that are in subtyping relation is not possible. On the downcasts and crosscasts. other – an implementation of the cast between source and An upcast is a cast from a derived class to one of its bases. target subobjects will have to take into account the location When the base class is unambiguous, such casts are implicit of the source subobject in the subobject graph into account and require no additional annotations. When the base class is in addition to the most-derived and target types on which a ambiguous, cast failure is manifested statically in a form of a simple subtype test would solely depend. compile-time error. This is the case for example with casting D to A under repeated multiple inheritance of A, in which 2.5 Previous Work case the user needs to explicitly cast the object to B or C first in order to indicate the desired subobject and resolve The closed nature of algebraic data types allows for their ef- ambiguity. In some cases, however, introduction of such ficient implementation. The traditional compilation scheme an explicit cast is not possible: e.g. in implicit conversions assigns unique (and often small and sequential) tags to every generated by the compiler to implement covariant return variant of the algebraic data type and type switching is then types, cross casts or conversions in generic code. This does simply implemented with a multi-way branch [42] (usually a not mean that in such cases we violate the Liskov subtitution jump table) over all the tags [3]. Dealing with extensible hi- principle though – the classes are still in subtyping relation, erarchical data types makes this extremely efficient approach but an implicit conversion is not available. infeasible: A downcast is a cast from a base class to one of its derived classes. The cast has to determine at run-time whether the • Extensibility implies that the compiler may not know the source subobject is contained by a subobject of the target exact set of all the derived classes till link-time (due to type in the most-derived type’s subobject graph. Failure of separate compilation) or even run-time (due to dynamic such a cast is manifested dynamically at run-time. linking). A crosscast is a cast between classes that are not nec- • Substitutability implies that we should be able to match essarily related by inheritance. Accordingly to the C++ se- tags of derived classes against case labels representing mantics such cast is defined to be a composition of upcast tags of base classes. to target type and downcast to the most-derived type. While • Presence of multiple inheritance might require pointer adjustments that are not known at compile time (e.g. due

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 5 2012/4/13 to virtual base classes, ambiguous base classes or cross- erarchies that are lattices. Schubert’s relative numbering [41] casting). encodes each type with an interval [l, r], effectivelly mak- ing type inclusion tests isomorphic to a simple range check- There are two main approaches to implementing case anal- ing. The encoding is optimal in space and time, however it ysis on extensible hierarchical data types, discussed in the is limited to single inheritance. PQ-Encoding of Zibin and literature. Gil employs PQ-trees to improve further space and time ef- The first approach is based on either explicit or implicit ficiency of the constant-time inclusion testing [51]. While sealing of the class hierarchy, on which type switching can capable of handling type inclusion queries on heterarchies, be performed. In Scala, for example, the user can forbid fu- the approach makes the closed world assumption and can ture extensions from a given class hierarchy through the use be costly for use with dynamic linking because it is not in- of a sealed keyword [16, §4.3.2]. The compiler then uses the cremental. The approach of Gibbs and Stroustrup [20] em- above tag allocation over all variants to implement type anal- ploys divisibility of numbers to obtain a constant-time type ysis. In some cases the sealing may happen implicitly. For inclusion test. The approach can handle multiple inheritance example, languages that allow names with internal and ex- and was the first constant-time technique to addresses the ternal linkage may employ the fact that classes with internal problem of casts between subobjects. Unfortunately the ap- linkage will not be externally accessible and thus effectively proach limits the size of the class hierarchies that can be sealed. While clearly efficient, the approach is not open as it encoded with this technique. Ducournau proposed constant- avoids the question rather than solves. time inclusion test based on the fact that in an open solution The broader problem with this approach is that techniques a class has known amount of base classes and thus perfect that rely on unique or sequential compile or link-time con- hashes can be used to map them to this-pointer offsets typ- stants violate independent extensibility since without a cen- ically used to implement subobject casts[14]. Unfortunately tralized authority there is no guarantee same constant will the approach addresses only virtual multiple inheritance and not be chosen in type unsafe manner by independent ex- similarly to other approaches relies on load-time computa- tensions. Updating such constants at load time may be too tions that may be costly. Detailed analysis and explanation costly even when possible. More often than not however of existing constant-time type inclusion tests can be found such updates may require code regeneration since decision in [44] and [51]. trees, lookup tables etc. may have been generated by com- With the exception of work by Gibbs and Stroustrup [20], piler for given values. all the approaches to efficient type-inclusion testing we An important practical solution that follows this approach found in the literature were based on the assumption that the is the visitor design pattern [18]. The set of visit methods in outcome of a subtyping test as well as the subsequent cast visitor’s interface essentially seals the class hierarchy. Ex- depend only on the target type and the most-derived type of tensions have been proposed in the literature [49], however the object. While such assumption is sound for subtyping they have problems of their own, discussed in §5. tests and subtype casts for shared inheritance (including sin- The second approach employs type inclusion tests com- gle), it does not reflect the relationship between subobjects bined with decision trees [5] to avoid duplicate checks. The in the general case multiple inheritance present in C++. efficiency of the approach is then entirely focused on the ef- ficiency of type inclusion tests [7, 9, 12, 14, 20, 26, 41, 44, 2.6 The Source of Inefficiency 48, 51]. While constant-time type inclusion tests are invaluable in Type inclusion tests for single inheritance were initially optimizing subtype tests in programing languages, their implemented by traversing a linked list of types, as proposed use in implementing a type switch is inferior to some by Wirth [48]. Such encoding requires little space, but runs workaround techniques. This may prevent wide adoption in time proportional to the distance between the two types of a language implementation of such a feature due to its in the class hierarchy. A trivial constant-time type inclusion inferior performance. We implemented 3 constant-time type test can be achieved with a binary matrix, encoding the sub- inclusion tests: binary matrix ??, Cohen’s algorithm [9] and typing relation on the class hierarchy [12]. While efficient fast dynamic cast [20] and combined them with a decision in time, it has quadratic space requirements, which makes it tree to implement a type switch on a class hierarchy ideally expensive for use on large class hierarchies. In response to suited for such scenario. The class hierarchy used in this Wirth’ original publication, Cohen proposed the first space- comparison was a perfect binary tree with classes number 2i efficient constant-time algorithm, which, howver, could only and 2i + 1 derived from a class number i. Our workaround deal with single inheritance [9]. Hierarchical encoding is an- techniques included visitor design pattern and a switch on other constant-time test that maps subtype queries into sub- the sealed sequential set of tags. set queries on bit-vectors [7, 26]. The approach can handle The chart in Figure 2 shows the time (Y-axis) each tech- multiple inheritance, but the space and time required for a nique took to recognize an object of the most-derived type i subtype test in this encoding increases with the size of the (X-axis). It is easy to see that the logarithmic cost associated class hierarchy, also Caseau’s approach is limited to class hi- with the decision tree very quickly surpasses the constant

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 6 2012/4/13 bles the Class Precedence List (CPL) of object-oriented de- scendants of Lisp (e.g. Dylan, Flavors, LOOPS, and CLOS) used there for linearization of class hierarchies. TPL is just an implementation detail and the only reason we distinguish TPL from CPL is that in C++ classes are often separated into interface and implementation classes and it might so happen that the same tag is associated by the user with an interface and several implementation classes. We also assume the tag- constant associated with a class Di be accessible through a static constant Di::class tag. These simplifications are not essential and the library does not rely on any of these as- sumptions. Instead the user can retroactively narrate to the library the specific tag encoding used throught a trait-like Figure 2. Type switch based on const-time type inclusion classes. tests A type switch below, built on top of a hierarchy of tagged classes, proceeds as a regular switch on the subject’s tag. overhead of double dispatch present in the visitor design pat- If the jump succeeds, we found an exact match; otherwise, tern or the jump-table implementation of the switch on all we get into a default clause that obtains the next tag in the tags. The edgy shape of timing results reflects the shape of tag precedence list and jumps back to the beginning of the the binary tree class hierarchy used for this experiment. switch statement for a rematch: 3. Type Switch size t attempt = 0; size t tag = object taglist[attempt]; C++ does not have direct support of algebraic data types, → ReMatch: but they can be encoded with classes in a number of ways. switch (tag) { One common such encoding is to introduce an abstract base default: class representing an algebraic data type with several derived tag = object taglist[++attempt]; classes representing variants. The variants can then be dis- → ReMatch; criminated with either run-time type information (referred to case end of list: as polymorphic encoding) or a dedicated member of a base break; class (referred to as tagged encoding). case D ::class tag: While our library supports both encodings, it handles 1 D & match = static cast D & (*object); them differently to let the user choose between openness 1 ⟨ 1 ⟩ s ;break; and efficiency. The type switch for tagged encoding is sim- 1 ... pler and more efficient for many typical use cases, however, case D ::class tag: making it open will eradicate its performance advantages. n D & match = static cast D & (*object); The difference in performance is the price we pay for keep- n ⟨ n ⟩ s ;break; ing the solution open. We describe pros and cons of each n } approach in §4.2. The above structure, which we call Tag Switch, lets us dis- 3.1 Attractive Non-Solution patch to case clauses of the most-derived class with an over- While Wirth’ linked list encoding was considered slow for head of initializing two local variables, compared to effi- subtype testing it can be adopted for very efficient type cient switch used on algebraic data types. Dispatching to switching. The idea is to combine the fast switching on a case clause of a base class will take time roughly pro- closed algebraic datatypes with a loop that tries the tags of portional to the distance between the matched base class base classes when switching on derived tags fails. and the most-derived class in the inheritance graph, thus For simplicity of presentation we assume a pointer to ar- the technique is not constant. When none of the base class ray of tags be available directly from within object’s taglist tags were matched, we will necessarily reach the end of list data member. The array is of variable size, its first element is marker in the tag precedence list and exit the loop. As men- always the tag corresponding to the most-derived type of the tioned before, the default clause of the type switch can object, while its end is marked with a dedicated end of list be implemented with a case clause on subject type’s tag: marker distinct from all the tags. The tags in between are case S::class tag: topologically sorted according to the subtyping relation with The efficiency of the above code crucially depends on the incomparable siblings listed in local precedence order – the set of tags we match against be small and sequential to jus- order of the direct base classes used in the class definition. tify the use of jump table instead of decision tree to imple- We call such a list a Tag Precedence List (TPL) as it resem- ment the switch. This is usually not a problem in closed hier-

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 7 2012/4/13 archies based on tag encoding since the user of the hierarchy switch to memoize which case is enabled for a given value hand-picks himself the tags. The use of static cast to obtain of x. proper reference once the most specialized derived class has The idea is to generate a simple cascading-if statement been established, however, essentially limits the use of this interleaved with jump targets and instructions that associate mechanism to single inheritance only. This of course only the original value with enabled target. The code before the refers to the way target classes inherit from the subject type statement looks up whether the association for a given value – they can freely inherit other classes as long as they do not has already been established, and, if so, jumps directly to the create repeated of virtual multiple inheritance of the subject target; otherwise the sequential execution of the cascading-if type. Due to these assumptions, the technique is not open be- is started. To ensure that the actual code associated with the cause it may violate independent extensibility. Moving away predicates remains unaware of this optimization, the code from these assumptions in order to make the technique more preceeding it after the target must re-establish any invariant open (e.g. randomizing tags, using dynamic cast etc.) will guaranteed by sequential execution (§3.7). also eradicate its performance advantages. The above code can easily be produced in a compiler setting, but producing it in a library setting is a challenge. 3.2 Open but Inefficient Solution Inspired by Duff’s Device [43], we devised a construct that Instead of starting with an efficient solution and trying to we call Memoization Device that does just that in standard make it open, we start with an open solution and try to C++: make it efficient. The following cascading-if statement im- typedef decltype(x) T; plements the first-fit semantics for our type switch in a truly static std::unordered map⟨T,size t⟩ jump targets; open fashion: * * switch (size t& jump to = jump targets[x]) { if (T1 match = dynamic cast⟨T1 ⟩(subject)) { s1;} else * * default: // entered when we have not seen x yet if (T2 match = dynamic cast⟨T2 ⟩(subject)) { s2;} else ... if (P1(x)) { jump to = 1; case 1: s1;} else * * if (P2(x)) { jump to = 2; case 2: s2;} else if (Tn match = dynamic cast⟨Tn ⟩(subject)) { sn;} ... Its main drawback is performance: a typical implementation if (Pn(x)) { jump to = n; case n: sn;} else of dynamic cast takes time proportional to the distance jump to = n + 1; between base and derived classes in the inheritance tree. case n + 1: // none of the predicates is true on x What is worse, is that the time to uncover the type in the ith } case clause is proportional to i, while failure to match will The static jump targets hash table will be allocated upon always take the longest. In a test involving a flat hierarchy of first entry to the function. The map is initially empty and 100 variants it took 93 cycles to discover the first type and according to its logic, request for a key x not yet in the 22760 to discover the last (with linear combination of those map will allocate a new entry with its associated data default times to discover the types in between). A visitor design initialized (to 0 for size t). Since there is no case 0 in pattern could uncover any type in about 55 cycles, regardless the switch, the default case will be taken, which, in turn, will of its position among the case clauses, while a switch based initiate sequential execution of the interleaved cascading- on sequential tags could achieve the same in less than 20 if statement. Assignments to jump to effectively establish cycles. The idea is thus to combine the openness of the association between value x and corresponding predicate, above structure with the efficiency of a jump table on small since jump to is just a reference to jump targets[x]. The sequential values. last assignment records absence of enabled predicates for the 3.3 Memoization Device value. To change the first-fit semantics of the above construct Let us look at a slightly more general problem than type into sequential all-fit, we remove the elses and rely on switching. Consider a generalization of the switch statement fall-through behavior of the switch. We also make the as- that takes predicates on a subject as its clauses and executes signments conditional to make sure only the first one gets the first statement si whose predicate is enabled: recorded: switch (x) { case P (x): s ; ... case P (x): s ; } 1 1 n n if (Pi(x)) { if (jump to ==0) jump to = i; case i: si;} Assuming that predicates depend only on x and nothing else Note that the protocol that has to be maintained by this struc- as well as that they do not involve any side effects, we can ture does not depend on the actual values of case labels. We be sure that the next time we come to such a switch with only require them to be different and include a predefined the same value, the same predicate will be enabled first. default value. The default clause can be replaced with a case Thus, we would like to avoid evaluating predicates and jump clause for the predefined value, however keeping the default straight to the statement it guards. In a way we would like the clause results in a faster code. A more important perfor-

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 8 2012/4/13 mance consideration is to keep the values close to each other. classes that do not change the outcome of the predicate. The Not following this rule might result in a compiler choosing a map can then associate the equivalence class of a value with decision tree over a jump table implementation of the switch, a target instead of associating the value with it. which in our experience significantly degrades the perfor- In application to type switching, the idea is to use the mance. memoization device to learn the outcomes of type inclusion The first-fit semantics is not an inherent property of the tests (with dynamic cast used as a predicate), thus avoid- memoization device. Assuming that the conditions are ei- ing calls to it on subsequent runs. It is easy to see that objects ther mutually exclusive or imply one another, we can build a can be grouped into equivalence classes based on their most- decision-tree-based memoization device that will effectively derived type without affecting the results of predicates – the have most-specific semantics – an analog of best-fit seman- outcome of each type inclusion test will be the same on all tics in predicate dispatching [17]. the objects from the same equivalence class. We can use the Imagine that the predicates with the numbers 2i and 2i + address of class’ type info object obtained in constant time 1 are mutually exclusive and each imply the value of the with typeid() operator as a unique identifier of each most- predicate with number i i.e. ∀x ∈ Domain(P ) derived type. Presence of multiple type info objects for the same class, as is often the case when dynamic linking is in- P2i+1(x) → Pi(x) ∧ P2i(x) → Pi(x) ∧ ¬(P2i+1(x) ∧ P2i(x)) volved, is not a problem as we would effectively split a sin- gle equivalence class into multiple ones. This in fact would An example of predicates that satisfy this condition are class have been a solution if we were only interested in class mem- membership tests where the truth of testing membership in bership. More often than not, however, we will be interesting a derived class implies the truth of testing membership in its in obtaining a reference to the target type of the subject and base class. we saw in §?? that proper this-pointer adjustments depend The following decision-tree based memoization device not only on the most-derived type, but also on target type will execute the statement si associated with the most- and most importantly – path to the subject’s static type from specific predicate Pi (i.e. the predicate that implies all other the most-derived type in the inheritance graph. Ideally we predicates true on x) that evaluates to true or will skip the would like to have different equivalence classes per different entire statement if none of the predicates is true on x. paths from object’s most-derived type to its static types, but switch (size t& jump to = jump targets[x]) { there seem to be no easy way of identifying them given just default: an object descriptor. if (P1(x)) { 3.4 Virtual Table Pointers if (P (x)) { 2 A class that declares or inherits a virtual function is called a if (P (x)) { jump to = 4; case 4: s ;} else 4 4 polymorphic class. The C++ standard [24] does not pre- if (P (x)) { jump to = 5; case 5: s ;} 5 5 scribe any specific implementation technique for virtual jump to = 2; case 2: s ; 2 function dispatch. However, in practice, all C++ compilers } else use a strategy based on so-called virtual function tables (or if (P (x)) { 3 vtables for short) for efficient disptach. The vtable is part of if (P (x)) { jump to = 6; case 6: s ;} else 6 6 the reification of a polymorphic class type. C++ compilers if (P (x)) { jump to = 7; case 7: s ;} 7 7 embed a pointer to a vtable (vtbl-pointer for short) in ev- jump to = 3; case 3: s ; 3 ery object of polymorphic class type. CFront, the first C++ } compiler, puts the vtbl-pointer at the end of an object. The jump to = 1; case 1: s ; 1 so-called “common vendor C++ ABI”[8], further referred } else { jump to = 0; case 0: ; } to as C++ ABI when not indicated otherwise, requires the } vtbl-pointer to be at offset 0 of an object. The following Our library solution prefers the simpler cascading-if ap- compilers comply with the C++ ABI: GCC (3.x and up); proach only because the necessary structure of the code can Clang and llvm-g++; versions of Intel and HP com- be laid out directly with macros. A compiler solution will pilers, and compilers from ARM. We do not have access to use the decision-tree approach whenever possible to lower the unpublished Microsoft ABI, but we have experimental the cost of the first match from linear in case’s number to evidence that Microsoft’s C++ compiler also puts the vtbl- logarithmic as seen in Figure??. pointer at the start of an object. The main advantage of the memoization device is that it While the exact offset of the vtbl-pointer within the ob- can be built around almost any code, providing that we can ject is not important for our discussion, it is important to re-establish the invariants, guaranteed by sequential execu- realize that every object of a static type S* or S& pointed tion. Its main disadvantage is the size of the hash table that to or referenced by a polymorphic class S will have a vtbl- grows proportionally to the number of different values seen. pointer at a predefined offset. Such offset may be different Fortunately, the values can often be grouped into equivalence for different static types S, in which case the compiler will

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 9 2012/4/13 know at which offset in type S the vtbl-pointer is located. For however. First of all the same vtbl-pointer is usually shared a library implementation we assume presence of a function by multiple types, for example, the first vtbl-pointer in Fig- template ⟨typename S⟩intptr t vtbl(const S* s); that re- ure ??(2) will be shared by objects of static type Z*, A*, B* turns the address of the virtual table corresponding to the and D*. This is not a problem for our purpose, because the subobject referenced to by s. Such a function can be triv- subobjects of these types will be at the same offset in the ially implemented for the common C++ ABI, where the most-derived object. Secondly, and more importantly, how- vtbl-pointer is always at offset 0: ever, there are legitimate optimizations that let the compiler share the same vtable among multiple subobjects of often template typename S std::intptr t vtbl(const S* s) { ⟨ ⟩ unrelated types. static assert(std::is polymorphic S ::value, ”error”); ⟨ ⟩ Generation of the Run-Time Type Information (or RTTI return *reinterpret cast const std::intptr t* (s); ⟨ ⟩ for short) can typically be disabled with a compiler switch } and the Figure 3(2) shows the same vtable layouts once the Consider a repeated multiple inheritance hierarchy from RTTI has been disabled. Since neither baz nor foo were Figure ??(1). Each of the vtbl fields shown in Figure ??(2) overriden, the prefix of the vtable for the C subobject in D will hold a vtbl-pointer referencing a group of virtual meth- is exactly the same as the vtable for its B subobject, the A ods known in object’s static type. Figure 3(1) shows a typi- subobject of C or the entire vtable of A and B classes. Such cal layout of virtual function tables together with objects it layout, for example, is produced by Microsoft Visual C++ points to for classes B and D. 11 when the command-line option /GR− is specified. Visual C++ compiler has been known to unify code identical on binary level, which in some cases may result in sharing of 1. Vtable layout with Run-Time Type Information the same vtable between unrelated classes (e.g. when virtual RTTI for B RTTI for D functions are empty). &A::foo We now would like to show more formally that in the thunk Y in D &Z::baz presence of RTTI, a C++ ABI compliant implementation &D::yep will always have all the vtbl-pointers different. To do so, B 0 D 0 -12 -20 RTTI baz foo RTTI baz foo yep RTTI baz foo RTTI yep we need look closer at the notion of subobject, which has been formalized before [38, 40, 47]. We follow here the B* C* 2. Vtable layout without Run-Time Type Information presentation of Ramamanandro et al [38].

&A::foo 3.5 Subobjects thunk Y in D &Z::baz &D::yep In a given program P , a class B is a direct repeated base A, B, C::A, D, D::B, D::C::A D::C::E baz foo yep yep class of D if B is mentioned in the list of base classes of D without the virtual keyword (D ≺R B). Similarly, B* C* a class B is a direct shared base class of D if B is men- tioned in the list of base classes of D with the virtual key- Figure 3. VTable layout with and without RTTI word (D ≺S B). A reflexive transitive closure of these re- ∗ ∗ subtyping Entries in the vtable to the right of the address pointed lationships ⪯ = (≺R ∪ ≺S) defines the relation P subobject to by a vtbl-pointer represent pointers to functions, while on types of program . A base class of a given complete object σ h, l entries to the left of it represent various additional fields is represented by a pair = ( ) with h Repeated, Shared like: pointer to class’ type information, offset to top, offsets ∈ { } representing the kind of inheritance Repeated l to virtual base classes etc. In many implementations, this- (single inheritance is with one base class) and pointer adjustments required to properly dispatch the call representing the path in a non-virtual inheritance graph. A C σ A σ were stored in the vtable along with function pointers. Today predicate * ( states that designates a subobject of A C most of the implementations prefer to use thunks or tram- static type within the most-derived object of type . More polines – additional entry points to a function, that adjust formally: this-pointer before transferring the control to the function, – which was shown to be more efficient []. Thunks in general C Repeated,C  C may only be needed when its virtual function gets overriden. * ( ∶∶ ) ( In such case the overriden function may be called via pointer C BB Repeated, l A to base class or a pointer to derived class, which may not be ≺R * ( ) ( at the same offset in the actual object. C * (Repeated,C ∶∶ l) ( A The intuition behind our proposal is to use the values of vtbl-pointers stored inside the object to uniquely identify the C ≺S BB * (h, l) ( A subobject in it. There are several problems with the approach C * (Shared, l) ( A

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 10 2012/4/13  indicates an empty path, but we will generally omit it in always present and is shown as the second field to the writing when understood from the context. In case of re- left of any vtbl-pointer in Figure 3(1). The numeric value peated inheritance in Figure 1(1), an object of the most- shown indicates the actual offset based on the object derived class D will have the following Repeated subob- layout from Figure ??(2). jects: D::C::Y, D::B::A::Z, D::C::A::Z, D::B::A, D::C::A, • vbase(a.vtbl): Virtual Base (vbase) offsets are used to D::B, D::C, D. Similarly, in case of virtual inheritance in access the virtual bases of an object. Such an entry is the same expample, an object of the most-derived class D required for each virtual base class. None are shown in will have the following Repeated subobjects: D::C::Y, D::B, our example in Figure 3(1) since it discussed repeated D::C, D as well as the following Shared subobjects: D::A::Z, inheritance, but they will occupy further entries to the left D::Z, D::A. of the vtbl-pointer, when present. It is easy to show by structural induction on the above We also use the notation offset σ to refer to the offset of definition, that C *σ (A Ô⇒ σ = (h, C ∶∶ l1)∧σ = (h, l2 ∶∶ ( ) the given subobject σ within C, known by the compiler. A ∶∶ ), which simply means that any path to a subobject of static type A within the most-derived object of type C starts Theorem 1. In an object layout that adheres to the com- with C and ends with A. This objservation shows that σ– = mon vendor C++ ABI with enabled RTTI, equality of vtbl- (Shared, ) does not represent a valid subobject. If ΣP is the pointers of two objects of the same static type implies that domain of all subobjects in the program P extended with they both belong to subobjects with the same inheritance σ–, then a cast operation can be understood as a function path in the same most-derived class. δ ∶ ΣP → ΣP . We use σ– to indicate an impossibility of ∀a1, a2 ∶ A S a1 ∈ C1 * σ1 ( A ∧ a2 ∈ C2 * σ2 ( A a cast. The fact that δ is defined on subobjects as opposed a1.vtbl = a2.vtbl ⇒ C1 = C2 ∧ σ1 = σ2 to actual run-time values reflects the non-coercive nature of the operation – i.e. the underlain value remains the same. Proof. Let us assume first a1.vtbl = a2.vtbl but C1 ≠ C2. In Any implementation of such a function must thus satisfy the this case we have rtti(a1.vtbl) =rtti(a2.vtbl). By definition following condition: rtti(a1.vtbl) = C1 while rtti(a2.vtbl) = C2, which contra- dicts that C1 ≠ C2. Thus C1 = C2 = C. δ σ σ C σ A C σ B ( 1) = 2 ∧ * 1 ( Ô⇒ * 2 ( Let us assume now that a1.vtbl = a2.vtbl but σ1 ≠ σ2. Let σ h , l , σ h , l i.e. the most-derived type of the value does not change dur- 1 = ( 1 1) 2 = ( 2 2) h h ing casting, only the way we reference it does. We refer to If 1 ≠ 2 then one of them refers to a virtual base h A as the source type and σ as the source subobject of the while the other to a repeated one. Assuming 1 refers to 1 vbase a .vtbl cast, while to B as the target type and to σ as the target a virtual base, ( 1 ) has to be defined inside the 2 vbase a .vtbl subobject of it. The type C is the most-derived type of the vtable according to the ABI, while ( 2 ) – should vtbl value being casted. The C++ semantics states more require- not. This would contradict again that both refer to the same virtual table. ment to the implementation of δ: e.g. δ σ– σ– etc. but ( ) = h h h h Shared their precise modeling is out of the of this discussion. We thus have 1 = 2 = . If = then there A C We would only like to point out here that since the result is only one path to such in , which would contradict σ σ h Repeated l of the cast does not depend on the actual value and only on 1 ≠ 2. If = then we must have that 1 ≠ l2. In this case let k be the first position in which they the source subobject and the target type, we can memoize j j k k differ: l1 = l2∀j < k ∧ l1 ≠ l2 . Since our class A is the outcome of a cast on one instance in order to apply its k k results to another. a base class for classes l1 and l2 , both of which are in turn base classes of C, the object identity requirement of 3.6 Uniqueness of vtbl-pointers under the C++ ABI C++ requires that the relevant subobjects of type A have Given a reference a to polymorphic type A that points to a different offsets within class C: offset(σ1) ≠ offset(σ2) However offset σ off2top a .vtbl off2top a .vtbl subobject σ of the most-derived type C (i.e. C*σ(A is true), ( 1) = ( 1 ) = ( 2 ) = we will use the traditional field-access notion a.vtbl to refer offset(σ2) since a1.vtbl = a2.vtbl, which contradicts that the to the virtual table of that subobject. The exact structure of offsets are different. the virtual table as mandated by the common vendor C++ Conjecture in the other direction is not true in general as ABI is immaterial for this discussion, but we mention a few there may be duplicate vtables for the same type present fields that are important for the reasoning [8, §2.5.2]: at run-time. This happens in many C++ implementations in • rtti(a.vtbl): the typeinfo pointer points to the typeinfo the presence of Dynamically Linked Libraries (or DLLs for object used for RTTI. It is always present and is shown as short) as the same class compiled into executable and DLL it the first field to the left of any vtbl-pointer in Figure 3(1). loads may have identical vtables inside the executable’s and • off2top(a.vtbl): the offset to top holds the displacement DLL’s binaries. to the top of the object from the location within the object Note also that we require both static types to be the same. of the vtbl-pointer that addresses this virtual table. It is Dropping this requirement and saying that equality of vtbl-

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 11 2012/4/13 pointers also implies equality of the static types is not true if (info.second ==0) { // supports fall−through in general because a derived class can share the vtbl-pointer info.first = intptr t(tptr)−intptr t(x); // offset with its primary base class. The theorem can be reformu- info.second = i; // jump target lated, however, stating that one static type will necessarily } be a subtype of the other. The current formulation is suffi- case i: // i is a constant here − clause’s position in switch cient for our purposes, while reformulation will require more auto match = adjust ptr⟨Ti⟩(x,info.first); elaborate discussion of the algebra of subobjects [38], which si; we touch only briefly. } During construction and deconstruction of an object, the value of a given vtbl-pointer may change. In particular, that Class std::unordered map provides amortized constant value will reflect the fact that the most-derived type of the time access on average and linear in the amount of elements object is the type of its fully constructed part only. This, in the worst case. We show in the next section that most however, does not affect our reasoning, as during such tran- of the time we will be bypassing traditional access to its sition we also treat the object to have the type of its fully elements. We need this extra optimization because, as-is, the constructed base only. Such interpretation is in line with the type switch is still about 50% slower than the visitor design C++ semantics for virtual function calls and the use of RTTI pattern. during construction and destruction of an object. Once the 3.8 Minimization of Conflicts complete object is fully constructed, the value of the vtbl- pointer will remain the same for the lifetime of the object. Virtual table pointers are not constant values and are not even guaranteed to be the same between different runs of the 3.7 Vtable Pointer Memoization application, because techniques like address space layout The C++ standard requires that information about types be randomization or of the module are likely to change available at run time for three distinct purposes: them. The relative distance between them will remain the same as long as they come from the same module. • to support the typeid operator, Knowing that vtbl-pointers point into an array of function • to match an exception handler with a thrown object, and pointers, we should expect them to be aligned accordingly • to implement the dynamic cast operator. and thus have a few lowest bits as zero. Moreover, since and if any of these facilities are used in a program that was many derived classes do not introduce new virtual functions, compiled with disabled RTTI, the compiler will emit an er- the size of their virtual tables remains the same. When allo- ror or at least a warning. Some compilers (e.g. Visual C++) cated sequentially in memory, we can expect a certain num- additionally let a library check presence of RTTI through a ber of lowest bits in the vtbl-pointers pointing to them to predefined macro, thus letting it report an error if its depen- be the same. These assumptions, supported by actual obser- dence on RTTI cannot be satisfied. Since our solution relies vations, has made virtual table pointers of classes related by on dynamic cast to perform casts at run-time, we implic- inheritance ideally suitable for hashing – the values obtained itly rely on the presence of RTTI and thus fall into the set- by throwing away the common bits on the right were com- ting that guarantees the preconditions of Theorem 1. Since pactly distributed in small disjoint ranges. We use them to all the objects that will be coming through a particular type address a cache built on top of the hash table in order to switch will have the same static type, the theorem guaran- eliminate a hash table lookup in most of the cases. tees that different vtbl-pointers will correspond to different Let Ξ be the domain of integral representations of point- ers. Given a cache with 2k entries, we use a family of hash subobjects. The idea is thus to group them accordingly to the k l functions Hkl ∶ Ξ → [0..2 − 1] defined as Hkl(v) = v~2 value of their vtbl-pointer and associate both jump target and k the required offset with it through memoization device: mod 2 to index the cache, where l ∈ [0..32] (assuming 32 bit addresses) is a parameter modeling the number of com- typedef pair⟨ptrdiff t,size t⟩ type switch info; mon bits on the right. Division and modulo operations are static unordered map⟨intptr t, type switch info⟩ jump targets; implemented with bit-shifts since denominator in each case type switch info& info = jump targets[vtbl(x)]; is a power of 2, which in turn explains the choice of the cache const void* tptr; size. ′ ′′ switch (info.second) ... Given a hash function Hkl, pointers v and v are said to be in conflict when H v′ H v′′ . For a given set of The code for the ith case now evaluates the required offset kl( ) = kl( ) pointers V 2Ξ we can always find such k and l that H on the first entry and associates it and the target with the ∈ kl will render no conclicts between its elements, however the vtbl-pointer of the subject. The call to adjust ptr T re- ⟨ i⟩ required cache size 2k can be too large to justify the use of establishes the invariant that match is a reference to type memory. The value K such that 2K−1 V 2K is the T of the subject x. < S S ≤ i smallest value of k under which absence of conflicts is still * if (tptr = dynamic cast⟨const Ti ⟩(x)) { possible. We thus allow k to only vary in range [K,K +1] to

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 12 2012/4/13 V ensure that the cache size is never more than 4 times bigger our original Hkl minimizing the number of conflicts. An than the minimum required cache size. important observation here is that the exact location of these Given a set V = {v1, ..., vn}, we would like to find a “extra” vtbl-pointers is not important, only the total number pair of parameters (k, l) such that Hkl will render the least m of them is. The probability of conflict under uniform V m number of conflicts on the elements of . Since for a fixed distribution of vi is thus always going to have form n , where set V , parameters k and l vary in a finite range, we can 0 ≤ m < n. always find the optimal (k, l) by trying all the combinations. V k Let Hkl ∶ V → [0..2 −1] be the hash function corresponding 4. Evaluation to such optimal (k, l) for the set V . In our setting, set V represents the set of vtbl-pointers Our evaluation methodology consisted of several indepen- coming through a particular type switch. While the exact dent studies of the technique in order to achieve a better con- values of these pointers are not known till run-time, their fidence in its validity. Our first study involved comparison of offset from the module’s base address is. This can often be relative performance of our approach against the visitor de- sufficient to at least estimate optimal k and l in a compiler sign pattern. The second study did a similar comparison with setting. In the library setting we estimate them by recomput- built-in facilities of Haskell and OCaml. In the third study ing them after a given amount of actual collisions happened we looked at how well our caching mechanisms deal with in cache. some large real-world class hierarchies. In the last study we When HV is injective (renders 0 conflicts on V ), the did rewrite an existing application that was based on visitors kl using our approach and compared the two. frequency of any given vtbl-pointer vi coming through the type switch does not affect the overal performance of the V 4.1 Comparison with Visitor Design Pattern switch. However when Hkl is not injective, we would prefer the conflict to happen on less frequent vtbl-pointers. Given Our evaluation methodology consists of several benchmarks representing various uses of objects inspected with either a probability pi of each vtbl-pointer vi ∈ V we can compute the probability of conflict rendered by a given Hkl: visitors or type switching. The repetitive benchmark (REP) performs calls on differ- ent objects of the same most-derived type. This scenario hap- p2 k ∑ i 2 −1 j pens in object-oriented setting when a group of polymorphic vi∈V P V p 1 kl objects is created and passed around (e.g. numerous particles kl( ) = Q ( Q i)( − 2 ) j=0 j pi vi∈V ( ∑ ) of a given kind in a particle simulation system). We include kl v ∈V j i kl it because double dispatch becomes twice faster (27 vs. 53 j cycles) in this scenario compared to others due to cache and where Vkl = {v ∈ V SHkl(v) = j}. In this case, the optimal V call target prediction mechanisms. hash function Hkl can similarly be defined as Hkl that min- imizes the above probability of conflict on V . The sequential benchmark (SEQ) effectively uses an ob- ject of each derived type only once and then moves on to Probabilities pi can be estimated in a compiler settings through profiling, while in a library setting we let the user an object of a different type. The cache is typically reused enable tracing of frequencies of each vtbl-pointer. This in- the least in this scenario, which is typical of lookup tables, troduces an overhead of an increment into the critical path of where each entry is implemented with a different derived execution, and according to our tests degrades the overal per- class. formance by 1-2%. By default, we do not enable frequency The random benchmark (RND) is the most representative tracing, however, because the significant drop in the number as it randomly makes calls on random objects, which will of actual collisions was not reflected in a noticeable decrease probably be the most common usage scenario in the real in execution time. This was because the total number of ac- world. tual collisions, even in non-frequency based caching, was Presence of forwarding in any of these benchmarks refers much smaller than the number of successful cache hits. to the common technique used by visitors where, for class hi- erarchies with multiple levels of inheritance, the visit method Assuming the uniform distribution of vi and substituting 1 of a derived class will provide a default implementation of the probability pi = n , where n = SV S, into the above formula we will get: forwarding to its immediate base class, which, in turn, may forward it to its base class, etc. The use of forwarding in visi- tors is a way to achieve substitutability, which in type switch 2k−1 j j SVklS − 1 corresponds to the use of base classes in the case clauses. Pkl(V ) = Q [SVklS ≠ 0] j=0 n The class hierarchy for non-forwarding test was a flat hierarchy with 100 derived classes, encoding an algebraic j The value SVklS − 1 represents the amount of “extra” pointers data type. The class hierarchy for forwarding tests had two V mapped into the entry j in cache and thus Hkl obtained levels of inheritance with 5 intermediate base classes and by minimization of probability of conflict is the same as 95 derived ones. While we do not advocate here for the

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 13 2012/4/13 closed solution of §3.1, we included it in our tests to show We can see that type switching wins by a good margin the performance gains a closed solution might have over the when implemented with tag switch as well as in the pres- open one. Our library supports both solutions with the same ence of at least one level of forwarding. Note that the num- surface syntax, which is why we believe many users will try bers are relative, and thus the ratio depends on both the per- them both before settling on one. formance of virtual function calls and the performance of The benchmarks were executed in the following config- switch statements. Visual C++ was generating faster virtual urations refered to as Linux Desktop and Windows Laptop function calls, while GCC was generating faster switch state- respectively: ments, which is why their relative performance seem to be much more favorable for us in the case of GCC. Similarly • Lnx: Dell Dimension® desktop with Intel® Pentium® D the code for x64 is only slower relatively: the actual time (Dual Core) CPU at 2.80 GHz; 1GB of RAM; Fedora spent for both visitors and type switching was smaller than Core 13 that for , but it was much smaller for visitors than type G++ 4.4.5 executed with -O2 switching, which resulted in worse relative performance. ® ® • Win: Sony VAIO laptop with Intel Core™i5 460M 4.2 Open vs. Closed Type Switch CPU at 2.53 GHz; 6GB of RAM; Windows 7 Profes- sional With a few exceptions for x64, it can be seen from Figure 4 G++ 4.5.2 and 4.6.1 / MinGW executed with -O2; x86 that the performance of the closed tag switch dominates the binaries performance of the open type switch. We believe that the MS Visual C++ 2010 Professional x86/x64 binaries difference, often significant, is the price one pays for the true with profile-guided optimizations openness of the vtable pointer memoization solution. As we mentioned in §3.1, the use of tags, even allocated by compiler, may require integration efforts to ensure that The code on the critical path of our type switch implemen- different DLLs have not reused the same tags. Randomiza- tation benefits significantly from branch hinting as some tion of tags, similar to a proposal of Garrigue [19], will not branches are much more likely than others. We use the eliminate the problem and will surely replace jump tables branch hinting in GCC to guide the compiler, but, unfortu- in switches with decision trees. This will likely significantly nately, Visual C++ does not have similar facilities. Microsoft degrade the numbers for the part of Figure 4 representing suggests to use Profile-Guided Optimization to achieve the closed tag switch, since the tags in our experiments were all same, which is why the results for Visual C++ reported here sequential. have been obtained with profile-guided optimizations en- The reliance of a tag switch on static cast has severe limi- abled. The results without profile-guided optimizations can tations in the presence of multiple inheritance, and thus is not be found in the accompanying technical report [4, §10]. as versatile as open type switch. Overcoming this problem We compare the performance of our solution relative to will either require the use of dynamic cast or techniques the performance of visitors in Figure 4. The values are given similar to those used for vtable pointer memoization, which as percentages of performance increase against the slower will likely degrade tag switch’es performance numbers even technique. Numbers in regular font represent cases where further. type switching was faster, while numbers in bold indicate Note also that the approach used to implement open type cases where visitors were faster. switch can be used to implement both first-fit and best-fit se- mantics, while the tag switch is only suitable for best-fit se- Open Closed mantics. Their complexity guarantees also differ: open type G++ MS VC++ G++ MS VC++ switch is constant on average, but slow on the first call with

x86-32 x86-32 x86-32 x86-64 x86-32 x86-32 x86-32 x86-64 given subobject. Tag switch is logarithmic in the size of the REP 16% 55% 4% 0% 124% 216% 124% 47% class hierarchy (assuming a balanced hierarchy), including SEQ 56% 3% 3% 1% 640% 520% 34% 14% the first call. This last point can very well be seen in Figure 4, RND 56% 1% 18% 27% 603% 542% 43% 16% where the performance of a closed degrades significantly in REP 33% 67% 10% 6% 53% 79% 31% 9% the presence of forwarding, while the performance of open SEQ 55% 90% 153% 145% 86% 259% 185% 118% solution improves. Forward RND 78% 27% 18% 6% 88% 31% 24% 10% 4.3 Comparison with OCaml and Haskell Lnx Win Lnx Win We now compare our solution to the built-in pattern-matching Figure 4. Relative performance of type switching versus facility of OCaml [28] and Haskell [25]. In this test, we visitors. Numbers in regular font (e.g. 67%), indicate that timed small OCaml and Haskell applications performing our our type switching is faster than visitors by corresponding sequential benchmark on an algebraic data type of 100 vari- percentage. Numbers in bold font (e.g. 18%), indicate that ants. Corresponding C++ applications were working with a visitors are faster by corresponding percentage. flat class hierarchy of 100 derived classes. The difference be-

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 14 2012/4/13 tween the C++ applications lies in the encoding used. Kind PARENTS CHILDREN LIBRARY LANGUAGE CLASSES PATHS HEIGHT ROOTS LEAFS BOTH encoding is the same as Tag encoding, but it does not require AVG MAX AVG MAX substitutability, and thus can be implemented with a direct DG2 SMALLTALK 534 534 11 2 381 1 1 1 3.48 59 DG3 SMALLTALK 1356 1356 13 2 923 1 1 1 3.13 142 switch on tags. It is only supported through specialized syn- ET+ C++ 370 370 8 87 289 79 1 1 3.49 51 tax in our library as it differs from the Tag encoding only GEO EIFFEL 1318 13798 14 1 732 0 1.89 16 4.75 323 semantically. JAV JAVA 604 792 10 1 445 0 1.08 3 4.64 210 LOV EIFFEL 436 1846 10 1 218 0 1.72 10 3.55 78 We used optimizing OCaml compiler ocamlopt.opt NXT OBJECTIVE-C 310 310 7 2 246 1 1 1 4.81 142 version 3.11.0 working under the Visual C++ toolset as SLF SELF 1801 36420 17 51 1134 0 1.05 9 2.76 232 well as the Glasgow Haskell Compiler version 7.0.3 (with UNI C++ 613 633 9 147 481 117 1.02 2 3.61 39 -O switch) working under the MinGW toolset. All the tests VA2 ?? 3241 3241 14 1 2582 0 1 1 4.92 249 VA2 ?? 2320 2320 13 1 1868 0 1 1 5.13 240 were performed on the Windows 7 laptop. The timing results VW1 SMALLTALK 387 387 9 1 246 0 1 1 2.74 87 presented in Figure 5 are averaged over 101 measurements VW2 SMALLTALK 1956 1956 15 1 1332 0 1 1 3.13 181 and show the number of seconds it took to perform a million OVERALLS 15246 63963 17 298 10877 199 1.11 16 3.89 323 decompositions within our sequential benchmark. Figure 6. Benchmarks Class Hierarchies

children were computed only among the classes having at least one parent or at least one children correspondingly. With few useful exceptions, it generally makes sence to only apply type switch to non-leaf nodes of the class hierarchy. 71% of the classes in the entire benchmarks suite were leaf classes. Out of the 4369 non-leaf classes 36% were spawning a subhierarchy of only 2 classes (including the Figure 5. Performance comparison of various encodings root), 15% – a subhierarchy of 3 classes, 10% of 4, 7% of 5 and syntax against OCaml code and so forth. Turning this into a cumulative distribution, a% of subhierarchies had more than b classes in them, where: a 1% 3% 5% 10% 20% 25% 50% 64% 100% 4.4 Dealing with real-world class hierarchies b 700 110 50 20 10 7 3 2 1 We used a class hierarchy benchmark used before to study These numbers reflect the percentage of use cases one may efficiency of type inclusion testing and dispatching tech- expect in the real word that have a given number of case niques [14, 26, 44, 51]. We use the names of the benchmarks clauses in them. from Vitek et al [44, Table 2], since the set of benchmarks For each non-leaf class A we created a function perform- we were working with was closest to that work. ing a type switch on every possible derived class Di of it While not all class hierarchies originated from C++, for as well as itself. The function was then executed with every this experiment it was more important for us that the hi- possible subobject Di * σj ( A it can possibly be applied erarchies were man-made. While converting the hierachies to, given the static type A of the subject. It was executed into C++, we had to prune inaccessible base classes (di- multiple but the same number of times on each subobject to rect base class that is already an indirect base class) when ensure uniformity on one side (since we do not have the data used with repeated inheritance in order to satisfy semantic about the actual probabilities of each subobject the bench- requirements of the C++. We maintained the same number mark hierarchies) as well as let the type switch infer the op- of virtual functions present in each class as well as the num- timal parameters k and l of its cache indexing function Hkl. ber of data members. The benchmarks, however, did not pre- We then plotted the result of each of the 4396 experiments as serve the types of those. The data in Figure 6 shows various a point in chart of Figure 7 relating the optimal probability parameters of the class hierarchies in each benchmark, after of conflict p achieved by the type switch and the number of their adoption to C++. subobjects n that came through that type switch. To account The number of paths represents the number of distinct for the fact that multiple experiments could have resulted in inheritance paths from the classes in the hierarchy to the the same pair (n, p), we use a shadow of each point to reflect roots of the hiearchy. As we showed in §?? this number somewhat the number of experiments yielding it. reflects the number of possible subobjects in the hierarchy. The curves on which the results of experiments line up The roots listed in the table are classes with no base classes. correspond to the fact that under uniform distribution of n We will subsequently use the term non-leaf to refer to the subobjects, only a finite number of different values repre- possible root of a subhierarchy. Leafs are classes with no senting the probability of conflict p are possible. In particu- m children, while both refers to utility classes that are both lar, all such values p = n , where 0 ≤ m < n. The number roots and leafs and thus neiter have base nor derived classes. m reflects the number of subobjects an optimal cache index- The average for the number of parents and the number of ing function Hkl could not allocate their own entry for. We

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 15 2012/4/13 ing we needed them to come after. Redundancy checking 0.40 support provided by our library was invaluable in finding out all such cases. 0.35 Both pretty printers were executed on a set of header files 0.30 from the C++ standard library and the produced output of P both program was byte-to-byte the same. We timed execu- 0.25 tion of the pretty printing phase (not including loading and 0.20 termination of the application or parsing of the input file)

Probability of Conflict and observed that on small files (e.g. those from C run-time 0.15 library and few small C++ files) visitors-based implementa- 0.10 tion was faster because the total number of nodes in AST and thus calls did not justify our set-up calls. In particular, 0.05 visitor-based implementation of pretty printer was faster on 0.00 files of 44–588 lines of code, with average 136 lines per 1 2 4 8 16 32 64 128 256 512 1024 2048 Number of subobjects n those inputs, where visitors win. On these input files it is faster by 1.17%–21.42% with an average speed-up of 8.75%. Open type switch based implementation of pretty printer was Figure 7. Probability of Conflict vs. Number of Subobjects faster on files of 144–9851 lines of code, with average 3497 in Hierarchy lines per those input files, where open type switch wins. On these inputs it is faster by 0.18% – 32.99% with an average showed in §3.8 that the probability of conflict under uniform speed-up of 5.53%. distribution of n subobjects depends only on m. The points Figure 8 shows memory usage as well as cache hits and on the same curve (which becomes a line on a log-log plot) misses for the run of our pretty printer on ’queue’ standard all share the same number m of “extra” vtbl-pointers that op- library header (it has the largest LOC after preprocessing in timal cache indexing function could not allocate individual our test set). entries for. While it is hard to see from the chart, 87.5% of all the points on the chart lay on the X-axis, which means that the optimal hash function for the corresponding type switches had no conflicts at all. In other words, only in 12.5% of V cases the optimal Hkl for the set of vtbl-pointers V com- ing through a given type switch had non-zero probability of conflict. This is why the average probability of conflict for the entire set is only 1.17. Experiments laying on the first curve amount to 5.58% of subhierarchies and represent V the cases in which optimal Hkl had only one “extra” vtbl- V pointer. 2.63% experiments had Hkl with 2 conflicts, 0.87% with 3 etc. These numbers do not indicate that the hash func- tion we used is better than other hash functions, they do in- dicate instead that the set of vtbl-pointers present in many Figure 8. Memory usage in real application applications is particularly suitable for such a hash function. The bars represent the total amount of memory in bytes 4.5 Refactoring an existing visitors based application each of the 8 match statements (marked A-H) used. The For this experiment we have reimplemented a visitor based info [N~M] next to the letter indicates the actual number of C++ pretty printer for Pivot[39] using our pattern-matching different subobjects (i.e. vtbl-pointers) N that came through library. Pivot’s class hierarchy consists of 154 node kinds that match statement, and the number of case clauses M the representing various entities in the C++ program. The orig- match statement had (the library uses it as an estimate of inal code had 8 visitor classes each handling 5, 7, 8, 10, 15, N). N is also the number of cases the corresponding match 17, 30 and 63 cases, which we turned into 8 match state- statement had to be executed sequentially (instead of a direct ments with corresponding numbers of case clauses. Most jump). of the rewrite was performed by sed-like replaces that con- The blue part of each bar corresponds to the memory verted visit methods into respective case-clauses. In several used by cache, while the maroon – to the memory used by cases we had to manually reorder case-clauses to avoid re- the hash table. Transparent parts of both colors indicate the dundancy as visit-methods for base classes were typically allocated extra memory that is not holding any data. The coming before the same for derived, while for type switch- black box within the blue part also indicates the amount

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 16 2012/4/13 of entries in the cache that are allocated for only one vtbl Our memoization device can be used to implement pattern pointer and thus never result in a cache miss. The non- matching on polymorphic variants. transparent part without black box represents the percentage Tom is a pattern-matching compiler that can be used to- of vtbl-pointers that have to share their cache entry with at gether with Java, C or Eiffel to bring a common pattern least one other vtbl-pointer and thus may result in collisions matching and term rewriting syntax into the languages [34]. during access. In comparison to our approach, Tom has much bigger goals: The actual number of hits and misses for each of the the combination of , term rewriting and match statements is indicated on top of the corresponding strategies turns Tom into a fully fledged tree-transformation column. The sum of them is the total amount of calls made. language. Its type patterns and %match statement can be Hits indicate situation when we found entry in cache and used as a type switch; however, Tom’s handling of type didn’t have to make roundtrip to the hash-table to get it. switching is based on decision trees and an instanceof-like Misses indicate the number of cases during actual run we predicate, which are inefficient. had to pick the entry from the hash table and update the Pattern matching in Scala [35] also supports type switch- cache with it. The number of misses is always larger then ing through type patterns. The language supports extensi- or equal to N. ble and hierarchical data types, but their handling in a type The library always preallocates memory for at least 8 sub- switching constructs varies. Sealed classes are handled with objects to avoid unnecessery recomputations of optimal pa- an efficient switch over all tags, while extensible classes are rameters k and l – this is the case with the last 3 match similarly approached with a combination of an InstanceOf statements. In all other cases it allocate the amount of mem- operator and a decision tree [16]. ory proportional to the smallest power of 2 that is greater of equal than max(M,N). The table does not have to be hash 6. Conclusions and Future Work table and can be implemented with any other container i.e. sorted vector, map etc. that let us find quickly by a given Type switching is an open alternative to visitor design pat- vtbl-pointer the data associated with it. If we implement it tern that overcomes the restrictions, inconveniences, and dif- with sorted vector, the red part will shrink to only the non- ficulties in teaching and using, typically associated with it. transparent part. Our implementation of it comes close or outperforms the visitor design pattern, which is true even in a library setting using a production-quality compiler, where the performance base-line is already very high. 5. Related Work In the future we would like to provide a compiler imple- Extensible Visitors with Default Cases [49, §4.2] attempt to mentation of our technique, which will enable a better sur- solve the extensibility problem of visitors; however, the solu- face syntax, improved diagnostics, increased performance tion has problems of its own. The visitation interface hierar- and many other benefits hard to achieve in a library imple- chy can easily be grown linearly, but independent extensions mentation. by different authorities require developer’s intervention. On top of the double dispatch the solution will incur two ad- Acknowledgments ditional virtual calls and a dynamic cast for each level of We would like to thank Xavier Leroy and Luc Maranget for visitor extension. The solution is simpler with virtual inher- valuable feedback and suggestions for improvements on the itance, which adds even more indirections. initial idea, Gregory Berkolaiko for ideas related to mini- Loh¨ and Hinze proposed to extend Haskell’s type system mization of conflicts, Jaakko Jarvi for assistance in compar- with open data types and open functions [31]. The solution ison to other languages, Andrew Sutton, Peter Pirkelbauer allows top-level data types and functions to be marked as and Abe Skolnik for helpful discussions and comments to open with concrete variants and overloads defined anywhere numerous rewrites of this paper. We also benefitted greatly in the program. The semantics of open extension is given from insightful comments by anonymous reviewers on ear- by transformation into a single module, which assumes a lier revisions of this work. We would also like to thank Karel whole-program view and thus is not an open solution un- Driesen for letting us use his class hierarchies benchmark for fortunately. Besides, open data types are extensible but not this work. hierarchical, which avoids the problems discussed here. Polymorphic variants in OCaml [19] allow the addition of new variants as well as define subtyping on them. The sub- References typing, however, is not defined between the variants, but be- [1] clang: a C language family frontend for LLVM. tween combinations of them. This maintains disjointness be- http://clang.llvm.org/, 2007. tween values from different variants and makes an important [2] A. Appel, L. Cardelli, K. Fisher, C. Gunter, R. Harper, distinction between extensible sum types like polymorphic X. Leroy, M. Lillibridge, D. B. MacQueen, J. Mitchell, variants and extensible hierarchical sum types like classes. G. Morrisett, J. H. Reppy, J. G. Riecke, Z. Shao, and C. A.

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 17 2012/4/13 Stone. Principles and a preliminary design for ML2000. Oct. 2008. ISSN 0164-0925. doi: 10.1145/1391956.1391960. March 1999. URL http://doi.acm.org/10.1145/1391956.1391960. [3] L. Augustsson. Compiling pattern matching. In [15] M. A. Ellis and B. Stroustrup. The annotated C++ refer- Proc. of a conference on Functional programming ence manual. Addison-Wesley Longman Publishing Co., Inc., languages and computer architecture, pages 368– Boston, MA, USA, 1990. ISBN 0-201-51459-1. 381, New York, NY, USA, 1985. Springer-Verlag [16] B. Emir. Object-oriented pattern matching. PhD thesis, New York, Inc. ISBN 3-387-15975-4. URL Lausanne, 2007. http://dl.acm.org/citation.cfm?id=5280.5303. [17] M. D. Ernst, C. S. Kaplan, and C. Chambers. Predicate [4] Blind Review. Technical report submitted as supplementary dispatching: A unified theory of dispatch. In ECOOP ’98, the material. Technical Report XXXX, University, Nov. 2011. 12th European Conference on Object-Oriented Programming, [5] L. Cardelli. Compiling a functional language. In pages 186–211, Brussels, Belgium, July 20-24, 1998. Proc. of the 1984 ACM Symposium on LISP and func- [18] E. Gamma, R. Helm, R. E. Johnson, and J. M. Vlissides. De- tional programming, LFP ’84, pages 208–217, New sign patterns: Abstraction and reuse of object-oriented design. York, NY, USA, 1984. ACM. ISBN 0-89791-142- In Proceedings of the 7th European Conference on Object- 3. doi: http://doi.acm.org/10.1145/800055.802037. URL Oriented Programming, ECOOP ’93, pages 406–431, Lon- http://doi.acm.org/10.1145/800055.802037. don, UK, UK, 1993. Springer-Verlag. ISBN 3-540-57120-5. [6] L. Cardelli, J. Donahue, M. Jordan, B. Kalsow, and [19] J. Garrigue. Programming with polymorphic variants. In ACM G. Nelson. The modula3 type system. In Pro- Workshop on ML, Sept. 1998. ceedings of the 16th ACM SIGPLAN-SIGACT sympo- sium on Principles of programming languages, POPL [20] M. Gibbs and B. Stroustrup. Fast dynamic casting. Softw. ’89, pages 202–212, New York, NY, USA, 1989. ACM. Pract. Exper., 36:139–156, February 2006. ISBN 0-89791-294-2. doi: 10.1145/75277.75295. URL [21] N. Glew. Type dispatch for named hierarchical types. In Pro- http://doi.acm.org/10.1145/75277.75295. ceedings of the fourth ACM SIGPLAN international confer- [7] Y. Caseau. Efficient handling of multiple inheri- ence on Functional programming, ICFP ’99, pages 172–182, tance hierarchies. In Proceedings of the eighth an- New York, NY, USA, 1999. nual conference on Object-oriented programming sys- [22] C. Grothoff. Walkabout revisited: The runabout. tems, languages, and applications, OOPSLA ’93, pages In ECOOP 2003 - Object-Oriented Programming, 271–287, New York, NY, USA, 1993. ACM. ISBN pages 103–125. Springer-Verlag, 2003. URL 0-89791-587-9. doi: 10.1145/165854.165905. URL http://grothoff.org/christian/runabout/runabout.pdf. http://doi.acm.org/10.1145/165854.165905. [23] R. Harper and G. Morrisett. Compiling polymor- [8] CodeSourcery, Compaq, EDG, HP, IBM, Intel, Red phism using intensional type analysis. In Proceed- Hat, and SGI. Itanium C++ ABI, March 2001. ings of the 22nd ACM SIGPLAN-SIGACT symposium on http://www.codesourcery.com/public/cxx-abi/. Principles of programming languages, POPL ’95, pages [9] N. H. Cohen. Type-extension type test can be 130–141, New York, NY, USA, 1995. ACM. ISBN performed in constant time. ACM Trans. Pro- 0-89791-692-1. doi: 10.1145/199448.199475. URL gram. Lang. Syst., 13(4):626–629, Oct. 1991. ISSN http://doi.acm.org/10.1145/199448.199475. 0164-0925. doi: 10.1145/115372.115297. URL [24] International Organization for Standardization. http://doi.acm.org/10.1145/115372.115297. ISO/IEC 14882:2011: Programming languages: [10] W. R. Cook. Object-oriented programming versus abstract C++. Geneva, Switzerland, 3rd edition, 2011. URL data types. In Proceedings of the REX School/Workshop on http://www.iso.org/iso/iso catalogue/catalogue tc/catalogue detail.htm?csnumber=50372. Foundations of Object-Oriented Languages, pages 151–178, [25] S. P. Jones, editor. Haskell 98 Language and Libraries – London, UK, 1991. Springer-Verlag. ISBN 3-540-53931-X. The Revised Report. Cambridge University Press, Cambridge, [11] O.-J. Dahl. SIMULA 67 common base language, (Norwegian England, 2003. Computing Center. Publication). 1968. ISBN B0007JZ9J6. [26] A. Krall, J. Vitek, and R. N. Horspool. Near optimal hierar- [12] J. Dean, G. DeFouw, D. Grove, V. Litvinov, and C. Cham- chical encoding of types. In In Proc. European Conference on bers. Vortex: an for object-oriented Object-Oriented Programming, ECOOP’97, Lecture Notes in languages. In Proceedings of the 11th ACM SIG- Computer Science, pages 128–145. Springer-Verlag, 1997. PLAN conference on Object-oriented programming, sys- [27] S. Krishnamurthi, M. Felleisen, and D. Friedman. Synthesiz- tems, languages, and applications, OOPSLA ’96, pages ing object-oriented and functional design to promote re-use. 83–100, New York, NY, USA, 1996. ACM. ISBN In E. Jul, editor, ECOOP’98 - Object-Oriented Programming, 0-89791-788-X. doi: 10.1145/236337.236344. URL volume 1445 of Lecture Notes in Computer Science, pages http://doi.acm.org/10.1145/236337.236344. 91–113. Springer Berlin / Heidelberg, 1998. [13] E. W. Dijkstra. Guarded commands, non-determinacy and [28] F. Le Fessant and L. Maranget. Optimizing pattern matching. formal derivation of programs. Jan. 1975. In Proceedings of the sixth ACM SIGPLAN international con- [14] R. Ducournau. Perfect hashing as an almost perfect subtype ference on Functional programming, ICFP ’01, pages 26–37, test. ACM Trans. Program. Lang. Syst., 30(6):33:1–33:56, New York, NY, USA, 2001. ACM. ISBN 1-58113-415-0.

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 18 2012/4/13 [29] B. Liskov. Keynote address - data abstraction and hierarchy. [41] L. Schubert, M. Papalaskaris, and J. Taugher. De- In OOPSLA ’87: Addendum to the proceedings on Object- termining type, part, color, and time relationships. oriented programming systems, languages and applications Computer, 16:53–60, 1983. ISSN 0018-9162. doi: (Addendum), pages 17–34, New York, NY, USA, 1987. ACM http://doi.ieeecomputersociety.org/10.1109/MC.1983.1654198. Press. ISBN 0-89791-266-7. [42] D. A. Spuler. Compiler Code Generation for Multiway Branch [30] B. Liskov, R. R. Atkinson, T. Bloom, E. B. Moss, R. Schaffert, Statements as a Static Search Problem. Technical Report and A. Snyder. Clu reference manual. Technical report, Technical Report 94/03, James Cook University, Jan. 1994. Cambridge, MA, USA, 1979. [43] Tom Duff. Duff’s Device, Aug 1988. [31]A.L oh¨ and R. Hinze. Open data types and open functions. In http://www.lysator.liu.se/c/duffs-device.html. Proceedings of the 8th ACM SIGPLAN international confer- [44] J. Vitek, R. N. Horspool, and A. Krall. Efficient type ence on Principles and practice of declarative programming, inclusion tests. In Proceedings of the 12th ACM SIG- PPDP ’06, pages 133–144, New York, NY, USA, 2006. ACM. PLAN conference on Object-oriented programming, sys- tems, languages, and applications, OOPSLA ’97, pages [32] Microsoft Research. Phoenix compiler and 142–157, New York, NY, USA, 1997. ACM. ISBN shared source common language infrastructure. 0-89791-908-4. doi: 10.1145/263698.263730. URL http://research.microsoft.com/phoenix/, 2005. http://doi.acm.org/10.1145/263698.263730. [33] R. Milner, M. Tofte, and R. Harper. The Definition of Stan- [45] D. Vytiniotis, G. Washburn, and S. Weirich. An open and shut dard ML. MIT Press, Cambridge, MA, USA, 1990. ISBN typecase. In Proceedings of the 2005 ACM SIGPLAN interna- 0262132559. tional workshop on Types in languages design and implemen- [34] P.-E. Moreau, C. Ringeissen, and M. Vittek. A pattern match- tation, TLDI ’05, pages 13–24, New York, NY, USA, 2005. ing compiler for multiple target languages. In Proceedings ACM. ISBN 1-58113-999-3. doi: 10.1145/1040294.1040296. of the 12th international conference on Compiler construc- URL http://doi.acm.org/10.1145/1040294.1040296. tion, CC’03, pages 61–76, Berlin, Heidelberg, 2003. Springer- [46] P. Wadler. The expression problem. Mail to the java-genericity Verlag. ISBN 3-540-00904-3. mailing list, November 1998. [35] M. Odersky, V. Cremet, I. Dragos, G. Dubochet, B. Emir, [47] D. Wasserrab, T. Nipkow, G. Snelting, and F. Tip. An op- S. Mcdirmid, S. Micheloud, N. Mihaylov, M. Schinz, E. Sten- erational semantics and type safety prooffor multiple inheri- man, L. Spoon, and M. Zenger. An overview of the scala tance in c++. In Proceedings of the 21st annual ACM SIG- programming language (second edition. Technical Report PLAN conference on Object-oriented programming systems, LAMP-REPORT-2006-001, Ecole Polytechnique Federale de languages, and applications, OOPSLA ’06, pages 345–362, Lausanne, 2006. New York, NY, USA, 2006. ACM. [36] B. C. Oliveira, M. Wang, and J. Gibbons. The visitor pattern [48] N. Wirth. Type extensions. ACM Trans. Pro- as a reusable, generic, type-safe component. In Proceedings gram. Lang. Syst., 10(2):204–214, Apr. 1988. ISSN of the 23rd ACM SIGPLAN conference on Object-oriented 0164-0925. doi: 10.1145/42190.46167. URL programming systems languages and applications, OOPSLA http://doi.acm.org/10.1145/42190.46167. ’08, pages 439–456, New York, NY, USA, 2008. ACM. ISBN [49] M. Zenger and M. Odersky. Extensible algebraic datatypes 978-1-60558-215-3. with defaults. In Proceedings of the sixth ACM SIGPLAN [37] J. Palsberg and C. B. Jay. The essence of the visi- international conference on Functional programming, ICFP tor pattern. In Proceedings of the 22nd International ’01, pages 241–252, New York, NY, USA, 2001. ACM. Computer Software and Applications Conference, COMP- [50] M. Zenger and M. Odersky. Independently extensible solu- SAC ’98, pages 9–15, Washington, DC, USA, 1998. tions to the expression problem. In Proc. FOOL 12, Jan. 2005. IEEE Computer Society. ISBN 0-8186-8585-9. URL [51] Y. Zibin and J. Y. Gil. Efficient subtyping tests with http://dl.acm.org/citation.cfm?id=645980.674267. pq-encoding. In Proceedings of the 16th ACM SIG- [38] T. Ramananandro, G. Dos Reis, and X. Leroy. Formal ver- PLAN conference on Object-oriented programming, sys- ification of object layout for c++ multiple inheritance. In tems, languages, and applications, OOPSLA ’01, pages Proceedings of the 38th annual ACM SIGPLAN-SIGACT sym- 96–107, New York, NY, USA, 2001. ACM. ISBN posium on Principles of programming languages, POPL ’11, 1-58113-335-9. doi: 10.1145/504282.504290. URL pages 67–80, New York, NY, USA, 2011. ACM. http://doi.acm.org/10.1145/504282.504290. [39] G. D. Reis and B. Stroustrup. A principled, complete, and efficient representation of C++. In Proc. Joint Conference of ASCM 2009 and MACIS 2009, volume 22 of COE Lecture Notes, pages 407–421, December 2009. [40] J. G. Rossie, Jr. and D. P. Friedman. An algebraic semantics of subobjects. In Proceedings of the tenth annual conference on Object-oriented programming systems, languages, and appli- cations, OOPSLA ’95, pages 187–199, New York, NY, USA, 1995. ACM.

Y.Solodkyy, G.Dos Reis, B.Stroustrup: Open and Efficient Type Switch for C++ 19 2012/4/13