Mixin-Based Programming in C++
Mixin-Based Programming in C++
1 Introduction
Large software artifacts are arguably among the most complex products of human
intellect. The complexity of software has led to implementation methodologies that
divide a problem into manageable parts and compose the parts to form the final prod-
uct. Several research efforts have argued that C++ templates (a powerful parameteriza-
tion mechanism) can be used to perform this division elegantly.
In particular, the work of VanHilst and Notkin [29][30][31] showed how one can
implement collaboration-based (or role-based) designs using a certain templatized
class pattern, known as a mixin class (or just mixin). Compared to other techniques
(e.g., a straightforward use of application frameworks [17]) the VanHilst and Notkin
method yields less redundancy and reusable components that reflect the structure of
the design. At the same time, unnecessary dynamic binding can be eliminated, result-
ing into more efficient implementations. Unfortunately, this method resulted in very
complex parameterizations, causing its inventors to question its scalability.
The mixin layers technique was invented to address these concerns. Mixin layers are
mixin classes nested in a pattern such that the parameter (superclass) of the outer
mixin determines the parameters (superclasses) of inner mixins. In previous work
[4][24][25], we showed how mixin layers solve the scalability problems of the Van-
Hilst and Notkin method and result into elegant implementations of collaboration-
based designs.
We believe that the information presented here represents a valuable step towards mov-
ing some powerful programming techniques into the mainstream. We found that the
mixin programming style is quite practical, as long as one is aware of the possible
interactions with C++ idiosyncrasies. As C++ compilers move closer to sophisticated
template support (e.g., some compilers already support separate template compilation)
the utility of such techniques will increase rapidly.
VanHilst and Notkin demonstrated that mixins are beneficial for a general class of
object-oriented designs [29]. They used a mixin-based approach to implement collabo-
ration-based (a.k.a. role-based) designs [5][15][16][21][29]. These designs are based
on the view that objects are composed of different roles that they play in their interac-
tion with other objects. The fundamental unit of functionality is a protocol for this
interaction, called a collaboration. The mixin-based approach of VanHilst and Notkin
results in efficient implementations of role-based designs with no redundancy. Some-
times, however, the resulting parameterization code is quite complicated—many mix-
ins need to be composed with others in a complex fashion. This introduces scalability
problems (namely, extensions that instantiate template parameters can be of length
exponential to the number of mixins composed—see [24]). To make the approach
more practical by reducing its complexity, mixin layers were introduced. Because
mixin layers are an incremental improvement of the VanHilst and Notkin method, we
only discuss implementing collaboration-based designs using mixin layers.
Mixin layers [24][25][26] are a particular form of mixins. They are designed with the
purpose of encapsulating refinements for multiple classes. Mixin layers are nested
mixins such that the parameter of an outer mixin determines the parameters of inner
mixins. The general form of a mixin layer in C++ is:
Mixin layers are a result of the observation that a conceptual unit of functionality is
usually neither one object nor parts of an object—a unit of functionality may span sev-
eral different objects and specify refinements (extensions) to all of them. All such
refinements can be encapsulated in a single mixin layer and the standard inheritance
mechanism can be used for composing extensions.
This property of mixin layers makes them particularly attractive for implementing col-
laboration-based designs. Each layer captures a single collaboration. Roles for all
classes participating in a collaboration are represented by inner classes of the layer.
Inheritance works at two different levels. First, a layer can inherit entire classes from
its superclass (i.e., the parameter of the layer). Second, inner classes inherit members
(variables, methods, or even other classes) from the corresponding inner classes in the
superclass layer. This dual application of inheritance simplifies the implementation of
collaboration-based designs, while preserving the benefits of the VanHilst and Notkin
method. An important source of simplifications is that inner classes of a mixin layer
can refer unambiguously to other inner classes—the layer acts as a namespace.
As shown in Fig 1, we can decompose this application into five independent collabora-
tions—one encompassing the functionality of an undirected graph, another encoding
depth-first traversals, and three containing the specifics of each graph algorithm (ver-
tex numbering, cycle checking, and connected regions). Note that each collaboration
Object Classes
captures a distinct aspect of the application and each object may participate in several
aspects. That is to say, each object may play several roles. For instance, the role of a
Graph object in the “Undirected Graph” collaboration supports storing and retrieving
a set of vertices. The role of the same object in the “Depth First Traversal” collabora-
tion implements a part of the actual depth-first traversal algorithm.
Note that no role (nested class) is prescribed for Graph. A Graph class is inherited
from the superclass of Number (the class denoted by parameter Next).
As shown in [24], such components are flexible and can be reused and interchanged.
For instance, the following composition builds Graph, Vertex, and WorkSpace
classes nested inside class CycleC that implement vertex numbering of undirected
graphs using a depth-first traversal:2
typedef DFT < NUMBER < DEFAULTW < UGRAPH > > > CycleC;
By replacing NUMBER with other mixin layers we get the other two graph algorithms
discussed. Many more combinations are possible. We can use the templates to create
classes that implement more than one algorithm. For instance, we can have an applica-
tion supporting both vertex numbering and cycle checking on the same graph by refin-
ing two depth-first traversals in order:
typedef DFT < NUMBER < DEFAULTW < UGRAPH > > > NumberC;
typedef DFT < CYCLE < NumberC > > CycleC;
Furthermore, all the characteristics of an undirected graph are captured by the UGRAPH
mixin layer. Hence, it is straightforward to apply the same algorithms to a directed
graph (mixin layer DGRAPH interchanged for UGRAPH):3
typedef DFT < NUMBER < DEFAULTW < DGRAPH > > > NumberC;
2. The DEFAULTW mixin layer is an implementation detail, borrowed from the VanHilst and Not-
kin implementation [29]. It contains an empty WorkSpace class and its purpose is to avoid
dynamic binding by changing the order of composition.
3. This is under the assumption that the algorithms are still valid for directed graphs as is the
case with the original code for this example [16].
ered function templates.4 Function templates in C++ are instantiated automatically and
only when needed. Thus, even after mixins are composed, not all their methods will be
type-checked (code will only be produced for methods actually referenced in the
object code). This means that certain errors (including type mismatches and references
to undeclared methods) can only be detected with the right template instantiations and
method calls. Consider the following example:
If client code never calls method sort, the compiler will not catch the misspelled
identifier above. This is true even if the ErrorMixin template is used to create
classes, and methods other than sort are invoked on objects of those classes.
When “subtype of” does not mean “substitutable for”. There are two instances
where inheritance may not behave the way one might expect in C++. First, constructor
methods are not inherited. Ellis and Stroustrup ([13], p.264) present valid reasons for
this design choice: the constructor of a superclass does not suffice for initializing data
members added by a subclass. Often, however, a mixin class may be used only to
enrich or adapt the method interface of its superclasses without adding data members.
In this case it would be quite reasonable to inherit a constructor, which, unfortunately,
is not possible. The practical consequence of this policy is that the only constructors
that are visible in the result of a mixin composition are the ones present in the outer-
most mixin (bottom-most class in the resulting inheritance hierarchy). To make matters
worse, constructor initialization lists (e.g.,
constr() : init1(1,2), init2(3) {} )
can only be used to initialize direct parent classes. In other words, all classes need to
know the interface for the constructor of their direct superclass (if they are to use con-
4. This wording, although used by the father of C++—see [28], p.330—is not absolutely accu-
rate since there is no automatic type inference.
structor initialization lists). Recall, however, that a desirable property for mixins is that
they be able to act as components: a mixin should be implementable in isolation from
other parts of the system in which it is used. Thus a single mixin class should be usable
with several distinct superclasses and should have as few dependencies as possible. In
practice, several mixin components are just thin wrappers adapting their superclass’s
interface.
Synonyms for compositions. In the past sections we have used typedefs to intro-
duce synonyms for complicated mixin compositions—e.g.,
typedef A < B < C > > Synonym;
Another reasonable approach would be to introduce an empty subclass:
The first form has the advantage of preserving constructors of component A in the syn-
onym. The second idiom is cleanly integrated into the language (e.g., can be templa-
tized, compilers create short link names for the synonym, etc.). Additionally, it can
solve a common problem with C++ template-based programming: generated names
(template instantiations) can be extremely long, causing compiler messages to be
incomprehensible.
struct Base1 {
virtual void virtual_or_not(FOO foo) { ... }
... // methods using “virtual_or_not”
};
struct Base2 {
void virtual_or_not(FOO foo) { ... }
};
In the general case, this phenomenon allows for interesting mixin configurations.
Classes at an intermediate layer may specify methods and let the inner-most layer
decide whether they are virtual or not.
Single mixin for multiple uses. The lack of template type-checking in C++ can actu-
ally be beneficial in some cases. Consider two classes Base1 and Base2 with very
similar interfaces (except for a few methods):
struct Base1 {
void regular() { ... }
...
};
struct Base2 {
void weird() { ... }
... // otherwise same interface as Base1
};
Because of the similarities between Base1 and Base2, it makes sense to use a single
mixin to adapt both. Such a mixin may need to have methods calling either of the
methods specific to one of the two base classes. This is perfectly feasible. A mixin can
be specified so that it calls either regular or weird:
This is a correct definition and it will do the right thing for both composition
Mixin<Base1> and Mixin<Base2>! What is remarkable is that part of Mixin seems
invalid (calls an undefined method), no matter which composition we decide to per-
form. But, since methods of class templates are treated as function templates, no error
will be signalled unless the program actually uses the wrong method (which may be
meth1 or meth2 depending on the composition). That is, an error will be signalled
only if the program is indeed wrong. We have used this technique to provide uniform,
componentized extensions to data structures supporting slightly different interfaces (in
particular, the red-black tree and hash table of the SGI implementation of the Standard
Template Library [22]).
class Container {
protected:
FINAL::Node* node_alloc() {
return new FINAL::Node();
}
... // Other allocation methods
};
};
class Comp : public BINTREE < ALLOC <int, Comp> > {/* empty */};
Note what is happening in this code fragment (which is abbreviated but preserves the
structure of actual code that we have used). A binary tree data structure is created by
composing a BINTREE mixin layer with an ALLOC mixin layer. The data structure
stores integer (int) elements. Nevertheless, the actual type of the element stored is not
int but a type describing the node of a binary tree (i.e., an integer together with three
pointers for the parent, and the two children of the node). This is the type of element
that the allocator should reserve memory for.
The problem is solved by passing the final product of the composition as a parameter
to the allocator mixin. This is done through the self-referential (or recursive) declara-
tion of class Comp. (Theoretically-inclined readers will recognize this as a fixpoint con-
struction.) Note that Comp is just a synonym for the composition and it has to use the
synonym pattern introducing a class (i.e., the typedef synonym idiom discussed ear-
lier would not work as it does not support recursion).
It should be noted that the above recursive construction has been often used in the liter-
ature. In the C++ world, the technique was introduced by Barton and Nackman [2] and
popularized by Coplien [9]. Nevertheless, the technique is not mixin-specific or even
C++-specific. For instance, it was used by Wadler, Odersky and the first author [32] in
Generic Java [7] (an extension of Java with parametric polymorphism). The origins of
the technique reach back at least to the development of F-bounded polymorphism [8].
Hygienic templates in the C++ standard. The C++ standard ([1], section 14.6)
imposes several rules for name resolution of identifiers that occur inside templates.
The extent to which current compilers implement these rules varies, but full conform-
ance is the best approach to future compatibility for user code.
Although the exact rules are complicated, one can summarize them (at loss of some
detail) as “templates cannot contain code that refers to ‘nonlocal’ variables or meth-
ods”. Intuitively, “nonlocal” denotes variables or methods that do not depend on a tem-
plate parameter and are not in scope at the global point closest to the template
definition. This rule prevents template instantiations from capturing arbitrary names
from their instantiation context, which could lead to behavior not predicted by the tem-
plate author.
struct Base {
void foo() { ... }
};
void foo() { }
That is, the call to foo from method which_one will refer to the global foo, not the
foo method of the Base superclass.
The main implication of these name resolution rules is on the way template-based pro-
grams should be developed. In particular, imagine changing a correct class definition
into a mixin definition (by turning the superclass into a template parameter). Even if
the mixin is instantiated with its superclass in the original code, the new program is not
guaranteed to work identically to the original, because symbols may now be resolved
differently. This may surprise programmers who work by creating concrete classes and
turning them into templates when the need for abstraction arises. To avoid the potential
for insidious bugs, it is a good practice to explicitly qualify references to superclass
methods (e.g., Super::foo instead of just foo).
Compiler support. Most C++ compilers now have good support for parameterized
inheritance (the technique we used for mixins) and nested classes. We have encoun-
tered few problems and mostly with older compilers when programming with C++
mixins. In fact, most of the compiler dependencies are not particular to mixin-based
programming but concern all template-based C++ programs. These include limitations
on the debugging support, error checking, etc. We will not discuss such issues as they
are time-dependent and have been presented before (e.g., [10]). Note, however, that
mixin-based programming is not more complex than regular template instantiation and
typically does not exercise any of the “advanced” features of C++ templates (type
inference, higher-order templates, etc.). Overall, the compiler support issues involved
in mixin-based programming are about the same as those arising in implementing the
C++ Standard Template Library [27].
4 Related Work
Various pieces of related work have been presented in the previous sections. We cannot
exhaustively reference all C++ template-based programming techniques, but we will
discuss two approaches that are distinct from ours but seem to follow parallel courses.
The most prominent example is the various implementations of the STL. Such imple-
mentations often exercise the limits of template support and reveal interactions of C++
policies with template-based programming. Nevertheless, parameterized inheritance is
not a part of STL implementations. Hence, the observations of this paper are mostly
distinct from the conclusions drawn from STL implementation efforts.
5 Conclusions
We presented some pragmatic issues pertaining to mixin-based programming in C++.
We believe that mixin-based techniques are valuable and will become much more
widespread in the future. Mixin-based programming promises to provide reusable soft-
ware components that result into flexible and efficient implementations.
Previous papers have argued for the value of mixin-based software components and
their advantages compared to application frameworks. In this paper we tried to make
explicit the engineering considerations specific to mixin-based programming in C++.
Our purpose is to inform programmers of the issues involved in order to help move
mixin-based programming into the mainstream.
References
[1] ANSI / ISO Standard: Programming Languages—C++, ISO/IEC 14882, 1998.
[2] J. Barton and L.R. Nackman, Scientific and Engineering C++: An Introduction
with Advanced Techniques and Applications, Addison-Wesley, 1994.
[3] D. Batory, V. Singhal, M. Sirkin, and J. Thomas, “Scalable Software Libraries”,
ACM SIGSOFT 1993.
[4] D. Batory, R. Cardone, and Y. Smaragdakis, “Object-Oriented Frameworks and
Product-Lines”, 1st Software Product-Line Conference, Denver, Colorado,
August 1999.
[5] K. Beck and W. Cunningham, “A Laboratory for Teaching Object-Oriented
Thinking”, OOPSLA 1989, 1-6.
[6] G. Bracha and W. Cook, “Mixin-Based Inheritance”, ECOOP/OOPSLA 90, 303-
311.
[7] G. Bracha, M. Odersky, D. Stoutamire and P. Wadler, “Making the future safe for
the past: Adding Genericity to the Java Programming Language”, OOPSLA 98.
[8] P. Canning, W. Cook, W. Hill, W. Olthoff, and J. C. Mitchell, “F-bounded
Polymorphism for Object-Oriented Programming”, in Proc. Conf. on Functional
Programming Languages and Computer Architecture, 1989, 273-280.
[9] J. Coplien, “Curiously Recurring Template Patterns”, C++ Report, 7(2):24-27,
Feb. 1995.
[10] K. Czarnecki and U. Eisenecker. Generative Programming: Methods, Tools, and
Applications. Addison-Wesley, 2000.
[11] K. Czarnecki and U. Eisenecker, “Synthesizing Objects”, ECOOP 1999, 18-42.
[12] U. Eisenecker, “Generative Programming in C++”, in Proc. Joint Modular
Languages Conference (JMLC’97), LNCS 1204, Springer, 1997, 351-365.
[13] M.A. Ellis and B. Stroustrup, The Annotated C++ Reference Manual, Addison-
Wesley, 1990.
[14] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of
Reusable Object-Oriented Software. Addison-Wesley, 1994.
[15] R. Helm, I. Holland, and D. Gangopadhyay, “Contracts: Specifying Behavioral
Compositions in Object-Oriented Systems”. OOPSLA 1990, 169-180.
[16] I. Holland, “Specifying Reusable Components Using Contracts”, ECOOP 1992,
287-308.
[17] R. Johnson and B. Foote, “Designing Reusable Classes”, J. of Object-Oriented
Programming, 1(2): June/July 1988, 22-35.
[18] G. Kiczales, J. des Rivieres, and D. G. Bobrow, The Art of the Metaobject
Protocol. MIT Press, 1991.
[19] O.L. Madsen, B. Møller-Pedersen, and K. Nygaard, Object-Oriented
Programming in the BETA Programming Language. Addison-Wesley, 1993.
[20] D.A. Moon, “Object-Oriented Programming with Flavors”, OOPSLA 1986.
[21] T. Reenskaug, E. Anderson, A. Berre, A. Hurlen, A. Landmark, O. Lehne, E.
Nordhagen, E. Ness-Ulseth, G. Oftedal, A. Skaar, and P. Stenslet, “OORASS:
Seamless Support for the Creation and Maintenance of Object-Oriented
Systems”, J. of Object-Oriented Programming, 5(6): October 1992, 27-41.
[22] Silicon Graphics Computer Systems Inc., STL Programmer’s Guide. See:
http://www.sgi.com/Technology/STL/ .
[23] 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.
[24] Y. Smaragdakis and D. Batory, “Implementing Reusable Object-Oriented
Components”. In the 5th Int. Conf. on Software Reuse (ICSR 98).
[25] Y. Smaragdakis and D. Batory, “Implementing Layered Designs with Mixin
Layers”. In ECOOP 98.
[26] Y. Smaragdakis, “Implementing Large-Scale Object-Oriented Components”,
Ph.D. Dissertation, Department of Computer Sciences, University of Texas at
Austin, December 1999.
[27] A. Stepanov and M. Lee, “The Standard Template Library”. Incorporated in
ANSI/ISO Committee C++ Standard.
[28] B. Stroustrup, The C++ Programming Language, 3rd Ed., Addison-Wesley,
1997.
[29] M. VanHilst and D. Notkin, “Using C++ Templates to Implement Role-Based
Designs”. JSSST International Symposium on Object Technologies for Advanced
Software, Springer-Verlag, 1996, 22-37.
[30] M. VanHilst and D. Notkin, “Using Role Components to Implement
Collaboration-Based Designs”. OOPSLA 1996.
[31] M. VanHilst and D. Notkin, “Decoupling Change From Design”, SIGSOFT 96.
[32] P. Wadler, M. Odersky and Y. Smaragdakis, “Do Parametric Types Beat Virtual
Types?”, unpublished manuscript, posted in October 1998 in the Java Genericity
mailing list (java-genericity@cs.rice.edu).
[33] K. Weihe, “A Software Engineering Perspective on Algorithmics”, available at
http://www.informatik.uni-konstanz.de/Preprints/ .