Mixin-Based Programming In C++ by Ulrich W. Eisenecker, Frank Blinn, and Krzysztof Czarnecki Listing One #include using namespace std; class Customer { public: Customer(const char* fn,const char* ln) :firstname_(fn),lastname_(ln) {} void print() const { cout << firstname_ << ' ' << lastname_; } private: const char *firstname_, *lastname_; }; template class PhoneContact: public Base { public: PhoneContact(const char* fn,const char* ln,const char* pn) :Base(fn,ln),phone_(pn) {} void print() const { Base::print(); cout << ' ' << phone_; } private: const char *phone_; }; template class EmailContact: public Base { public: EmailContact(const char* fn,const char* ln,const char* e) :Base(fn,ln),email_(e) {} void print() const { Base::print(); cout << ' ' << email_; } private: const char *email_; }; int main() { Customer c1("Teddy","Bear"); c1.print(); cout << endl; PhoneContact c2("Rick","Racoon","050-998877"); c2.print(); cout << endl; EmailContact c3("Dick","Deer","dick@deer.com"); c3.print(); cout << endl; // The following composition isn't legal because there // is no constructor that takes all four arguments. // EmailContact > // c4("Eddy","Eagle","049-554433","eddy@eagle.org"); // c4.print(); cout << endl; return 0; } Listing Two #include using namespace std; // Define a new mixin class with a constructor that accepts all arguments. class Customer { public: Customer(const char* fn,const char* ln) :firstname_(fn),lastname_(ln) {} void print() const { cout << firstname_ << ' ' << lastname_; } private: const char *firstname_, *lastname_; }; // The new mixin class will be defined using multiple inheritance. // Therefore Base must be turned into a virtual base class. template class PhoneContact: virtual public Base { public: PhoneContact(const char* fn,const char* ln,const char* pn) :Base(fn,ln),phone_(pn) {} void print() const { Base::print(); basicprint(); } protected: // We need an "inner" print method that prints the PhoneContact-specific // information only. void basicprint() const { cout << ' ' << phone_; } private: const char *phone_; }; // Base has to be declared as virtual base class here, too. template class EmailContact: virtual public Base { public: EmailContact(const char* fn,const char* ln,const char* e) :Base(fn,ln),email_(e) {} void print() const { Base::print(); basicprint(); } protected: // We need an "inner" print method that prints the EmailContact-specific // information only. void basicprint() const { cout << ' ' << email_; } private: const char *email_; }; template class PhoneAndEmailContact : public PhoneContact, public EmailContact { public: // Because Base is a virtual base class, PhoneAndEmailContact is now // responsible for its initialization. PhoneAndEmailContact(const char* fn, const char* ln,char* pn,const char* e) :PhoneContact(fn,ln,pn), EmailContact(fn,ln,e), Base(fn,ln) {} void print() const { Base::print(); PhoneContact::basicprint(); EmailContact::basicprint(); } }; int main() { Customer c1("Teddy","Bear"); c1.print(); cout << endl; PhoneContact c2("Rick","Racoon","050-998877"); c2.print(); cout << endl; EmailContact c3("Dick","Deer","dick@deer.com"); c3.print(); cout << endl; PhoneAndEmailContact c4("Eddy","Eagle","049-554433","eddy@eagle.org"); c4.print(); cout << endl; return 0; } Listing Three #include using namespace std; // Define a class that wraps the union of all constructor arguments // of Customer and all derived mixin classes. // CustomerParameter combines all constructor arguments of CustomerParameter // and its derived mixin classes. The default values for the last two // arguments provide some convenience to the client programmer. struct CustomerParameter { const char * fn, * ln, * pn, * e; CustomerParameter( const char* fn_,const char*ln_, const char* pn_ = "",const char* e_ = "") :fn(fn_),ln(ln_),pn(pn_),e(e_) {} }; class Customer { public: Customer(const CustomerParameter& cp) :firstname_(cp.fn),lastname_(cp.ln) {} void print() const { cout << firstname_ << ' ' << lastname_; } private: const char *firstname_, *lastname_; }; template class PhoneContact: public Base { public: PhoneContact(const CustomerParameter& cp) :Base(cp),phone_(cp.pn) {} void print() const { Base::print(); cout << ' ' << phone_; } private: const char *phone_; }; template class EmailContact: public Base { public: EmailContact(const CustomerParameter& cp) :Base(cp),email_(cp.e) {} void print() const { Base::print(); cout << ' ' << email_; } private: const char *email_; }; int main() { Customer c1(CustomerParameter("Teddy","Bear")); c1.print(); cout << endl; PhoneContact c2(CustomerParameter("Rick","Racoon","050-998877")); c2.print(); cout << endl; EmailContact c3(CustomerParameter("Dick","Deer","","dick@deer.com")); c3.print(); cout << endl; EmailContact > c4(CustomerParameter("Eddy","Eagle","049-554433","eddy@eagle.org")); c4.print(); cout << endl; PhoneContact > // The following composition prints the last two arguments in a // reverse order because the print() method is now composed differently. c5(CustomerParameter("Eddy","Eagle","049-554433","eddy@eagle.org")); c5.print(); cout << endl; return 0; } Listing Four #include using namespace std; // Define special intialization methods in each class and no longer rely // on the proper initialization though constructors. class Customer { public: // Initialization method for Customer. // A default constructor will be generated automatically. void init(const char* fn,const char* ln) { firstname_ = fn; lastname_ = ln; } void print() const { cout << firstname_ << ' ' << lastname_; } private: const char *firstname_, *lastname_; }; template class PhoneContact: public Base { public: // Initialization method for PhoneContact only. // A default constructor will be generated automatically. void init(const char* pn) { phone_ = pn; } void print() const { Base::print(); cout << ' ' << phone_; } private: const char *phone_; }; template class EmailContact: public Base { public: // Initialization method for EmailContact only. // A default constructor will be generated automatically. void init(const char* e) { email_ = e; } void print() const { Base::print(); cout << ' ' << email_; } private: const char *email_; }; int main() { // Compiler generated default constructor gets called. Customer c1; // Now explicitly invoke the initialization method. c1.init("Teddy","Bear"); c1.print(); cout << endl; // Basically the same as above. PhoneContact c2; // But initialization method for Customer must also be explicitly invoked! c2.Customer::init("Rick","Racoon"); c2.init("050-998877"); c2.print(); cout << endl; // Basically the same as above. EmailContact c3; c3.Customer::init("Dick","Deer"); c3.init("dick@deer.com"); c3.print(); cout << endl; // Now the three initialization methods of three different mixin classes // must be explicitly invoked! The composed class does not provide its own // initialization method. EmailContact > c4; c4.Customer::init("Eddy","Eagle"); c4.PhoneContact::init("eddy@eagle.org"); c4.EmailContact >::init("049-554433"); c4.print(); cout << endl; // Basically the same as above. PhoneContact > c5; c5.Customer::init("Eddy","Eagle"); c5.EmailContact::init("eddy@eagle.org"); c5.PhoneContact >::init("049-554433"); c5.print(); cout << endl; return 0; } Listing Five #include using namespace std; // Define additional constructors that will be instantiated only if required. class Customer { public: Customer(const char* fn,const char* ln):firstname_(fn),lastname_(ln) {} void print() const { cout << firstname_ << ' ' << lastname_; } private: const char *firstname_, *lastname_; }; template class PhoneContact: public Base { public: // The following constructors will be instantiated only if required. PhoneContact( const char* fn,const char* ln, const char* pn):Base(fn,ln),phone_(pn) {} PhoneContact( const char* fn,const char* ln, const char* pn,const char* e) :Base(fn,ln,e),phone_(pn) {} void print() const { Base::print(); cout << ' ' << phone_; } private: const char *phone_; }; template class EmailContact: public Base { public: // The following constructors will be instantiated only if required. EmailContact( const char* fn, const char* ln, const char* e):Base(fn,ln),email_(e) {} EmailContact( const char* fn,const char* ln, const char* pn,const char* e) :Base(fn,ln,pn),email_(e) {} void print() const { Base::print(); cout << ' ' << email_; } private: const char *email_; }; int main() { Customer c1("Teddy","Bear"); c1.print(); cout << endl; PhoneContact c2("Rick","Racoon","050-998877"); c2.print(); cout << endl; EmailContact c3("Dick","Deer","dick@deer.com"); c3.print(); cout << endl; EmailContact > c4("Eddy","Eagle","049-554433","eddy@eagle.org"); c4.print(); cout << endl; PhoneContact > // The following composition prints the last two arguments in reverse // order because the print() method is composed differently than previously. c5("Eddy","Eagle","049-554433","eddy@eagle.org"); c5.print(); cout << endl; return 0; } Listing Six #include using namespace std; // We need NIL because - as opposed to void - it must be possible // to create instances of it. struct NIL {}; template struct Param { Param(const T& t_,const Next_& n_ = NIL()):t(t_),n(n_) {} const T& t; Next_ n; typedef Next_ N; }; struct SomePersonParameters { const char *firstname_, *lastname_; int age_; SomePersonParameters(const char* fn,const char* ln,const int age) :firstname_(fn),lastname_(ln),age_(age) {} }; int main() { SomePersonParameters p1("Peter","Parrot",3); cout << p1.firstname_ << ' ' << p1.lastname_ << ' ' << p1.age_ << endl; // Can be rewritten as Param > > p2("Peter",Param >("Parrot",3)); //please note //that we can pass 3 as the last element instead of Param(3) //because it will be automatically converted using the constructor //of Param cout << p2.t << ' ' << p2.n.t << ' ' << p2.n.n.t << endl; // Or more easily readable typedef Param T1; typedef Param T2; typedef Param T3; T3 p3("Peter",T2("Parrot",3)); cout << p3.t << ' ' << p3.n.t << ' ' << p3.n.n.t << endl; return 0; } Listing Seven #include #include using namespace std; struct NIL {}; template struct Param { Param(const T& t_,const Next_& n_ = NIL()):t(t_),n(n_) {} const T& t; Next_ n; typedef Next_ N; }; template class Customer { public: // Exporting config typedef Config_ Config; // Create parameter type typedef Param< typename Config::LastnameType, Param< typename Config::FirstnameType > > ParamType; Customer(const ParamType& p) :lastname_(p.t),firstname_(p.n.t) {} void print() const { cout << firstname_ << ' ' << lastname_; } private: typename Config::FirstnameType firstname_; typename Config::LastnameType lastname_; }; template class PhoneContact: public Base { public: // retrieve config and export it typedef typename Base::Config Config; // retrieve the constructor parameter type from the base class // and extend it with own parameters typedef Param< typename Config::PhoneNoType, typename Base::ParamType > ParamType; PhoneContact(const ParamType& p) :Base(p.n),phone_(p.t) {} void print() const { Base::print(); cout << ' ' << phone_; } private: typename Config::PhoneNoType phone_; }; template class EmailContact: public Base { public: // retrieve config and export it typedef typename Base::Config Config; // retrieve the constructor parameter type from the base class // and extend it with own parameters typedef Param< typename Config::EmailAddressType, typename Base::ParamType> ParamType; EmailContact(const ParamType& p) :Base(p.n),email_(p.t) {} void print() const { Base::print(); cout << ' ' << email_; } private: typename Config::EmailAddressType email_; }; template struct ParameterAdapter: Base { // Retrieve config form Base and export it. typedef typename Base::Config Config; // Retrieve the most complete param type typedef typename Config::RET::ParamType P; typedef typename P::N P1; typedef typename P1::N P2; // Constructor adapter with 1 argument template < class A1 > ParameterAdapter( const A1& a1) :Base(a1) {} // Constructor adapter with 2 arguments template < class A1, class A2 > ParameterAdapter( const A1& a1, const A2& a2) :Base(P(a2,a1)) {} // Constructor adapter with 3 arguments template < class A1, class A2, class A3 > ParameterAdapter( const A1& a1, const A2& a2, const A3& a3) :Base(P(a3,P1(a2,a1))) {} // Constructor adapter with 4 arguments template < class A1, class A2, class A3, class A4 > ParameterAdapter( const A1& a1, const A2& a2, const A3& a3, const A4& a4) :Base(P(a4,P1(a3,P2(a2,a1)))) {} }; struct C1 { // Provide standard name for config typedef C1 ThisConfig; // Provide elementary types typedef const char* FirstnameType; typedef const char* LastnameType; // Parameterize base class typedef Customer CustomerType; // Add ParameterAdapter typedef ParameterAdapter RET; }; struct C2 { // Provide standard name for config typedef C2 ThisConfig; // Provide elementary types typedef const char* FirstnameType; typedef const char* LastnameType; typedef const char* PhoneNoType; // Assemble mixin classes typedef Customer CustomerType; typedef PhoneContact PhoneContactType; // Add ParameterAdapter typedef ParameterAdapter RET; }; struct C3 { // Provide standard name for config typedef C3 ThisConfig; // Provide elementary types typedef const char* FirstnameType; typedef const char* LastnameType; typedef const char* EmailAddressType; // Assemble mixin classes typedef Customer CustomerType; typedef EmailContact EmailContactType; // Add ParameterAdapter typedef ParameterAdapter RET; }; struct C4 { // Provide standard name for config typedef C4 ThisConfig; // Provide elementary types typedef const char* FirstnameType; typedef const char* LastnameType; typedef const char* PhoneNoType; typedef const char* EmailAddressType; // Assemble mixin classes typedef Customer CustomerType; typedef PhoneContact PhoneContactType; typedef EmailContact EmailContactType; // Add ParameterAdapter typedef ParameterAdapter RET; }; int main() { C1::RET c1("Teddy","Bear"); c1.print(); cout << endl; C2::RET c2("Rick","Racoon","050-998877"); c2.print(); cout << endl; C3::RET c3("Dick","Deer","dick@deer.com"); c3.print(); cout << endl; C4::RET c4("Eddy","Eagle","049-554433","eddy@eagle.org"); c4.print(); cout << endl; return 0; } Listing Eight #include #include using namespace std; #include "meta.h" using namespace meta; #ifdef _MSC_VER #pragma warning (disable:4786) #pragma warning (disable:4305) #define GeneratorRET RET #else #define GeneratorRET Generator::RET #endif struct NIL {}; template struct Param { Param(const T& t_,const Next_& n_ = NIL()):t(t_),n(n_) {} const T& t; Next_ n; typedef Next_ N; }; // We will pass Generator to Customer rather than Config; the latter will be // nested in Generator, and Customer has to retrieve it; this modification // of Customer is necessary to avoid certain circularity problems. template class Customer { public: // Exporting config typedef typename Generator_::Config Config; // ... // Rest of Customer is the same as in Listing Seven. // The remaining mixin classes and the parameter adapter // are the same as in Listing Seven. // Bitmask for describing customer options - part of the domain // specific language (DSL) for describing customers enum CustomerSpec { BasicCustomer = 0, // values represent bits in a bitmask WithPhone = 1, WithEmail = 2 }; // Customer generator (the generator parameters represent rest of DSL) template < int spec = BasicCustomer, // spec is a bitmask class Firstname = const char*, class Lastname = const char*, class PhoneNo = const char*, class EmailAdd = const char* > struct CUSTOMER_GENERATOR { // Provide a shorthand for CUSTOMER_GENERATOR ... typedef CUSTOMER_GENERATOR < spec, Firstname, Lastname, PhoneNo, EmailAdd > Generator; // Parse DSL // Assume there is always a basic customer ... enum { hasPhone = spec & WithPhone, hasEmail = spec & WithEmail }; // Assemble components typedef Customer Part1; typedef typename IF < hasPhone, PhoneContact, Part1 >::RET Part2; typedef typename IF < hasEmail, EmailContact, Part2 >::RET Part3; // Result of the generator template metafunction: typedef ParameterAdapter RET; // Compute config struct BasicCustomerConfig { // Provide some metainformation enum { specification = spec }; typedef Firstname FirstnameType; typedef Lastname LastnameType; typedef GeneratorRET RET; }; struct CustomerWithPhoneConfig: BasicCustomerConfig { typedef PhoneNo PhoneNoType; }; struct CustomerWithEmailConfig: BasicCustomerConfig { typedef EmailAdd EmailAddressType; }; struct CustomerWithPhoneAndEmailConfig : CustomerWithPhoneConfig,CustomerWithEmailConfig {}; typedef typename SWITCH < spec, CASE > > > >::RET Config; }; int main() { CUSTOMER_GENERATOR<>::RET c1("Teddy","Bear"); c1.print(); cout << endl; CUSTOMER_GENERATOR::RET c2("Rick","Racoon","050-998877"); c2.print(); cout << endl; CUSTOMER_GENERATOR::RET c3("Dick","Deer","dick@deer.com"); c3.print(); cout << endl; CUSTOMER_GENERATOR::RET c4("Eddy","Eagle","049-554433","eddy@eagle.org"); c4.print(); cout << endl; return 0; } 15