Generic Programming Johan Torp Agenda • GP introduction • GP in perspective • Questions * GP = Generic programming in C++ What is GP? • Algorithmic GP • GP techniques & patterns in C++ Polymorphism class BarInterface { virtual void bar() = 0; }; void polymorphicFunction(BarInterface& t) { ... t.bar(); ... } template<class T> void polymorphicFunction(T& t) { ... t.bar(); ... } GP interface is called concept // MyBarConcept // // Has a memberfunction called bar() // ... more assumptions ... // T must fulfill MyBarConcept template<class T> void polymorphicFunction1(T& bar); // T must fulfill MyBarConcept template<class T> void polymorphicFunction200(T& bar); class MyBar { void bar(); }; Example concepts Concept Valid expressions DefaultConstructible Copyable Assignable Addable Convertible to OtherType OutputStreamable BarConcept T t; T t2(t1); t1 = t2 t1+t2; static_cast<OtherType>(t); stream << t; t.bar(); Require as little as possible for maximal genericity Concepts are type requirements • Valid expressions, pre/post conditions & semantics • Additionally: Invariants, associated types, complexity guarantees, etc Concepts in C++ are expressed in documentation Types which fulfill concept C are said to model C Refine new concepts from old ones by adding additional requirements Input Iterator Description An Input Iterator is an iterator that may be dereferenced to refer to ... Refinement of Trivial Iterator Associated Types Value type - The type obtained by dereferencing ... Distance type - A signed integral type used to ... Valid expressions Dereference *i Pre-increment ++i Expression semantics Dereference Will return a reference to the accessed value type Pre-increment Will ... Complexity guarantees All operations are amortized constant time Template instantiation class OneBar { void bar() }; class AnotherBar { void bar() }; Source1.cpp: polymorphicFunction(int(123)); Source2.cpp: polymorphicFunction(OneBar()); polymorphicFunction(OneBar()); polymorphicFunction(AnotherBar()); Source3.cpp: polymorphicFunction(OneBar()); Generalize memcpy void* memcpy(void* region1, const void* region2,size_t n); • Only reads contigous memory • Only writes to contigous memory • Only byte-wise copying Minimal requirements of copying • • • • traverse through source sequence access source elements copy elements to destination know when to stop STL version of copy template <class InputIterator, class OutputIterator> OutputIterator copy(InputIterator first, InputIterator pastLast, OutputIterator result) { while (!(first == pastLast)) // Don't require != operator *result++ = *first++; return result; } How pointless?! • Real std::copy is optimized for different types • Solves optimized generic copying once and for all C++ language features supporting GP Dispatching features • Inheritance • Templates • Namespaces & argument dependent lookup (ADL) • Function & operator overloading • Implicit type conversion • SFINAE • [Partial] template specialization Other useful language features • Dependent types • Template type deduction • Template non-type arguments (compile-time integers, bools, etc) • Template template arguments GP techniques & patterns • • • • • • • • • type traits mix-in classes policy based design object generators enable if tag dispatching type erasure lazy data views concept and archetype checking GP related programming models: • template metaprogramming • expression templates to create DSELs • macro metaprogramming GP in practice • Generalize patterns / boiler plate into generic classes • Library based programming models GP problems • Compilation & link time • Code size • Language flaws How much GP can we afford? Maybe we can use more GP because: • • • • • • Compilers have improved Well modularized => recompile less Master/unity builds lessens template bloat Stability & higher abstraction level allows faster iteration too Explicit template instantiation can lessen template bloat Replacing boilerplate interfaces with type erasure gives similar run-time performance GP flaws fixed in C++0x • • • • • • • • Concept code support True variadic templates Type deduction and type inference of named variables Rvalue references Preventing template instantiation Axioms Object construction improvements ... and lots more! Compilers are continously adding C++0x features GP vs OOP: Efficiency and compilation GP more efficient run-time OO code requires less compilation GP vs OOP: Dependencies • Concrete OO classes and polymorphic code are coupled via the interface • A type might fulfill/model a concept. Polymorphic code only depends on concept via documentation A.k.a. : ad-hoc polymorphism vs parametric polymorphism GP vs OOP: Customization points Mix-in classes change interface, state, data layout, etc template <class MixIn> class Foo : public MixIn {...}; Type traits & tags can non-intrusively add static information template<class T> struct iterator_traits<T*> { typedef random_access_iterator_tag iterator_category;}; GP vs OOP: Orthogonality template<class Aspect1, class Aspect2, class Aspect3> class TweakableGPClass{ ... }; class TweakableOOClass{ TweakableOOClass(Aspect1&, Aspect2&, Aspect3&); private: Aspect1& m_a1; ... }; Composition problems • Boiler-plate • Run-time costs (vtable calls, method forwarding, extra state) GP vs OOP: Dispatch • OOP is only polymorphic on one type - the object type • GP is polymorphic on all parameter types • GP also allow complex dispatch logic! // Default fallback, unless no other function matches better template<class Object1, class Object2> void handleCollision(Object1&, Object2&); // Any object going into a black hole disappears template<Object1> void handleCollision(Object1&, BlackHole&); // Unless it's another black hole -- then we get // a super nova explosion instead. Yay! template<> void handleCollision(BlackHole&, BlackHole&); // Missiles which hit anything damagable inflicts damage template<class Object2> enable_if<void, isDamagable<Object2>::value> handleCollision(Missile&, Object2&); GP vs OOP: Simplicity • More power = more ways to shoot ourselves in the foot • OO is less abstract GP is best suited to build generic libraries - which in turn provide simple and limited programming models C++ multiparadigm programming • Object orientation • Generic programming • Procedural programming GP and dynamically typed languages def foo(t): """t must model bar""" ... t.bar() ... • Compiled separately, extremely fast iteration time • Late binding can provide poor runtime performance • Nothing checked statically, rely on interpreters & unit tests A reflection on reflection Reflection allows a program to observe and modify its own structure and behavior at runtime • GP dispatch techniques are similar to run-time reflection • GP allows static analysis => understandable behaviour GP is generative Macro > Template instantiation > Compilation • Work on multiple "compilation levels" in the same file at the same time Code generation • Raise abstraction level without runtime penalty • Pay in terms of build system complexity • Identify boiler plate & patterns and generate them Summary • • • • • • • • • • Concepts are type requirements Template instantiation Algorithmic GP GP techniques GP problems GP classes are highly customizable and reusable GP vs OOP GP is useful for core libraries & algorithms Dynamic programming languages & reflection Code generation Questions?