320 likes | 344 Views
This article provides an introduction to generic programming (GP) in C++. It explains what GP is, discusses algorithmic GP, and explores GP techniques and patterns in C++. The article also covers concepts, refinement of concepts, and types that fulfill concepts. It highlights the benefits and limitations of GP and compares it to object-oriented programming (OOP).
E N D
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 DefaultConstructibleCopyable Assignable Addable Convertible to OtherTypeOutputStreamable BarConcept Valid expressions 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>OutputIteratorcopy(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