Mixin-Based Programming in C++1
Yannis Smaragdakis Don Batory College of Computing Department of Computer Sciences Georgia Institute of Technology The University of Texas at Austin Atlanta, GA 30332 Austin, Texas 78712 [email protected] [email protected]
Abstract reflect the structure of the design. At the same time, unnecessary dynamic binding can be elimi- Combinations of C++ features, like inheritance, nated, resulting into more efficient implementa- templates, and class nesting, allow for the expres- tions. Unfortunately, this method resulted in very sion of powerful component patterns. In particular, complex parameterizations, causing its inventors to research has demonstrated that, using C++ mixin question its scalability. classes, one can express layered component-based designs concisely with efficient implementations. The mixin layers technique was invented to address In this paper, we discuss pragmatic issues related to these concerns. Mixin layers are mixin classes component-based programming using C++ mixins. nested in a pattern such that the parameter (super- We explain surprising interactions of C++ features class) of the outer mixin determines the parameters and policies that sometimes complicate mixin (superclasses) of inner mixins. In previous work implementations, while other times enable addi- [4][25][26], we showed how mixin layers solve the tional functionality without extra effort. scalability problems of the VanHilst and Notkin method and result into elegant implementations of 1Introduction collaboration-based designs.
Large software artifacts are arguably among the This paper discusses practical issues related to most complex products of human intellect. The mixin-based programming. We adopt a viewpoint complexity of software has led to implementation oriented towards C++ implementations, but our methodologies that divide a problem into manage- discussion is not geared towards the C++ expert. able parts and compose the parts to form the final Instead, we aim to document common problems product. Several research efforts have argued that and solutions in C++ mixin writing for the casual C++ templates (a powerful parameterization mech- programmer. Additionally, we highlight issues that anism) can be used to perform this division ele- pertain to language design in general (e.g., to Java gantly. parameterization or to the design of future lan- guages). Most of the issues clearly arise from the In particular, the work of VanHilst and Notkin interaction of C++ features with the constructs [30][31][32] showed how one can implement col- under study. The discussion mainly stems from laboration-based (or role-based) designs using a actual experience with C++ mixin-based imple- certain templatized class pattern, known as a mixin mentations but a few points are a result of close class (or just mixin). Compared to other techniques examination of the C++ standard, since they refer (e.g., a straightforward use of application frame- to features that no compiler we have encountered works [17]) the VanHilst and Notkin method yields implements. Even though we present an introduc- less redundancy and reusable components that tion to mixins, mixin layers, and their uses, the pri-
1. We gratefully acknowledge the sponsorship of Microsoft Research, the Defense Advanced Research Projects Agency (Cooper- ative Agreement F30602-96-2-0226), and the University of Texas at Austin Applied Research Laboratories.
1 mary purpose of this paper is not to convince To give an example, consider a mixin implement- readers of the value of these constructs. (The ing operation counting for a graph. Operation reader should consult [4], [25], [26], [27], or [30] counting means keeping track of how many nodes for that.) and edges have been visited during the execution of a graph algorithm. (This simple example is one We believe that the information presented here rep- of the non-algorithmic refinements to algorithm resents a valuable step towards moving some pow- functionality discussed in [34]). The mixin could erful programming techniques into the mainstream. have the form: We found that the mixin programming style is quite practical, as long as one is aware of the possi- template
2 collaboration. The mixin-based approach of Van- laboration are represented by inner classes of the Hilst and Notkin results in efficient implementa- layer. Inheritance works at two different levels. tions of role-based designs with no redundancy. First, a layer can inherit entire classes from its Sometimes, however, the resulting parameteriza- superclass (i.e., the parameter of the layer). Sec- tion code is quite complicated (i.e., many mixins ond, inner classes inherit members (variables, need to be composed with others in a complex methods, or even other classes) from the corre- fashion). This introduces some scalability prob- sponding inner classes in the superclass layer. This lems (namely, extensions that instantiate template dual application of inheritance simplifies the parameters can be of length exponential to the implementation of collaboration-based designs, number of mixins composed—see [25]). To make while preserving the benefits of the VanHilst and the approach more practical, by reducing its com- Notkin method. An important source of simplifica- plexity, mixin layers were introduced. Because tions is that inner classes of a mixin layer can refer mixin layers are an incremental improvement of unambiguously to other inner classes—the layer the VanHilst and Notkin method, we only discuss acts as a namespace. implementing collaboration-based designs using mixin layers. We illustrate our point with an example (presented in detail in [25]) of a collaboration-based design Mixin layers [25][26][27] are a particular form of and its mixin layers implementation. (Full source mixins. They are designed with the purpose of code is available, upon request.) This example pre- encapsulating refinements for multiple classes. sents a graph traversal application and was exam- Mixin layers are nested mixins such that the ined initially by Holland [16] and subsequently by parameter of an outer mixin determines the param- VanHilst and Notkin [30]. This application defines eters of inner mixins. The general form of a mixin three different algorithms on an undirected graph, layer in C++ is: all implemented using a depth-first traversal: Ver- tex Numbering numbers all nodes in the graph in template
3 Object Classes Graph Vertex Workspace Undirected Graph- VertexWith- Graph Undirected Adjacencies
Depth First GraphDFT VertexDFT Traversal Workspace- Vertex VertexNumber Numbering Number Workspace- Cycle GraphCycle VertexCycle Checking Cycle Collaborations (Layers) Connected Graph- Vertex- Workspace- Region Connected Connected Connected
Figure 1: Collaboration decomposition of the example application: A depth-first traversal of an undirected graph is specialized to yield three different graph operations. Ovals represent collab- orations, rectangles represent classes, and their intersections represent roles.
“Undirected Graph” collaboration supports storing As shown in [25], such components are flexible and retrieving a set of vertices. The role of the and can be reused and interchanged. For instance, same object in the “Depth First Traversal” collabo- the following composition builds Graph, Vertex, ration implements a part of the actual depth-first and WorkSpace classes nested inside class CycleC traversal algorithm. that implement vertex numbering of undirected graphs using a depth-first traversal:2 By implementing collaborations as mixin layers, the modular design of Figure 1 can be maintained typedef DFT < NUMBER < DEFAULTW < at the implementation level. For instance, the “Ver- UGRAPH>>>CycleC; tex Numbering” collaboration can be implemented using a layer of the general form: By replacing NUMBER with other mixin layers we get the other two graph algorithms discussed. template
Note that no role (nested class) is prescribed for 2. The DEFAULTW mixin layer is an implementation detail, bor- Graph.AGraph class is inherited from the super- rowed from the VanHilst and Notkin implementation [30]. class of Number (the class denoted by parameter It contains an empty WorkSpace class and its purpose is to Next). avoid dynamic binding by changing the order of composi- tion.
4 layer. Hence, it is straightforward to apply the same checked (code will only be produced for methods algorithms to a directed graph (mixin layer DGRAPH actually referenced in the object code). This means interchanged for UGRAPH):3 that certain errors (including type mismatches and references to undeclared methods) can only be typedef DFT < NUMBER < DEFAULTW < detected with the right template instantiations and DGRAPH > > > NumberC; method calls. Consider the following example:
This technique (of composing source components template
Since little has been written about the pragmatics If client code never calls method sort,thecom- of doing component programming using C++ mix- piler will not catch the misspelled identifier above. ins (mixin classes or mixin layers), we feel it is This is true even if the ErrorMixin template is necessary to discuss some pertinent issues. Most of used to create classes, and methods other than sort the points raised below concern fine interactions are invoked on objects of those classes. between the mixin approach and C++ idiosyncra- sies. Others are implementation suggestions. They Delaying the instantiation of methods in template are all useful knowledge before one embarks into a classes can be used to advantage, as we will see development effort using C++ mixins and could later. Nevertheless, many common designs are serve to guide design choices for future parameter- such that all member methods of a template class ization mechanisms in programming languages. should be valid for all instantiations. It is not The C++ aspects we discuss are well-documented straightforward to enforce the latter part (“for all and other C++ programmers have probably also instantiations”) but for most practical purposes made some of our observations. Nevertheless, we checking all methods for a single instantiation is believe that most of them are non-obvious and enough. This can be done by explicit instantiation many only arise in the context of component pro- of the template class, which forces the instantiation gramming—that is, when a mixin is designed and of all its members. The idiom for explicit instantia- used in complete isolation from other components tion applied to our above example is: of the system. template class ErrorMixin
Lack of template type-checking. Templates do When “subtype of” does not mean “substitut- not correspond to types in the C++ language. Thus, able for”. There are two instances where inherit- they are not type-checked until instantiation time ance may not behave the way one might expect in (that is, composition time for mixins). Further- C++. First, constructor methods are not inherited. more, methods of templatized classes are them- Ellis and Stroustrup ([13], p.264) present valid rea- selves considered function templates.4 Function sons for this design choice: the constructor of a templates in C++ are instantiated automatically and superclass does not suffice for initializing data only when needed. Thus, even after mixins are members added by a subclass. Often, however, a composed, not all their methods will be type- mixinclassmaybeusedonlytoenrichoradaptthe method interface of its superclasses without adding 3. This is under the assumption that the algorithms are still data members. In this case it would be quite rea- valid for directed graphs as is the case with the original sonable to inherit a constructor, which, unfortu- code for this example [16]. nately, is not possible. The practical consequence 4. This wording, although used by the father of C++—see [29], p.330—is not absolutely accurate since there is no of this policy is that the only constructors that are automatic type inference. visible in the result of a mixin composition are the
5 ones present in the outer-most mixin (bottom-most ment of this type). Once this is done, the function class in the resulting inheritance hierarchy). To generated by the template can be invoked with make matters worse, constructor initialization lists actual arguments that are subtypes of the corre- (e.g., sponding formal argument types. That is, the fol- constr() : init1(1,2), init2(3) {} ) lowing example (using weird_function, above) is can only be used to initialize direct parent classes. valid code: In other words, all classes need to know the inter- face for the constructor of their direct superclass (if Base base; Mixin
A possible workaround for this problem is to use a One may question whether the problem arises in a standardized construction interface. A way to do practical setting. Indeed, we encountered the prob- this is by creating a construction class encoding the lem when attempting to build a set of mixin com- union of all possible arguments to constructors in a ponents based on data structure classes from the hierarchy. Then a mixin “knows” little about its Standard Template Library (STL) [28]. The equal- direct superclass, but has dependencies on the ity operator for STL data structures (operator== union of the construction interfaces for all its possi- in C++) is itself a function template of the same ble parent classes. (Of course, another workaround form as weird_function, above. For instance, the is to circumvent constructors altogether by having equality operator for linked lists is defined as: separate initialization methods. This, however, template
6 Another reasonable approach would be to intro- intermediate layer may specify methods and let the duce an empty subclass: inner-most layer decide whether they are virtual or not. class Synonym : publicA>{}; As we recently found out, this technique was The first form has the advantage of preserving con- described first in [12]. structors of component A in the synonym. The sec- ond idiom is cleanly integrated into the language Single mixin for multiple uses. The lack of tem- (e.g., can be templatized, compilers create short plate type-checking in C++ can actually be benefi- link names for the synonym, etc.). Additionally, it cial in some cases. Consider two classes Base1 and can solve a common problem with C++ template- Base2 with very similar interfaces (except for a based programming: generated names (template few methods): instantiations) can be extremely long, causing compiler messages to be incomprehensible. struct Base1 { void regular() {...} Designating virtual methods. Sometimes C++ ... policies have pleasant side-effects when used in }; struct Base2 { conjunction with mixins. An interesting case is that void weird() {...} of a mixin used to create classes where a certain ... // otherwise same interface as Base1 method can be virtual or not, depending on the }; concrete class used to instantiate the mixin. This is due to the C++ policy of letting a superclass Because of the similarities between Base1 and declare whether a method is virtual, while the sub- Base2, it makes sense to use a single mixin to adapt class does not need to specify this explicitly. Con- both. Such a mixin may need to have methods call- sider a regular mixin and two concrete classes ing either of the methods specific to one of the two instantiating it (a C++ struct isaclasswhose base classes. This is perfectly feasible. A mixin can members are public by default): be specified so that it calls either regular or weird: template
7 black tree and hash table of the SGI implementa- template
8 [33] in Generic Java [7] (an extension of Java with That is, the call to foo from method which_one parametric polymorphism). The origins of the tech- will refer to the global foo, not the foo method of nique reach back at least to the development of F- the Base superclass. bounded polymorphism (e.g., [8]). The main implication of these name resolution Hygienic templates in the C++ standard.The rules is on the way template-based programs C++ standard ([1], section 14.6) imposes several should be developed. In particular, imagine chang- rules for name resolution of identifiers that occur ing a correct class definition into a mixin definition inside templates. The extent to which current com- (by turning the superclass into a template parame- pilers implement these rules varies, but full con- ter). Even if the mixin is instantiated with its super- formance is the best approach to future class in the original code, the new program is not compatibility for user code. guaranteed to work identically to the original, because symbols may now be resolved differently. Although the exact rules are complicated, one can Thismaysurpriseprogrammerswhoworkbycre- summarize them (at loss of significant detail) as ating concrete classes and turning them into tem- “templates cannot contain code that refers to ‘non- plates when the need for abstraction arises. To local’ variables or methods”. Intuitively, “nonlo- avoid the potential for insidious bugs, it is a good cal” denotes variables or methods that do not practice to explicitly qualify references to super- depend on a template parameter and are not in class methods (e.g., Super::foo instead of just scope at the global point closest to the template foo). definition. This rule prevents template instantia- tions from capturing arbitrary names from their Compiler support. Most C++ compilers now have instantiation context, which could lead to behavior good support for parameterized inheritance (the not predicted by the template author.5 technique we used for mixins) and nested classes. We have encountered few problems and mostly A specific rule applies to mixin-based program- with older compilers when programming with C++ ming. To quote the C++ standard (14.6.2), “if a mixins. In fact, most of the compiler dependencies base class is a dependent type, a member of that are not particular to mixin-based programming but class cannot hide a name declared within a tem- concern all template-based C++ programs. These plate, or a name from the templates enclosing include limitations on the debugging support, error scopes”. Consider the example of a mixin calling a checking, etc. We will not discuss such issues as method defined in its parameter (i.e., the superclass they are time-dependent and have been presented of the class it will create when instantiated): before (e.g., [10]). Note, however, that mixin-based programming is not more complex than regular struct Base { template instantiation and typically does not exer- void foo() { ... } cise any of the “advanced” features of C++ tem- }; plates (type inference, higher-order templates, void foo() { } etc.). Overall, the compiler support issues involved in mixin-based programming are about the same as template
9 techniques but we will selectively mention two References approaches. [1] ANSI / ISO Standard: Programming Languages—C++, ISO/IEC 14882, 1998. The most prominent example is the various imple- mentations of the STL. Such implementations [2] J. Barton and L.R. Nackman, Scientific and often exercise the limits of template support and Engineering C++: An Introduction with reveal interactions of C++ policies with template- Advanced Techniques and Applications, based programming. Nevertheless, parameterized Addison-Wesley, 1994. inheritance is not a part of STL implementations. [3] D. Batory, V. Singhal, M. Sirkin, and J. Hence, the observations of this paper are mostly Thomas, “Scalable Software Libraries”, ACM distinct from the conclusions drawn from STL SIGSOFT 1993. implementation efforts. [4] D. Batory, R. Cardone, and Y. Smaragdakis, “Object-Oriented Frameworks and Product- Czarnecki and Eisenecker’s generative program- Lines”, 1st Software Product-Line ming techniques [10][11] were used in the Genera- Conference, Denver, Colorado, August 1999. tive Matrix Computation Library (GMCL). Their [5] K. Beck and W. Cunningham, “A Laboratory approach is a representative of techniques using for Teaching Object-Oriented Thinking”, C++ templates as a programming language (that is, OOPSLA 1989,1-6. to perform arbitrary computation at template instantiation time). What sets their method apart [6] G. Bracha and W. Cook, “Mixin-Based from other template meta-programming techniques Inheritance”, ECOOP/OOPSLA 90,303-311. is that it has similar goals to mixin-based program- [7] G. Bracha, M. Odersky, D. Stoutamire and P. ming. In particular, Czarnecki and Eisenecker try Wadler,“Makingthefuturesafeforthepast: to develop components which can be composed in Adding Genericity to the Java Programming multiple ways to yield a variety of implementa- Language”, OOPSLA 1998. tions. Several of the remarks in this paper are [8] P. Canning, W. Cook, W. Hill, W. Olthoff, and applicable to their method, even though their use of J. C. Mitchell, “F-bounded Polymorphism for mixins is different (for instance, they do not use Object-Oriented Programming”, in Proc. mixin layers). Conf. on Functional Programming Languages and Computer Architecture, 1989, 5 Conclusions 273-280. [9] J. Coplien, “Curiously Recurring Template We presented some pragmatic issues pertaining to Patterns”, C++ Report, 7(2):24-27, Feb. mixin-based programming in C++. We believe that 1995. mixin-based techniques are valuable and will [10] K. Czarnecki and U. Eisenecker. Generative become much more widespread in the future. Programming: Methods, Tools, and Mixin-based programming promises to provide Applications. Addison-Wesley, 2000. reusable software components that result into flexi- ble and efficient implementations. [11] K. Czarnecki and U. Eisenecker, “Synthesizing Objects”, ECOOP 1999,18- Previous papers have argued for the value of 42. mixin-based software components and their advan- [12] U. Eisenecker, “Generative Programming in tages compared to application frameworks. In this C++”, in Proc. Joint Modular Languages paper we tried to make explicit the engineering Conference (JMLC’97), LNCS 1204, considerations specific to mixin-based program- Springer, 1997, 351-365. ming in C++. Our purpose is to inform program- [13] M.A. Ellis and B. Stroustrup, The Annotated mers of the issues involved in order to help move C++ Reference Manual, Addison-Wesley, mixin-based programming into the mainstream. 1990.
10 [14] E. Gamma, R. Helm, R. Johnson, and J. [26] Y. Smaragdakis and D. Batory, Vlissides, Design Patterns: Elements of “Implementing Layered Designs with Mixin Reusable Object-Oriented Software. Layers”. In ECOOP 98. Addison-Wesley, 1994. [27] Y. Smaragdakis, “Implementing Large-Scale [15] R. Helm, I. Holland, and D. Gangopadhyay, Object-Oriented Components”, Ph.D. “Contracts: Specifying Behavioral Dissertation, Department of Computer Compositions in Object-Oriented Systems”. Sciences, University of Texas at Austin, OOPSLA 1990, 169-180. December 1999. [16] I. Holland, “Specifying Reusable [28] A. Stepanov and M. Lee, “The Standard Components Using Contracts”, ECOOP Template Library”. Incorporated in ANSI/ 1992, 287-308. ISO Committee C++ Standard. [17] R. Johnson and B. Foote, “Designing [29] B. Stroustrup, The C++ Programming Reusable Classes”, J. of Object-Oriented Language, 3rd Edition, Addison-Wesley, Programming, 1(2): June/July 1988, 22-35. 1997. [18] G. Kiczales, J. des Rivieres, and D. G. [30] M. VanHilst and D. Notkin, “Using C++ Bobrow, The Art of the Metaobject Protocol. Templates to Implement Role-Based MIT Press, 1991. Designs”. JSSST International Symposium on Object Technologies for Advanced Software, [19] E. Kohlbecker, D. P. Friedman, M. Felleisen, Springer-Verlag, 1996, 22-37. and B. Duba. “Hygienic Macro Expansion”. In Proc. of the SIGPLAN ‘86 ACM Conf. on [31] M. VanHilst and D. Notkin, “Using Role Lisp and Functional Programming, 151-161. Components to Implement Collaboration- Based Designs”. OOPSLA 1996. [20] O.L. Madsen, B. Møller-Pedersen, and K. Nygaard, Object-Oriented Programming in [32] M. VanHilst and D. Notkin, “Decoupling the BETA Programming Language. Addison- Change From Design”, ACM SIGSOFT 1996. Wesley, 1993. [33] P. Wadler, M. Odersky and Y. Smaragdakis, [21] D.A. Moon, “Object-Oriented Programming “Do Parametric Types Beat Virtual Types?”, with Flavors”, OOPSLA 1986. unpublished manuscript, posted in October 1998 in the Java Genericity mailing list [22] T. Reenskaug, E. Anderson, A. Berre, A. ([email protected]). Hurlen, A. Landmark, O. Lehne, E. Nordhagen, E. Ness-Ulseth, G. Oftedal, A. [34] K. Weihe, “A Software Engineering Skaar, and P. Stenslet, “OORASS: Seamless Perspective on Algorithmics”, available at Support for the Creation and Maintenance of http://www.informatik.uni-konstanz.de/ Object-Oriented Systems”, J. of Object- Preprints/ . Oriented Programming, 5(6): October 1992, 27-41. [23] Silicon Graphics Computer Systems Inc., STL Programmer’s Guide.See:http:// www.sgi.com/Technology/STL/ . [24] V. Singhal, A Programming Language for Writing Domain-Specific Software System Generators, Ph.D. Dissertation, Dep. of Computer Sciences, University of Texas at Austin, August 1996. [25] Y. Smaragdakis and D. Batory, “Implementing Reusable Object-Oriented Components”. In the 5th Int. Conf. on Software Reuse (ICSR 98).
11